From e191ca0a7c18e6bfdabcd02aff4ddf86727c34c6 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Fri, 30 Apr 2021 22:48:01 +0000 Subject: Filter package visibility in checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(). Bug: 186404356 Test: manual Change-Id: I2b7afbba024d27fd1cb3e21a4b5abcd1d212eada --- .../server/pm/permission/PermissionManagerService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 2d1178a3f116..0f23538081d2 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1152,6 +1152,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission( @NonNull String permName) { + final String permissionPackageName; final boolean isImmutablyRestrictedPermission; synchronized (mLock) { final Permission bp = mRegistry.getPermission(permName); @@ -1159,15 +1160,25 @@ public class PermissionManagerService extends IPermissionManager.Stub { Slog.w(TAG, "No such permissions: " + permName); return false; } + permissionPackageName = bp.getPackageName(); isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted() && bp.isImmutablyRestricted(); } + + final int callingUid = getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) { + EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName); + return false; + } + if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission( Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Cannot modify allowlisting of an immutably " + "restricted permission: " + permName); } + return true; } -- cgit v1.2.3 From 19a655d70c100253b5874f3d9b751877b348e5c4 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Tue, 20 Jul 2021 00:01:29 +0000 Subject: Prevend user spoofing in isRequestPinItemSupported This CL ensure the caller process is from the same user when calling ShortcutService#isRequestPinItemSupported. Bug: 191772737 Test: atest ShortcutManagerTest1 ShortcutManagerTest2 ShortcutManagerTest3 ShortcutManagerTest4 ShortcutManagerTest5 ShortcutManagerTest6 ShortcutManagerTest7 ShortcutManagerTest8 ShortcutManagerTest9 ShortcutManagerTest10 ShortcutManagerTest11 ShortcutManagerTest12 Test: atest CtsShortcutManagerTestCases Change-Id: Icab7cdf25b870b88ecfde9b99e107bbeda0eb485 --- .../core/java/com/android/server/pm/ShortcutService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index fcbf40e29933..62d6717e847a 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1664,6 +1664,19 @@ public class ShortcutService extends IShortcutService.Stub { mContext.enforceCallingPermission(permission, message); } + private void verifyCallerUserId(@UserIdInt int userId) { + if (isCallerSystem()) { + return; // no check + } + + final int callingUid = injectBinderCallingUid(); + + // Otherwise, make sure the arguments are valid. + if (UserHandle.getUserId(callingUid) != userId) { + throw new SecurityException("Invalid user-ID"); + } + } + private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) { Preconditions.checkStringNotEmpty(packageName, "packageName"); @@ -2847,6 +2860,8 @@ public class ShortcutService extends IShortcutService.Stub { @Override public boolean isRequestPinItemSupported(int callingUserId, int requestType) { + verifyCallerUserId(callingUserId); + final long token = injectClearCallingIdentity(); try { return mShortcutRequestPinProcessor -- cgit v1.2.3 From 3582d26e2aee4f4b43238864ff5f41bf8e9c7fb9 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Fri, 23 Jul 2021 15:43:22 -0700 Subject: Early exit if the target region is empty Bug: 193849901 Test: atest StaticLayoutBidiTouchTest Change-Id: I55f04068a6e5b353867d50742356c272d28886b3 --- core/java/android/text/TextLine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 1f11d10052fe..1a7ec7f99c95 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -782,7 +782,7 @@ public class TextLine { int spanStart = runStart; int spanLimit; - if (mSpanned == null) { + if (mSpanned == null || runStart == runLimit) { spanLimit = runLimit; } else { int target = after ? offset + 1 : offset; -- cgit v1.2.3 From 1877d8ec29b19a3997cc3dfb884059bfb53b05f9 Mon Sep 17 00:00:00 2001 From: Aseem Kumar Date: Mon, 17 May 2021 09:25:03 +0000 Subject: Prevent apps from spamming addAccountExplicitly. See comment here for the discussion on solution https://b.corp.google.com/issues/169762606#comment14 Change-Id: If212df3a3b7be1de0fb26b8e88b2fcbb8077c253 Bug: 169762606 (cherry picked from commit 11053c17b397db67b20e96ce769508766cef7db9) Change-Id: Ia048d20c377ed2bde0efb0506ec61ae9d8356bf0 Merged-In: If212df3a3b7be1de0fb26b8e88b2fcbb8077c253 --- core/java/android/accounts/Account.java | 7 +++++++ .../java/com/android/server/accounts/AccountManagerService.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/core/java/android/accounts/Account.java b/core/java/android/accounts/Account.java index 0d6a07938e95..e6cdcc0ee742 100644 --- a/core/java/android/accounts/Account.java +++ b/core/java/android/accounts/Account.java @@ -31,6 +31,7 @@ import android.util.Log; import com.android.internal.annotations.GuardedBy; +import java.util.Objects; import java.util.Set; /** @@ -86,6 +87,12 @@ public class Account implements Parcelable { if (TextUtils.isEmpty(type)) { throw new IllegalArgumentException("the type must not be empty: " + type); } + if (name.length() > 200) { + throw new IllegalArgumentException("account name is longer than 200 characters"); + } + if (type.length() > 200) { + throw new IllegalArgumentException("account type is longer than 200 characters"); + } this.name = name; this.type = type; this.accessId = accessId; diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index a6a8cf018eef..400b084ee966 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -1833,6 +1833,11 @@ public class AccountManagerService + ", skipping since the account already exists"); return false; } + if (accounts.accountsDb.findAllDeAccounts().size() > 100) { + Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString() + + ", skipping since more than 50 accounts on device exist"); + return false; + } long accountId = accounts.accountsDb.insertCeAccount(account, password); if (accountId < 0) { Log.w(TAG, "insertAccountIntoDatabase: " + account.toSafeString() -- cgit v1.2.3 From 8f71a21677fc9d8887ec1aca35fb4f0afecec638 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Wed, 14 Jul 2021 13:15:19 -0700 Subject: [QPR1] Fix dismiss not working in landscape In stack animation controller only one target would ever be added so when the target is re-created after rotating then it wouldn't be used. Test: - have some bubbles, drag them around, rotate device, try to dismiss them => they can be dismissed in landscape - get more bubbles, rotate back to portrait, try to dismiss them => they can be dismissed in portrait - drag the stack around the edges of the screen and verify that the only magnetic spot is where the dismiss target is Fixes: 193008696 Change-Id: I6b24b1f9d301d37749dc67c95392fd6c8451df00 Merged-In: I6b24b1f9d301d37749dc67c95392fd6c8451df00 --- .../android/wm/shell/bubbles/BubbleStackView.java | 21 ++++++++------------- .../bubbles/animation/StackAnimationController.java | 7 ++----- .../shell/common/magnetictarget/MagnetizedObject.kt | 7 +++++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index c0df06f2954f..ac97c8f80617 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -576,20 +576,17 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.setActiveController(mStackAnimationController); hideFlyoutImmediate(); - if (!mPositioner.showingInTaskbar()) { - // Also, save the magnetized stack so we can dispatch touch events to it. - mMagnetizedObject = mStackAnimationController.getMagnetizedStack( - mMagneticTarget); - mMagnetizedObject.setMagnetListener(mStackMagnetListener); - } else { + if (mPositioner.showingInTaskbar()) { // In taskbar, the stack isn't draggable so we shouldn't dispatch touch events. mMagnetizedObject = null; + } else { + // Save the magnetized stack so we can dispatch touch events to it. + mMagnetizedObject = mStackAnimationController.getMagnetizedStack(); + mMagnetizedObject.clearAllTargets(); + mMagnetizedObject.addTarget(mMagneticTarget); + mMagnetizedObject.setMagnetListener(mStackMagnetListener); } - // Also, save the magnetized stack so we can dispatch touch events to it. - mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget); - mMagnetizedObject.setMagnetListener(mStackMagnetListener); - mIsDraggingStack = true; // Cancel animations to make the stack temporarily invisible, since we're now @@ -881,7 +878,6 @@ public class BubbleStackView extends FrameLayout mRelativeStackPositionBeforeRotation = null; } - setUpDismissView(); if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. beforeExpandedViewAnimation(); @@ -1043,10 +1039,9 @@ public class BubbleStackView extends FrameLayout contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */); // Save the MagneticTarget instance for the newly set up view - we'll add this to the - // MagnetizedObjects. + // MagnetizedObjects when the dismiss view gets shown. mMagneticTarget = new MagnetizedObject.MagneticTarget( mDismissView.getCircle(), dismissRadius); - mBubbleContainer.bringToFront(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java index 0802fb59a008..636e1452aa9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java @@ -1024,11 +1024,9 @@ public class StackAnimationController extends } /** - * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided - * {@link MagnetizedObject.MagneticTarget} added as a target. + * Returns the {@link MagnetizedObject} instance for the bubble stack. */ - public MagnetizedObject getMagnetizedStack( - MagnetizedObject.MagneticTarget target) { + public MagnetizedObject getMagnetizedStack() { if (mMagnetizedStack == null) { mMagnetizedStack = new MagnetizedObject( mLayout.getContext(), @@ -1053,7 +1051,6 @@ public class StackAnimationController extends loc[1] = (int) mStackPosition.y; } }; - mMagnetizedStack.addTarget(target); mMagnetizedStack.setHapticsEnabled(true); mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt index 9f6dd1f27b62..9e012598554b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt @@ -302,6 +302,13 @@ abstract class MagnetizedObject( associatedTargets.remove(target) } + /** + * Removes all associated targets from this object. + */ + fun clearAllTargets() { + associatedTargets.clear() + } + /** * Provide this method with all motion events that move the magnetized object. If the * location of the motion events moves within the magnetic field of a target, or indicate a -- cgit v1.2.3 From 55479777dd42067ff5e8830454c18f8bb817757d Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Thu, 29 Jul 2021 14:29:50 -0700 Subject: DO NOT MERGE [QPR1] Fix QS having a white when opened over bubbles mBehindTint now controls the background of quick settings. I think this wasn't the case in R when the bubble state was written & it was never updated appropriately whenever that changed. Seems like most of the code in the unlocked state should apply to bubbles so just copied it in and updated the test. Test: atest ScrimControllerTest Bug: 191338071 Change-Id: I90fa26760d346a67d42b22d8e562d5b4c66f3017 --- .../systemui/statusbar/phone/ScrimState.java | 34 +++++++++++++++++++--- .../statusbar/phone/ScrimControllerTest.java | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 06811932ac0c..a73fec8455d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -278,15 +278,41 @@ public enum ScrimState { BUBBLE_EXPANDED { @Override public void prepare(ScrimState previousState) { + mBehindAlpha = mClipQsScrim ? 1 : 0; + mNotifAlpha = 0; + mFrontAlpha = 0; + + mAnimationDuration = mKeyguardFadingAway + ? mKeyguardFadingAwayDuration + : StatusBar.FADE_KEYGUARD_DURATION; + + mAnimateChange = !mLaunchingAffordanceWithPreview; + mFrontTint = Color.TRANSPARENT; - mBehindTint = Color.TRANSPARENT; + mBehindTint = Color.BLACK; mBubbleTint = Color.BLACK; + mBlankScreen = false; - mFrontAlpha = 0f; - mBehindAlpha = mDefaultScrimAlpha; + if (previousState == ScrimState.AOD) { + // Set all scrims black, before they fade transparent. + updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */); + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */); + if (mScrimForBubble != null) { + updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */); + } + + // Scrims should still be black at the end of the transition. + mFrontTint = Color.BLACK; + mBehindTint = Color.BLACK; + mBubbleTint = Color.BLACK; + mBlankScreen = true; + } + + if (mClipQsScrim) { + updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK); + } mAnimationDuration = ScrimController.ANIMATION_DURATION; - mBlankScreen = false; } }; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 678b193073c2..73164fa35fcc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -621,7 +621,7 @@ public class ScrimControllerTest extends SysuiTestCase { assertScrimTinted(Map.of( mScrimInFront, false, - mScrimBehind, false, + mScrimBehind, true, mScrimForBubble, true )); -- cgit v1.2.3 From fef9b3757c4a31f60592f25c4e056f9e2ae9444b Mon Sep 17 00:00:00 2001 From: Winson Date: Thu, 8 Jul 2021 12:07:08 -0700 Subject: Unexport all PackageInstaller receivers All of PackageInstaller's declared receivers receive system broadcasts, so they do not need to be exported. Leaving them exported opens up vulnerabilities for apps to launch them and execute functionality that PackageInstaller includes. Bug: 191283525 Test: manual, verify receivers still work Test: atest com.android.packageinstaller.test.ExportedComponentTest Merged-In: I782fc21c848831a2a4aeab736cd47ad45781b290 Change-Id: I782fc21c848831a2a4aeab736cd47ad45781b290 (cherry picked from commit 0806568d4133e1dc736ebcc45f62655165366a9e) --- packages/PackageInstaller/AndroidManifest.xml | 8 ++-- packages/PackageInstaller/TEST_MAPPING | 5 ++- .../PackageManager/packageinstaller/Android.bp | 39 +++++++++++++++++ .../packageinstaller/AndroidManifest.xml | 29 +++++++++++++ .../packageinstaller/AndroidTest.xml | 29 +++++++++++++ .../packageinstaller/test/ExportedComponentTest.kt | 50 ++++++++++++++++++++++ 6 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 services/tests/PackageManager/packageinstaller/Android.bp create mode 100644 services/tests/PackageManager/packageinstaller/AndroidManifest.xml create mode 100644 services/tests/PackageManager/packageinstaller/AndroidTest.xml create mode 100644 services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml index 48cdf1647d86..197b7b2b0eaf 100644 --- a/packages/PackageInstaller/AndroidManifest.xml +++ b/packages/PackageInstaller/AndroidManifest.xml @@ -31,7 +31,7 @@ android:directBootAware="true"> + android:exported="false"> @@ -76,7 +76,7 @@ + android:exported="false"> @@ -106,14 +106,14 @@ + android:exported="false"> + android:exported="false"> diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING index 5d7b9bb36f75..cef9014ec229 100644 --- a/packages/PackageInstaller/TEST_MAPPING +++ b/packages/PackageInstaller/TEST_MAPPING @@ -19,6 +19,9 @@ }, { "name": "CtsPackageUninstallTestCases" + }, + { + "name": "PackageInstallerTests" } ] -} \ No newline at end of file +} diff --git a/services/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp new file mode 100644 index 000000000000..35d754b4adc5 --- /dev/null +++ b/services/tests/PackageManager/packageinstaller/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "PackageInstallerTests", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + static_libs: [ + "androidx.test.rules", + "androidx.test.runner", + "junit", + "kotlin-test", + "truth-prebuilt", + ], + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/services/tests/PackageManager/packageinstaller/AndroidManifest.xml b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml new file mode 100644 index 000000000000..d7062587c4bc --- /dev/null +++ b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/services/tests/PackageManager/packageinstaller/AndroidTest.xml b/services/tests/PackageManager/packageinstaller/AndroidTest.xml new file mode 100644 index 000000000000..c39285ffca38 --- /dev/null +++ b/services/tests/PackageManager/packageinstaller/AndroidTest.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt new file mode 100644 index 000000000000..d7d2726c1583 --- /dev/null +++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.packageinstaller.test + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import androidx.test.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import org.junit.Test + +class ExportedComponentTest { + + private val context: Context = InstrumentationRegistry.getContext() + + @Test + fun verifyNoExportedReceivers() { + val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply { + data = Uri.parse("content://mockForTest") + } + val packageInstallers = context.packageManager.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_DISABLED_COMPONENTS) + .map { it.activityInfo.packageName } + .distinct() + .map { context.packageManager.getPackageInfo(it, PackageManager.GET_RECEIVERS) } + + assertThat(packageInstallers).isNotEmpty() + + packageInstallers.forEach { + val exported = it.receivers.filter { it.exported } + assertWithMessage("Receivers should not be exported").that(exported).isEmpty() + } + } +} -- cgit v1.2.3 From 3418e49eb9689f3a8649914fdf93a1ce80fca76c Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Wed, 11 Aug 2021 00:13:33 +0000 Subject: Fix hard edge on charging ripple. Bug: 196132025 Test: Manual Change-Id: I12eed187fcffc352de5637142b3189c159b3f097 --- .../src/com/android/systemui/statusbar/charging/ChargingRippleView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt index 4a467ce3c987..d01fc93ee84c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt @@ -104,7 +104,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context // the active effect area. Values here should be kept in sync with the // animation implementation in the ripple shader. val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 1.5f + (1 - rippleShader.progress)) * radius * 2 canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) } } -- cgit v1.2.3 From 3f3563c3ae329b5e31c815f91138a2a61c0aa71d Mon Sep 17 00:00:00 2001 From: Govinda Wasserman Date: Tue, 10 Aug 2021 23:10:00 -0400 Subject: Fixes AssistOrbController crash AssistOrbController caused a crash by trying to remove a view that was not attached. This change adds a check if the view is attached before removal. Test: Tested locally BUG: 195902117 FIX: 195902117 Change-Id: Ibc65a9b1789c7ee629c28de31392fffc0536ed70 --- .../SystemUI/src/com/android/systemui/assist/AssistOrbController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java index 81a13a236685..408201558a9b 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java @@ -57,7 +57,9 @@ public class AssistOrbController { public void run() { mView.removeCallbacks(this); mView.show(false /* show */, true /* animate */, () -> { - mWindowManager.removeView(mView); + if (mView.isAttachedToWindow()) { + mWindowManager.removeView(mView); + } }); } }; -- cgit v1.2.3 From 3796d9756a3e62988813051a24aca7a7db58ca2b Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 30 Jul 2021 13:47:52 -0400 Subject: Simply ignore invalid keys Test: NotificationManagerServiceTest Fixes: 194697004 Fixes: 194697001 Change-Id: Ide8d56b8d76019304e0b6339ec15d14ca462d0f2 (cherry picked from commit 70d1d86d831961a22c9e9dc060072bb3a3d3e707) --- .../notification/NotificationManagerService.java | 6 +-- .../NotificationManagerServiceTest.java | 46 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b54e8f973bd6..207baf539345 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4363,8 +4363,7 @@ public class NotificationManagerService extends SystemService { final int userId = r.getSbn().getUserId(); if (userId != info.userid && userId != UserHandle.USER_ALL && !mUserProfiles.isCurrentProfile(userId)) { - throw new SecurityException("Disallowed call from listener: " - + info.service); + continue; } cancelNotificationFromListenerLocked(info, callingUid, callingPid, r.getSbn().getPackageName(), r.getSbn().getTag(), @@ -4431,8 +4430,7 @@ public class NotificationManagerService extends SystemService { final int userId = r.getSbn().getUserId(); if (userId != info.userid && userId != UserHandle.USER_ALL && !mUserProfiles.isCurrentProfile(userId)) { - throw new SecurityException("Disallowed call from listener: " - + info.service); + continue; } seen.add(r); if (!r.isSeen()) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f57c416e4a97..7bbf3e6c3b2e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4792,6 +4792,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mAppUsageStats).reportInterruptiveNotification(anyString(), anyString(), anyInt()); } + @Test + public void testSetNotificationsShownFromListener_protectsCrossUserInformation() + throws RemoteException { + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE), + null, 0); + final NotificationRecord r = + new NotificationRecord(mContext, sbn, mTestNotificationChannel); + r.setTextChanged(true); + mService.addNotification(r); + + // no security exception! + mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()}); + + verify(mAppUsageStats, never()).reportInterruptiveNotification( + anyString(), anyString(), anyInt()); + } + + @Test + public void testCancelNotificationsFromListener_protectsCrossUserInformation() + throws RemoteException { + Notification.Builder nb = new Notification.Builder( + mContext, mTestNotificationChannel.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, + "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE), + null, 0); + final NotificationRecord r = + new NotificationRecord(mContext, sbn, mTestNotificationChannel); + r.setTextChanged(true); + mService.addNotification(r); + + // no security exception! + mBinderService.cancelNotificationsFromListener(null, new String[] {r.getKey()}); + + waitForIdle(); + assertEquals(1, mService.getNotificationRecordCount()); + } + @Test public void testMaybeRecordInterruptionLocked_doesNotRecordTwice() throws RemoteException { -- cgit v1.2.3 From 408d0a2a53b1f70d26f1fb43063bf1350978930f Mon Sep 17 00:00:00 2001 From: Beverly Date: Mon, 2 Aug 2021 14:57:29 -0400 Subject: On devices with udfps supported, show auth ripple Show the auth ripple if the user enters the device via the unlock icon (where the udfps icon would normally be). Test: manual Fixes: 195162787 Change-Id: Ie65dce30c009f94a0472f566c3663a0de2bc063b --- .../src/com/android/keyguard/LockIconViewController.java | 9 ++++++++- .../src/com/android/systemui/biometrics/AuthRippleController.kt | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 509ac8a6d9fe..aa8cbd710e90 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -46,6 +46,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; @@ -99,6 +100,7 @@ public class LockIconViewController extends ViewController impleme @NonNull private CharSequence mUnlockedLabel; @NonNull private CharSequence mLockedLabel; @Nullable private final Vibrator mVibrator; + @Nullable private final AuthRippleController mAuthRippleController; private boolean mIsDozing; private boolean mIsBouncerShowing; @@ -135,7 +137,8 @@ public class LockIconViewController extends ViewController impleme @NonNull AccessibilityManager accessibilityManager, @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, - @Nullable Vibrator vibrator + @Nullable Vibrator vibrator, + @Nullable AuthRippleController authRippleController ) { super(view); mStatusBarStateController = statusBarStateController; @@ -148,6 +151,7 @@ public class LockIconViewController extends ViewController impleme mConfigurationController = configurationController; mExecutor = executor; mVibrator = vibrator; + mAuthRippleController = authRippleController; final Context context = view.getContext(); mUnlockIcon = mView.getContext().getResources().getDrawable( @@ -538,6 +542,9 @@ public class LockIconViewController extends ViewController impleme // pre-emptively set to true to hide view mIsBouncerShowing = true; + if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { + mAuthRippleController.showRipple(FINGERPRINT); + } updateVisibility(); mKeyguardViewController.showBouncer(/* scrim */ true); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 1df8ad5e51fb..66b45e5900bf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -79,7 +79,7 @@ class AuthRippleController @Inject constructor( notificationShadeWindowController.setForcePluginOpen(false, this) } - private fun showRipple(biometricSourceType: BiometricSourceType?) { + fun showRipple(biometricSourceType: BiometricSourceType?) { if (!keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.userNeedsStrongAuth()) { return -- cgit v1.2.3 From e21597da375818850c6e1289455e74efa5530cfe Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Wed, 11 Aug 2021 13:04:50 -0400 Subject: Crash on device controls availability Post the incoming event to the view's handler to avoid crashing when attempting to update the on the wrong thread. Fixes: 196008691 Test: manual (cannot repro locally but would most likely happen after reboot) Change-Id: I3777d5a43e0ff3a56910124a93670eef1b027fe0 --- .../systemui/statusbar/phone/KeyguardBottomAreaView.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 0a4e59c0391e..4701d8b32ee6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -203,13 +203,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsListingController.ControlsListingCallback mListingCallback = new ControlsListingController.ControlsListingCallback() { public void onServicesUpdated(List serviceInfos) { - boolean available = !serviceInfos.isEmpty(); + post(() -> { + boolean available = !serviceInfos.isEmpty(); - if (available != mControlServicesAvailable) { - mControlServicesAvailable = available; - updateControlsVisibility(); - updateAffordanceColors(); - } + if (available != mControlServicesAvailable) { + mControlServicesAvailable = available; + updateControlsVisibility(); + updateAffordanceColors(); + } + }); } }; -- cgit v1.2.3 From 04bcff1e9f5d4f718bd2e9ac2feb8afc7abea32c Mon Sep 17 00:00:00 2001 From: Mark Renouf Date: Wed, 11 Aug 2021 18:57:28 +0000 Subject: Return packageName to scroll capture requests from SysUI Bug: 195013652 Test: manual Change-Id: Ib623e34031b7a5117d9c5afb439fc382f9c77dd1 --- core/java/android/view/ScrollCaptureResponse.java | 49 +++++++++++++++++++---- core/java/android/view/ViewRootImpl.java | 1 + 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java index 8808827b248a..758f9ab935cf 100644 --- a/core/java/android/view/ScrollCaptureResponse.java +++ b/core/java/android/view/ScrollCaptureResponse.java @@ -53,6 +53,10 @@ public class ScrollCaptureResponse implements Parcelable { @Nullable private String mWindowTitle = null; + /** The package which owns the window. */ + @Nullable + private String mPackageName = null; + /** Carries additional logging and debugging information when enabled. */ @NonNull @DataClass.PluralOf("message") @@ -77,7 +81,7 @@ public class ScrollCaptureResponse implements Parcelable { - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -97,6 +101,7 @@ public class ScrollCaptureResponse implements Parcelable { @Nullable Rect windowBounds, @Nullable Rect boundsInWindow, @Nullable String windowTitle, + @Nullable String packageName, @NonNull ArrayList messages) { this.mDescription = description; com.android.internal.util.AnnotationValidations.validate( @@ -105,6 +110,7 @@ public class ScrollCaptureResponse implements Parcelable { this.mWindowBounds = windowBounds; this.mBoundsInWindow = boundsInWindow; this.mWindowTitle = windowTitle; + this.mPackageName = packageName; this.mMessages = messages; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mMessages); @@ -152,6 +158,14 @@ public class ScrollCaptureResponse implements Parcelable { return mWindowTitle; } + /** + * The package name of the process the window is owned by. + */ + @DataClass.Generated.Member + public @Nullable String getPackageName() { + return mPackageName; + } + /** * Carries additional logging and debugging information when enabled. */ @@ -172,6 +186,7 @@ public class ScrollCaptureResponse implements Parcelable { "windowBounds = " + mWindowBounds + ", " + "boundsInWindow = " + mBoundsInWindow + ", " + "windowTitle = " + mWindowTitle + ", " + + "packageName = " + mPackageName + ", " + "messages = " + mMessages + " }"; } @@ -187,12 +202,14 @@ public class ScrollCaptureResponse implements Parcelable { if (mWindowBounds != null) flg |= 0x4; if (mBoundsInWindow != null) flg |= 0x8; if (mWindowTitle != null) flg |= 0x10; + if (mPackageName != null) flg |= 0x20; dest.writeByte(flg); dest.writeString(mDescription); if (mConnection != null) dest.writeStrongInterface(mConnection); if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags); if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags); if (mWindowTitle != null) dest.writeString(mWindowTitle); + if (mPackageName != null) dest.writeString(mPackageName); dest.writeStringList(mMessages); } @@ -213,6 +230,7 @@ public class ScrollCaptureResponse implements Parcelable { Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); String windowTitle = (flg & 0x10) == 0 ? null : in.readString(); + String packageName = (flg & 0x20) == 0 ? null : in.readString(); ArrayList messages = new ArrayList<>(); in.readStringList(messages); @@ -223,6 +241,7 @@ public class ScrollCaptureResponse implements Parcelable { this.mWindowBounds = windowBounds; this.mBoundsInWindow = boundsInWindow; this.mWindowTitle = windowTitle; + this.mPackageName = packageName; this.mMessages = messages; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mMessages); @@ -256,6 +275,7 @@ public class ScrollCaptureResponse implements Parcelable { private @Nullable Rect mWindowBounds; private @Nullable Rect mBoundsInWindow; private @Nullable String mWindowTitle; + private @Nullable String mPackageName; private @NonNull ArrayList mMessages; private long mBuilderFieldsSet = 0L; @@ -318,13 +338,24 @@ public class ScrollCaptureResponse implements Parcelable { return this; } + /** + * The package name of the process the window is owned by. + */ + @DataClass.Generated.Member + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; + mPackageName = value; + return this; + } + /** * Carries additional logging and debugging information when enabled. */ @DataClass.Generated.Member public @NonNull Builder setMessages(@NonNull ArrayList value) { checkNotUsed(); - mBuilderFieldsSet |= 0x20; + mBuilderFieldsSet |= 0x40; mMessages = value; return this; } @@ -340,7 +371,7 @@ public class ScrollCaptureResponse implements Parcelable { /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull ScrollCaptureResponse build() { checkNotUsed(); - mBuilderFieldsSet |= 0x40; // Mark builder used + mBuilderFieldsSet |= 0x80; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mDescription = ""; @@ -358,6 +389,9 @@ public class ScrollCaptureResponse implements Parcelable { mWindowTitle = null; } if ((mBuilderFieldsSet & 0x20) == 0) { + mPackageName = null; + } + if ((mBuilderFieldsSet & 0x40) == 0) { mMessages = new ArrayList<>(); } ScrollCaptureResponse o = new ScrollCaptureResponse( @@ -366,12 +400,13 @@ public class ScrollCaptureResponse implements Parcelable { mWindowBounds, mBoundsInWindow, mWindowTitle, + mPackageName, mMessages); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x40) != 0) { + if ((mBuilderFieldsSet & 0x80) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -379,10 +414,10 @@ public class ScrollCaptureResponse implements Parcelable { } @DataClass.Generated( - time = 1614833185795L, - codegenVersion = "1.0.22", + time = 1628630366187L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java", - inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)") + inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3550a31f9038..985f20b5ca30 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9493,6 +9493,7 @@ public final class ViewRootImpl implements ViewParent, ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder(); response.setWindowTitle(getTitle().toString()); + response.setPackageName(mContext.getPackageName()); StringWriter writer = new StringWriter(); IndentingPrintWriter pw = new IndentingPrintWriter(writer); -- cgit v1.2.3 From 71bc80de7b5342215d786a5f4307cb26bc2f2806 Mon Sep 17 00:00:00 2001 From: Mark Renouf Date: Wed, 11 Aug 2021 19:06:23 +0000 Subject: requestScrollCapture: provide package name on exception Bug: 195013652 Test: manual Change-Id: Icdd5a0ac1af599c31cda343231af3cb313d201c1 --- services/core/java/com/android/server/wm/WindowManagerService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1ec9187d7a76..058cccb9836a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -7061,6 +7061,7 @@ public class WindowManagerService extends IWindowManager.Stub "requestScrollCapture: caught exception dispatching to window." + "token=%s", targetWindow.mClient.asBinder()); responseBuilder.setWindowTitle(targetWindow.getName()); + responseBuilder.setPackageName(targetWindow.getOwningPackage()); responseBuilder.setDescription(String.format("caught exception: %s", e)); listener.onScrollCaptureResponse(responseBuilder.build()); } -- cgit v1.2.3 From 09e4403d209e287cbdeb0a0d6e3249920a935164 Mon Sep 17 00:00:00 2001 From: Hai Zhang Date: Wed, 11 Aug 2021 20:38:34 +0000 Subject: Read permission state before scanning packages. The readLegacyPermissionStateTEMP() call was originally introduced right before the old updateAllPermissions() call in ag/12329540 probably because we wanted to make sure packages have been properly scanned and reconciled before permission code tries to read permission state in combination with the existing package settings. But in the case of OTA removing a system app, it is possible for the permission state to change during the package reconciliation so the permission state should indeed be read earlier. It should actually be fine to read the permission state earlier as well because in Settings.RuntimePermissionPersistence.readStateForUserSyncLPr() (part of Settings.readLPw()), we are also consulting the not-yet-reconciled package settings and that has been fine for a long time. Fixes: 196177583 Test: presubmit Change-Id: I01dae1da934f927ca87b62773b6673a9b3aefb8a --- services/core/java/com/android/server/pm/PackageManagerService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 904a1f0d6f4f..4e3dfe4625d2 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7220,6 +7220,7 @@ public class PackageManagerService extends IPackageManager.Stub t.traceEnd(); mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions); + mPermissionManager.readLegacyPermissionStateTEMP(); if (!mOnlyCore && mFirstBoot) { requestCopyPreoptedFiles(mInjector); @@ -7635,7 +7636,6 @@ public class PackageManagerService extends IPackageManager.Stub + ((SystemClock.uptimeMillis()-startTime)/1000f) + " seconds"); - mPermissionManager.readLegacyPermissionStateTEMP(); // If the build fingerprint has changed since the last time we booted, // we need to re-grant app permission to catch any new ones that // appear. This is really a hack, and means that apps can in some -- cgit v1.2.3 From f6dbea381aafedc6b78008fc51b1c70068a8b2bf Mon Sep 17 00:00:00 2001 From: Nate Jiang Date: Tue, 10 Aug 2021 12:22:22 -0700 Subject: Remove modules-utils-build_system from filegroup Remove modules-utils-build_system from static lib of service-wifi.jar, because it is already in static lib of framework-wifi.jar Cherry-picked from https://android-review.googlesource.com/1792931 Bug: 195965491 Test: build apex Merged-In: Ica496e67dd8b7c83aa93a512d06ad1a05d1c9c8d Change-Id: Ib2fc438eb01787a0910c29601b579c17d06130de --- services/net/Android.bp | 1 - 1 file changed, 1 deletion(-) diff --git a/services/net/Android.bp b/services/net/Android.bp index a822257e1a74..53ce6b264651 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -52,7 +52,6 @@ java_library { // classes generated by netd_aidl_interfaces-platform-java above. "netd_aidl_interface-V3-java", "networkstack-client", - "modules-utils-build_system", ], apex_available: [ "com.android.wifi", -- cgit v1.2.3 From 00f00ace62b2a6684bf0022d606f34a6f34278c7 Mon Sep 17 00:00:00 2001 From: synch Date: Thu, 12 Aug 2021 14:43:22 +0800 Subject: fix a bug that extra is not actually set to the query. Bug: 196294479 Test: manually Change-Id: I765ac6b996880c607aee4ed5e627c1c33bde2b6d --- core/java/android/app/search/Query.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java index c64e10723ca6..f073b4e50555 100644 --- a/core/java/android/app/search/Query.java +++ b/core/java/android/app/search/Query.java @@ -70,7 +70,7 @@ public final class Query implements Parcelable { @NonNull Bundle extras) { mInput = input; mTimestampMillis = timestampMillis; - mExtras = extras == null ? extras : new Bundle(); + mExtras = extras != null ? extras : new Bundle(); } /** -- cgit v1.2.3 From 5167131c79dfec789d652c556703022c4010b92b Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Fri, 6 Aug 2021 22:43:00 +0800 Subject: Improve shouldRestoreImeVisibility perf shouldRestoreImeVisibility(token) used to check if the task window token should restore IME visibliity by checking the tasksnapshot. Previously by default it calls mAtmService.getTaskSnapshot() to load tasksnapshot from the disk if the does not exist, which caused additional bitmap allocation to see if the tasksnapshot is not active. Since we only check the IME surface from the tasksnapshot when the task is still running. Also, after the next booted, tapping the tasksnapshot from the overview will be refreshed with splashscreen that we don't trying to restore IME visiblity for that case. Use WMS#getTaskSnapshot with specifying restoreFromDisk parameter as false to get the active tasksnapshot from memory. Fix: 195347355 Test: Enable systrace on device from developer option, launch an App with showing IME, swiping up to the overview and then tapping the original task again, expect no additional EGLContext created in the systrace. Change-Id: I0abb5d6d77fdb200c737e7582923ced9ee98ad05 --- services/core/java/com/android/server/wm/WindowManagerService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1ec9187d7a76..b42ba702916d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -8606,8 +8606,9 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTargetWindowTask == null) { return false; } - final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId, - false /* isLowResolution */); + final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, + imeTargetWindowTask.mUserId, false /* isLowResolution */, + false /* restoreFromDisk */); return snapshot != null && snapshot.hasImeSurface(); } } -- cgit v1.2.3 From 795147073b834d0024bc1a8bc84790e124f42f3c Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Mon, 2 Aug 2021 09:36:45 -0400 Subject: KeyguardViewMediatorTest - Flaky test fix Keep new test changes isolated to the new test to avoid disruption to the existing tests Fixes: 196295234 Test: atest KeyguardViewMediatorTest Change-Id: Ia7706ce8b3923c1bf4fccd3f15edf97c60893472 (cherry picked from commit d4f5bde12fa5d1eccd6d0042b186cda56c621e9b) --- .../src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index ad0878031679..31d70f5c811f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -67,7 +67,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) +@TestableLooper.RunWithLooper @SmallTest public class KeyguardViewMediatorTest extends SysuiTestCase { private KeyguardViewMediator mViewMediator; @@ -126,7 +126,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, () -> mNotificationShadeDepthController); mViewMediator.start(); - mViewMediator.onSystemReady(); } @Test @@ -165,8 +164,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() { // When showing and provisioned + mViewMediator.onSystemReady(); when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true); mViewMediator.setShowingLocked(true); -- cgit v1.2.3 From 9eb0f18a8dba03b6176a719c65d018a83f35d452 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Thu, 15 Jul 2021 11:49:23 -0400 Subject: Move screenshot UI to avoid covering nav bar Bug: 193552343 Fix: 193552343 Test: manual; visual inspection Change-Id: Idbf632c268cb753079d432cbe197f06e9abaf11d --- .../res/layout/global_screenshot_static.xml | 1 - packages/SystemUI/res/values/dimens.xml | 3 +-- .../systemui/screenshot/ScreenshotController.java | 9 ++++---- .../systemui/screenshot/ScreenshotView.java | 25 +++++++++++++++------- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml index e4a96947aa6a..6a9254cad8f4 100644 --- a/packages/SystemUI/res/layout/global_screenshot_static.xml +++ b/packages/SystemUI/res/layout/global_screenshot_static.xml @@ -36,7 +36,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal" - android:layout_marginBottom="@dimen/screenshot_action_container_offset_y" android:paddingEnd="@dimen/screenshot_action_container_padding_right" android:paddingVertical="@dimen/screenshot_action_container_padding_vertical" android:elevation="1dp" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6ad9ab9a26ec..2dc5560380af 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -331,11 +331,10 @@ 80dp 242dp 4dp - 24dp + 8dp 16dp 48dp 8dp - 16dp 18dp 4dp 8dp diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 16872b08b9c8..37f0e0425035 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -559,8 +559,8 @@ public class ScreenshotController { mScreenshotView.reset(); } - mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics() - .getWindowInsets().getDisplayCutout()); + mScreenshotView.updateOrientation( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); mScreenBitmap = screenshot; @@ -594,9 +594,8 @@ public class ScreenshotController { // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed(this::requestScrollCapture, 150); - mScreenshotView.updateDisplayCutoutMargins( - mWindowManager.getCurrentWindowMetrics().getWindowInsets() - .getDisplayCutout()); + mScreenshotView.updateInsets( + mWindowManager.getCurrentWindowMetrics().getWindowInsets()); // screenshot animation calculations won't be valid anymore, so just end if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.end(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index e9e62f26a10e..827e6a674c34 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -414,21 +414,30 @@ public class ScreenshotView extends FrameLayout implements mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets)); } - void updateDisplayCutoutMargins(DisplayCutout cutout) { + void updateInsets(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); FrameLayout.LayoutParams p = (FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams(); + DisplayCutout cutout = insets.getDisplayCutout(); + Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()); if (cutout == null) { - p.setMargins(0, 0, 0, 0); + p.setMargins(0, 0, 0, navBarInsets.bottom); } else { Insets waterfall = cutout.getWaterfallInsets(); if (mOrientationPortrait) { - p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top), - waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom)); + p.setMargins( + waterfall.left, + Math.max(cutout.getSafeInsetTop(), waterfall.top), + waterfall.right, + Math.max(cutout.getSafeInsetBottom(), + Math.max(navBarInsets.bottom, waterfall.bottom))); } else { - p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top, - Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom); + p.setMargins( + Math.max(cutout.getSafeInsetLeft(), waterfall.left), + waterfall.top, + Math.max(cutout.getSafeInsetRight(), waterfall.right), + Math.max(navBarInsets.bottom, waterfall.bottom)); } } mStaticLeftMargin = p.leftMargin; @@ -436,10 +445,10 @@ public class ScreenshotView extends FrameLayout implements mScreenshotStatic.requestLayout(); } - void updateOrientation(DisplayCutout cutout) { + void updateOrientation(WindowInsets insets) { int orientation = mContext.getResources().getConfiguration().orientation; mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT); - updateDisplayCutoutMargins(cutout); + updateInsets(insets); int screenshotFixedSize = mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale); ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams(); -- cgit v1.2.3 From dc059a5f7349717def72df8c0417a064b6860762 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 8 Jul 2021 12:40:58 -0400 Subject: Add chevron to Screen cast tile Adds the chevron when it's not casting and we expect to open a dialog Test: manual Test: atest CastTileTest Fixes: 191154648 Change-Id: Ib3e70d3c8dbc797fd85a6781743270ed4abc7e63 Merged-In: Ib3e70d3c8dbc797fd85a6781743270ed4abc7e63 (cherry picked from commit 9dd955bd0cd08674a5f3a791ab6fee5085bf4afc) --- .../com/android/systemui/qs/tiles/CastTile.java | 17 +++-- .../android/systemui/qs/tiles/CastTileTest.java | 74 ++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) 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 4b13015361cc..04f089d31664 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -150,11 +150,7 @@ public class CastTile extends QSTileImpl { } List activeDevices = getActiveDevices(); - // We want to pop up the media route selection dialog if we either have no active devices - // (neither routes nor projection), or if we have an active route. In other cases, we assume - // that a projection is active. This is messy, but this tile never correctly handled the - // case where multiple devices were active :-/. - if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) { + if (willPopDetail()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { showDetail(true); }); @@ -163,6 +159,15 @@ public class CastTile extends QSTileImpl { } } + // We want to pop up the media route selection dialog if we either have no active devices + // (neither routes nor projection), or if we have an active route. In other cases, we assume + // that a projection is active. This is messy, but this tile never correctly handled the + // case where multiple devices were active :-/. + private boolean willPopDetail() { + List activeDevices = getActiveDevices(); + return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo); + } + private List getActiveDevices() { ArrayList activeDevices = new ArrayList<>(); for (CastDevice device : mController.getCastDevices()) { @@ -234,10 +239,12 @@ public class CastTile extends QSTileImpl { state.contentDescription = state.contentDescription + "," + mContext.getString(R.string.accessibility_quick_settings_open_details); state.expandedAccessibilityClassName = Button.class.getName(); + state.forceExpandIcon = willPopDetail(); } else { state.state = Tile.STATE_UNAVAILABLE; String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi); state.secondaryLabel = noWifi; + state.forceExpandIcon = false; } state.stateDescription = state.stateDescription + ", " + state.secondaryLabel; mDetailAdapter.updateItems(devices); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index d44a52607707..e939411e4a2a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; @@ -327,4 +328,77 @@ public class CastTileTest extends SysuiTestCase { assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state); assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name)); } + + @Test + public void testExpandView_wifiNotConnected() { + mCastTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertFalse(mCastTile.getState().forceExpandIcon); + } + + @Test + public void testExpandView_wifiEnabledNotCasting() { + enableWifiAndProcessMessages(); + + assertTrue(mCastTile.getState().forceExpandIcon); + } + + @Test + public void testExpandView_casting_projection() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastController.CastDevice.STATE_CONNECTED; + List devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + assertFalse(mCastTile.getState().forceExpandIcon); + } + + @Test + public void testExpandView_connecting_projection() { + CastController.CastDevice connecting = new CastController.CastDevice(); + connecting.state = CastDevice.STATE_CONNECTING; + connecting.name = "Test Casting Device"; + + List devices = new ArrayList<>(); + devices.add(connecting); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + assertFalse(mCastTile.getState().forceExpandIcon); + } + + @Test + public void testExpandView_casting_mediaRoute() { + CastController.CastDevice device = new CastController.CastDevice(); + device.state = CastDevice.STATE_CONNECTED; + device.tag = mock(MediaRouter.RouteInfo.class); + List devices = new ArrayList<>(); + devices.add(device); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + assertTrue(mCastTile.getState().forceExpandIcon); + } + + @Test + public void testExpandView_connecting_mediaRoute() { + CastController.CastDevice connecting = new CastController.CastDevice(); + connecting.state = CastDevice.STATE_CONNECTING; + connecting.tag = mock(RouteInfo.class); + connecting.name = "Test Casting Device"; + + List devices = new ArrayList<>(); + devices.add(connecting); + when(mController.getCastDevices()).thenReturn(devices); + + enableWifiAndProcessMessages(); + + assertTrue(mCastTile.getState().forceExpandIcon); + } } -- cgit v1.2.3 From c14dd63160ee9fb303e3cd92ff63825a1303362a Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Wed, 4 Aug 2021 11:50:07 -0400 Subject: Accessibility for privacy status animations This CL adds contentDescriptions to the privacy event chip and the ongoing privacy dot views. This is a partial fix which allows talkback for the ongoing privacy chip, and allows talkback scrolling to the privacy dot. However, due to the fact that the dot lives in a different window, the privacy dot shows up in the wrong part of the accesibility tree and still needs to be virtually placed in the status bar. Bug: 187197696 Test: manual Change-Id: I6210e18c75a1aab61cbcf619a9a31c459fffe544 --- .../statusbar/events/PrivacyDotViewController.kt | 17 +++++++-- .../systemui/statusbar/events/StatusEvent.kt | 12 +++++- .../events/SystemEventChipAnimationController.kt | 3 +- .../statusbar/events/SystemEventCoordinator.kt | 11 +++++- .../events/SystemStatusAnimationScheduler.kt | 44 +++++++++++----------- 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt index 29cfb07a14f9..1037e576f263 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -497,7 +497,12 @@ class PrivacyDotViewController @Inject constructor( } if (state.designatedCorner != currentViewState.designatedCorner) { + currentViewState.designatedCorner?.contentDescription = null + state.designatedCorner?.contentDescription = state.contentDescription + updateDesignatedCorner(state.designatedCorner, state.shouldShowDot()) + } else if (state.contentDescription != currentViewState.contentDescription) { + state.designatedCorner?.contentDescription = state.contentDescription } val shouldShow = state.shouldShowDot() @@ -514,9 +519,13 @@ class PrivacyDotViewController @Inject constructor( private val systemStatusAnimationCallback: SystemStatusAnimationCallback = object : SystemStatusAnimationCallback { - override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { + override fun onSystemStatusAnimationTransitionToPersistentDot( + contentDescr: String? + ): Animator? { synchronized(lock) { - nextViewState = nextViewState.copy(systemPrivacyEventIsActive = true) + nextViewState = nextViewState.copy( + systemPrivacyEventIsActive = true, + contentDescription = contentDescr) } return null @@ -620,7 +629,9 @@ private data class ViewState( val rotation: Int = 0, val height: Int = 0, val cornerIndex: Int = -1, - val designatedCorner: View? = null + val designatedCorner: View? = null, + + val contentDescription: String? = null ) { fun shouldShowDot(): Boolean { return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt index 539020d52db5..d4d84c138b20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -34,6 +34,7 @@ interface StatusEvent { // Whether or not to show an animation for this event val showAnimation: Boolean val viewCreator: (context: Context) -> View + var contentDescription: String? // Update this event with values from another event. fun updateFromEvent(other: StatusEvent?) { @@ -50,6 +51,7 @@ class BatteryEvent : StatusEvent { override val priority = 50 override val forceVisible = false override val showAnimation = true + override var contentDescription: String? = "" override val viewCreator: (context: Context) -> View = { context -> val iv = ImageView(context) @@ -62,7 +64,9 @@ class BatteryEvent : StatusEvent { return javaClass.simpleName } } + class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { + override var contentDescription: String? = null override val priority = 100 override val forceVisible = true var privacyItems: List = listOf() @@ -72,6 +76,7 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { val v = LayoutInflater.from(context) .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip v.privacyList = privacyItems + v.contentDescription = contentDescription privacyChip = v v } @@ -81,7 +86,9 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { - return other is PrivacyEvent && other.privacyItems != privacyItems + return other is PrivacyEvent && + (other.privacyItems != privacyItems || + other.contentDescription != contentDescription) } override fun updateFromEvent(other: StatusEvent?) { @@ -90,6 +97,9 @@ class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEvent { } privacyItems = other.privacyItems + contentDescription = other.contentDescription + + privacyChip?.contentDescription = other.contentDescription privacyChip?.privacyList = other.privacyItems } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt index b861c1db9b8b..7291b5a8be3b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -34,8 +34,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowView import javax.inject.Inject /** - * //TODO: this _probably_ doesn't control a window anymore - * Controls the window for system event animations. + * Controls the view for system event animations. */ class SystemEventChipAnimationController @Inject constructor( private val context: Context, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt index ba50659f5567..04f7492e8562 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.events +import android.content.Context import android.provider.DeviceConfig import android.provider.DeviceConfig.NAMESPACE_PRIVACY +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.privacy.PrivacyChipBuilder import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.statusbar.policy.BatteryController @@ -33,7 +36,8 @@ import javax.inject.Inject class SystemEventCoordinator @Inject constructor( private val systemClock: SystemClock, private val batteryController: BatteryController, - private val privacyController: PrivacyItemController + private val privacyController: PrivacyItemController, + private val context: Context ) { private lateinit var scheduler: SystemStatusAnimationScheduler @@ -66,6 +70,11 @@ class SystemEventCoordinator @Inject constructor( fun notifyPrivacyItemsChanged(showAnimation: Boolean = true) { val event = PrivacyEvent(showAnimation) event.privacyItems = privacyStateListener.currentPrivacyItems + event.contentDescription = { + val items = PrivacyChipBuilder(context, event.privacyItems).joinTypes() + context.getString( + R.string.ongoing_privacy_chip_content_multiple_apps, items) + }() scheduler.onStatusEvent(event) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt index f30010cf4d1c..f1610d866f34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -100,17 +100,20 @@ class SystemStatusAnimationScheduler @Inject constructor( // Don't deal with threading for now (no need let's be honest) Assert.isMainThread() - if (event.priority > scheduledEvent?.priority ?: -1 || - scheduledEvent?.shouldUpdateFromEvent(event) == true) { + if ((event.priority > scheduledEvent?.priority ?: -1) && + animationState != ANIMATING_OUT && + (animationState != SHOWING_PERSISTENT_DOT && event.forceVisible)) { + // events can only be scheduled if a higher priority or no other event is in progress if (DEBUG) { Log.d(TAG, "scheduling event $event") } - if (event.showAnimation) { - scheduleEvent(event) - } else if (event.forceVisible) { - hasPersistentDot = true - notifyTransitionToPersistentDot() + + scheduleEvent(event) + } else if (scheduledEvent?.shouldUpdateFromEvent(event) == true) { + if (DEBUG) { + Log.d(TAG, "updating current event from: $event") } + scheduledEvent?.updateFromEvent(event) } else { if (DEBUG) { Log.d(TAG, "ignoring event $event") @@ -142,22 +145,16 @@ class SystemStatusAnimationScheduler @Inject constructor( * Clear the scheduled event (if any) and schedule a new one */ private fun scheduleEvent(event: StatusEvent) { - if (animationState == ANIMATING_OUT || - (animationState == SHOWING_PERSISTENT_DOT && event.forceVisible)) { - // do not schedule an event or change the current one - return - } + scheduledEvent = event - // If we are showing the chip, possibly update the current event, rather than replacing - if (scheduledEvent?.shouldUpdateFromEvent(event) == true) { - scheduledEvent?.updateFromEvent(event) - return - } else { - scheduledEvent = event + if (event.forceVisible) { + hasPersistentDot = true } - if (scheduledEvent!!.forceVisible) { - hasPersistentDot = true + // If animations are turned off, we'll transition directly to the dot + if (!event.showAnimation && event.forceVisible) { + notifyTransitionToPersistentDot() + return } // Schedule the animation to start after a debounce period @@ -218,7 +215,7 @@ class SystemStatusAnimationScheduler @Inject constructor( private fun notifyTransitionToPersistentDot(): Animator? { val anims: List = listeners.mapNotNull { - it.onSystemStatusAnimationTransitionToPersistentDot() + it.onSystemStatusAnimationTransitionToPersistentDot(scheduledEvent?.contentDescription) } if (anims.isNotEmpty()) { val aSet = AnimatorSet() @@ -346,7 +343,10 @@ interface SystemStatusAnimationCallback { @JvmDefault fun onSystemChromeAnimationEnd() {} // Best method name, change my mind - @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null } + @JvmDefault + fun onSystemStatusAnimationTransitionToPersistentDot(contentDescription: String?): Animator? { + return null + } @JvmDefault fun onHidePersistentDot(): Animator? { return null } } -- cgit v1.2.3 From 833d535101ec58b3d6721946e4a97d62f8c5d56d Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Tue, 13 Jul 2021 11:00:36 -0400 Subject: Replace DateView with VariableDateView (1/2) VariableDateView adjusts its pattern (long, short, or empty) depending on how much space it has to show. This is done from a controller. Keep DateView for now and replace in next CL for compatibility Also, move `updateAnimators` out of `onMeasure`, as it creates new objects. Test: manual Test: atest VariableDateViewControllerTest Fixes: 192574271 Change-Id: I547a2f035cfc7f9eaa9bdea37846a3ba4fbe1475 Merged-In: I547a2f035cfc7f9eaa9bdea37846a3ba4fbe1475 --- .../res-keyguard/values/donottranslate.xml | 3 + .../SystemUI/res/layout/quick_qs_status_icons.xml | 37 +++- .../quick_status_bar_header_date_privacy.xml | 6 +- packages/SystemUI/res/values/attrs.xml | 4 + .../android/systemui/qs/QuickStatusBarHeader.java | 22 +- .../qs/QuickStatusBarHeaderController.java | 16 +- .../systemui/statusbar/policy/VariableDateView.kt | 79 ++++++++ .../statusbar/policy/VariableDateViewController.kt | 221 +++++++++++++++++++++ .../qs/QuickStatusBarHeaderControllerTest.kt | 15 +- .../policy/VariableDateViewControllerTest.kt | 176 ++++++++++++++++ 10 files changed, 562 insertions(+), 17 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml index a4d0ff7269a2..1934457b4bc6 100644 --- a/packages/SystemUI/res-keyguard/values/donottranslate.xml +++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml @@ -21,6 +21,9 @@ EEEMMMd + + MMMd + hm diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml index 5b9ca1b26158..966f9929b37c 100644 --- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml +++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml @@ -26,17 +26,38 @@ android:focusable="true" android:theme="@style/Theme.SystemUI.QuickSettings.Header"> - + > + + + + + - + systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm" + systemui:shortDatePattern="@string/abbrev_month_day_no_year" + /> + + + + diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 77906abce625..19d5fa0ec74f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconMa import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.VariableDateView; import java.util.List; @@ -62,11 +63,14 @@ public class QuickStatusBarHeader extends FrameLayout { protected QuickQSPanel mHeaderQsPanel; private View mDatePrivacyView; private View mDateView; + // DateView next to clock. Visible on QQS + private VariableDateView mClockDateView; private View mSecurityHeaderView; private View mClockIconsView; private View mContainer; private View mQSCarriers; + private ViewGroup mClockContainer; private Clock mClockView; private Space mDatePrivacySeparator; private View mClockIconsSeparator; @@ -86,7 +90,6 @@ public class QuickStatusBarHeader extends FrameLayout { private int mWaterfallTopInset; private int mCutOutPaddingLeft; private int mCutOutPaddingRight; - private float mViewAlpha = 1.0f; private float mKeyguardExpansionFraction; private int mTextColorPrimary = Color.TRANSPARENT; private int mTopViewMeasureHeight; @@ -123,12 +126,14 @@ public class QuickStatusBarHeader extends FrameLayout { mIconContainer = findViewById(R.id.statusIcons); mPrivacyChip = findViewById(R.id.privacy_chip); mDateView = findViewById(R.id.date); + mClockDateView = findViewById(R.id.date_clock); mSecurityHeaderView = findViewById(R.id.header_text_container); mClockIconsSeparator = findViewById(R.id.separator); mRightLayout = findViewById(R.id.rightLayout); mDateContainer = findViewById(R.id.date_container); mPrivacyContainer = findViewById(R.id.privacy_container); + mClockContainer = findViewById(R.id.clock_container); mClockView = findViewById(R.id.clock); mDatePrivacySeparator = findViewById(R.id.space); // Tint for the battery icons are handled in setupHost() @@ -177,7 +182,7 @@ public class QuickStatusBarHeader extends FrameLayout { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) { mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight(); - updateAnimators(); + post(this::updateAnimators); } } @@ -280,7 +285,8 @@ public class QuickStatusBarHeader extends FrameLayout { TouchAnimator.Builder builder = new TouchAnimator.Builder() .addFloat(mSecurityHeaderView, "alpha", 0, 1) // These views appear on expanding down - .addFloat(mClockView, "alpha", 0, 1) + .addFloat(mDateView, "alpha", 0, 0, 1) + .addFloat(mClockDateView, "alpha", 1, 0, 0) .addFloat(mQSCarriers, "alpha", 0, 1) .setListener(new TouchAnimator.ListenerAdapter() { @Override @@ -289,10 +295,14 @@ public class QuickStatusBarHeader extends FrameLayout { if (!mIsSingleCarrier) { mIconContainer.addIgnoredSlots(mRssiIgnoredSlots); } + // Make it gone so there's enough room for carrier names + mClockDateView.setVisibility(View.GONE); } @Override public void onAnimationStarted() { + mClockDateView.setVisibility(View.VISIBLE); + mClockDateView.setFreezeSwitching(true); setSeparatorVisibility(false); if (!mIsSingleCarrier) { mIconContainer.addIgnoredSlots(mRssiIgnoredSlots); @@ -302,6 +312,7 @@ public class QuickStatusBarHeader extends FrameLayout { @Override public void onAnimationAtStart() { super.onAnimationAtStart(); + mClockDateView.setFreezeSwitching(false); setSeparatorVisibility(mShowClockIconsSeparator); // In QQS we never ignore RSSI. mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots); @@ -434,10 +445,11 @@ public class QuickStatusBarHeader extends FrameLayout { mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE); mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE); - LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams(); + LinearLayout.LayoutParams lp = + (LinearLayout.LayoutParams) mClockContainer.getLayoutParams(); lp.width = visible ? 0 : WRAP_CONTENT; lp.weight = visible ? 1f : 0f; - mClockView.setLayoutParams(lp); + mClockContainer.setLayoutParams(lp); lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams(); lp.width = visible ? 0 : WRAP_CONTENT; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java index b8b7f42455bb..0104149de56a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusIconContainer; import com.android.systemui.statusbar.policy.Clock; +import com.android.systemui.statusbar.policy.VariableDateViewController; import com.android.systemui.util.ViewController; import java.util.List; @@ -71,6 +72,9 @@ class QuickStatusBarHeaderController extends ViewController(view) { + + private var dateFormat: DateFormat? = null + private var datePattern = view.longerPattern + set(value) { + if (field == value) return + field = value + dateFormat = null + if (isAttachedToWindow) { + post(::updateClock) + } + } + private var lastWidth = Integer.MAX_VALUE + private var lastText = "" + private var currentTime = Date() + + // View class easy accessors + private val longerPattern: String + get() = mView.longerPattern + private val shorterPattern: String + get() = mView.shorterPattern + private fun post(block: () -> Unit) = mView.handler?.post(block) + + private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // If the handler is null, it means we received a broadcast while the view has not + // finished being attached or in the process of being detached. + // In that case, do not post anything. + val handler = mView.handler ?: return + val action = intent.action + if ( + Intent.ACTION_TIME_TICK == action || + Intent.ACTION_TIME_CHANGED == action || + Intent.ACTION_TIMEZONE_CHANGED == action || + Intent.ACTION_LOCALE_CHANGED == action + ) { + if ( + Intent.ACTION_LOCALE_CHANGED == action || + Intent.ACTION_TIMEZONE_CHANGED == action + ) { + // need to get a fresh date format + handler.post { dateFormat = null } + } + handler.post(::updateClock) + } + } + } + + private val onMeasureListener = object : VariableDateView.OnMeasureListener { + override fun onMeasureAction(availableWidth: Int) { + if (availableWidth != lastWidth) { + // maybeChangeFormat will post if the pattern needs to change. + maybeChangeFormat(availableWidth) + lastWidth = availableWidth + } + } + } + + override fun onViewAttached() { + val filter = IntentFilter().apply { + addAction(Intent.ACTION_TIME_TICK) + addAction(Intent.ACTION_TIME_CHANGED) + addAction(Intent.ACTION_TIMEZONE_CHANGED) + addAction(Intent.ACTION_LOCALE_CHANGED) + } + + broadcastDispatcher.registerReceiver(intentReceiver, filter, + HandlerExecutor(timeTickHandler), UserHandle.SYSTEM) + + post(::updateClock) + mView.onAttach(onMeasureListener) + } + + override fun onViewDetached() { + dateFormat = null + mView.onAttach(null) + broadcastDispatcher.unregisterReceiver(intentReceiver) + } + + private fun updateClock() { + if (dateFormat == null) { + dateFormat = getFormatFromPattern(datePattern) + } + + currentTime.time = systemClock.currentTimeMillis() + + val text = getTextForFormat(currentTime, dateFormat!!) + if (text != lastText) { + mView.setText(text) + lastText = text + } + } + + private fun maybeChangeFormat(availableWidth: Int) { + if (mView.freezeSwitching || + availableWidth > lastWidth && datePattern == longerPattern || + availableWidth < lastWidth && datePattern == "" + ) { + // Nothing to do + return + } + if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern") + // Start with longer pattern and see what fits + var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern)) + var length = mView.getDesiredWidthForText(text) + if (length <= availableWidth) { + changePattern(longerPattern) + return + } + + text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern)) + length = mView.getDesiredWidthForText(text) + if (length <= availableWidth) { + changePattern(shorterPattern) + return + } + + changePattern("") + } + + private fun changePattern(newPattern: String) { + if (newPattern.equals(datePattern)) return + if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern") + datePattern = newPattern + } + + class Factory @Inject constructor( + private val systemClock: SystemClock, + private val broadcastDispatcher: BroadcastDispatcher, + @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler + ) { + fun create(view: VariableDateView): VariableDateViewController { + return VariableDateViewController( + systemClock, + broadcastDispatcher, + handler, + view + ) + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 35360bd19393..8b7e20ed0e5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -36,6 +36,8 @@ import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.Clock +import com.android.systemui.statusbar.policy.VariableDateView +import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -87,8 +89,14 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var privacyDialogController: PrivacyDialogController @Mock + private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory + @Mock + private lateinit var variableDateViewController: VariableDateViewController + @Mock private lateinit var clock: Clock @Mock + private lateinit var variableDateView: VariableDateView + @Mock private lateinit var mockView: View @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var context: Context @@ -109,6 +117,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { stubViews() `when`(iconContainer.context).thenReturn(context) `when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController) + `when`(variableDateViewControllerFactory.create(any())) + .thenReturn(variableDateViewController) `when`(view.resources).thenReturn(mContext.resources) `when`(view.isAttachedToWindow).thenReturn(true) `when`(view.context).thenReturn(context) @@ -133,7 +143,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { colorExtractor, privacyDialogController, qsExpansionPathInterpolator, - featureFlags + featureFlags, + variableDateViewControllerFactory ) } @@ -274,6 +285,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() { `when`(view.findViewById(R.id.statusIcons)).thenReturn(iconContainer) `when`(view.findViewById(R.id.privacy_chip)).thenReturn(privacyChip) `when`(view.findViewById(R.id.clock)).thenReturn(clock) + `when`(view.requireViewById(R.id.date)).thenReturn(variableDateView) + `when`(view.requireViewById(R.id.date_clock)).thenReturn(variableDateView) } private fun setPrivacyController(micCamera: Boolean, location: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt new file mode 100644 index 000000000000..871a48c503be --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.capture +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +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.Mockito.`when` +import org.mockito.Mockito.anyString +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.util.Date + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +@SmallTest +class VariableDateViewControllerTest : SysuiTestCase() { + + companion object { + private const val TIME_STAMP = 1_500_000_000_000 + private const val LONG_PATTERN = "EEEMMMd" + private const val SHORT_PATTERN = "MMMd" + private const val CHAR_WIDTH = 10f + } + + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var view: VariableDateView + @Captor + private lateinit var onMeasureListenerCaptor: ArgumentCaptor + + private var lastText: String? = null + + private lateinit var systemClock: FakeSystemClock + private lateinit var testableLooper: TestableLooper + private lateinit var testableHandler: Handler + private lateinit var controller: VariableDateViewController + + private lateinit var longText: String + private lateinit var shortText: String + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + testableHandler = Handler(testableLooper.looper) + + systemClock = FakeSystemClock() + systemClock.setCurrentTimeMillis(TIME_STAMP) + + `when`(view.longerPattern).thenReturn(LONG_PATTERN) + `when`(view.shorterPattern).thenReturn(SHORT_PATTERN) + `when`(view.handler).thenReturn(testableHandler) + + `when`(view.setText(anyString())).thenAnswer { + lastText = it.arguments[0] as? String + Unit + } + `when`(view.isAttachedToWindow).thenReturn(true) + + val date = Date(TIME_STAMP) + longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN)) + shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN)) + + // Assume some sizes for the text, the controller doesn't need to know if these sizes are + // the true ones + `when`(view.getDesiredWidthForText(any())).thenAnswer { + getTextLength(it.arguments[0] as CharSequence) + } + + controller = VariableDateViewController( + systemClock, + broadcastDispatcher, + testableHandler, + view + ) + + controller.init() + testableLooper.processAllMessages() + + verify(view).onAttach(capture(onMeasureListenerCaptor)) + } + + @Test + fun testViewStartsWithLongText() { + assertThat(lastText).isEqualTo(longText) + } + + @Test + fun testListenerNotNull() { + assertThat(onMeasureListenerCaptor.value).isNotNull() + } + + @Test + fun testLotsOfSpaceUseLongText() { + onMeasureListenerCaptor.value.onMeasureAction(10000) + + testableLooper.processAllMessages() + assertThat(lastText).isEqualTo(longText) + } + + @Test + fun testSmallSpaceUseEmpty() { + onMeasureListenerCaptor.value.onMeasureAction(1) + testableLooper.processAllMessages() + + assertThat(lastText).isEmpty() + } + + @Test + fun testSpaceInBetweenUseShortText() { + val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt() + + onMeasureListenerCaptor.value.onMeasureAction(average) + testableLooper.processAllMessages() + + assertThat(lastText).isEqualTo(shortText) + } + + @Test + fun testSwitchBackToLonger() { + onMeasureListenerCaptor.value.onMeasureAction(1) + testableLooper.processAllMessages() + + onMeasureListenerCaptor.value.onMeasureAction(10000) + testableLooper.processAllMessages() + + assertThat(lastText).isEqualTo(longText) + } + + @Test + fun testNoSwitchingWhenFrozen() { + `when`(view.freezeSwitching).thenReturn(true) + + val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt() + onMeasureListenerCaptor.value.onMeasureAction(average) + testableLooper.processAllMessages() + assertThat(lastText).isEqualTo(longText) + + onMeasureListenerCaptor.value.onMeasureAction(1) + testableLooper.processAllMessages() + assertThat(lastText).isEqualTo(longText) + } + + private fun getTextLength(text: CharSequence): Float { + return text.length * CHAR_WIDTH + } +} \ No newline at end of file -- cgit v1.2.3 From 00bb4c6cc1b6206b190a76915a19069c546cac29 Mon Sep 17 00:00:00 2001 From: Lyn Han Date: Wed, 4 Aug 2021 18:50:03 -0700 Subject: Fix shade flicker & touch absorption Intercept MOVE events for closed shade (with no HUNs) and call startExpandMotion when appropriate. ---------------------------------------------------- Fixes: 188249360 (shade flicker to full height on swipe open) This bug happened because ag/13733252 introduced a new behavior (reverted here) where PVC intercepts only if PanelView is hidden. This means: => PanelView initially shows onIntercept(down), so we do NOT intercept and onTouch(down) does NOT run. => PanelView is then hidden onIntercept(move), so we intercept and onTouch(move) runs. When onTouch(down) does NOT run and onTouch(move) runs, we are MISSING the following: => onTouch(down): mGestureWaitForTouchSlop = true for closed shade => onTouch(move) calls startExpandMotion => startExpandMotion: mInitialOffsetOnTouch = 0 => onTouch(move): newHeight = mInitialOffsetOnTouch = 0 Instead, newHeight = full shade height from previous shade open. This causes the shade to jump to full height on swipe start. ---------------------------------------------------- Bug: 178277858 (touch absorption / shade not opening on swipe) This bug happened because onIntercept(move) did not intercept and call startExpandMotion when we swipe open from closed shade, resulting in the shade staying closed while child views silently eat the event. ------------------------------------------------ Test: open/close shade with varying drag speeds and flings => shade consistently opens with no flicker to full height Test: swipe-to-dismiss hun, expand hun, swipe down for hun-to-shade => no regressions Change-Id: I26ada34ed173393ee08a96aefd37b1935702bb90 --- .../systemui/statusbar/phone/PanelViewController.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 323a1128d3bb..de0f31d3cf59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -1211,10 +1211,14 @@ public abstract class PanelViewController { case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; addMovement(event); - if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) { + final boolean openShadeWithoutHun = + mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown; + if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown + || openShadeWithoutHun) { float hAbs = Math.abs(h); float touchSlop = getTouchSlop(event); - if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) + if ((h < -touchSlop + || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop)) && hAbs > Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); @@ -1227,10 +1231,7 @@ public abstract class PanelViewController { mVelocityTracker.clear(); break; } - - // Finally, if none of the above cases applies, ensure that touches do not get handled - // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858) - return (mView.getVisibility() != View.VISIBLE); + return false; } @Override -- cgit v1.2.3 From ba405b787bde5336888dbac56ee4b8fdf117c005 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 29 Jul 2021 10:44:20 -0400 Subject: Fix support for USER_ALL in FakeSettings If a content observer was registered to a SettingsProxy using UserHandle.USER_ALL, testing that was expecting to pass would fail. This CL fixes that by matching every user to USER_ALL observers that match the Uri. Test: atest SystemUITests Fixes: 194934707 Change-Id: I41a0eb50a2cd1700b03b372bfcc199bebd9aea35 Merged-In: I41a0eb50a2cd1700b03b372bfcc199bebd9aea35 --- .../systemui/util/settings/FakeSettings.java | 21 +++++++++++++++++--- .../systemui/util/settings/FakeSettingsTest.java | 23 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java index 69764227040b..7bb26748a9d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java @@ -31,6 +31,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti private final Map mValues = new HashMap<>(); private final Map> mContentObservers = new HashMap<>(); + private final Map> mContentObserversAllUsers = new HashMap<>(); public static final Uri CONTENT_URI = Uri.parse("content://settings/fake"); @@ -55,9 +56,15 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti @Override public void registerContentObserverForUser(Uri uri, boolean notifyDescendents, ContentObserver settingsObserver, int userHandle) { - SettingsKey key = new SettingsKey(userHandle, uri.toString()); - mContentObservers.putIfAbsent(key, new ArrayList<>()); - List observers = mContentObservers.get(key); + List observers; + if (userHandle == UserHandle.USER_ALL) { + mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>()); + observers = mContentObserversAllUsers.get(uri.toString()); + } else { + SettingsKey key = new SettingsKey(userHandle, uri.toString()); + mContentObservers.putIfAbsent(key, new ArrayList<>()); + observers = mContentObservers.get(key); + } observers.add(settingsObserver); } @@ -67,6 +74,10 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti List observers = mContentObservers.get(key); observers.remove(settingsObserver); } + for (String key : mContentObserversAllUsers.keySet()) { + List observers = mContentObserversAllUsers.get(key); + observers.remove(settingsObserver); + } } @Override @@ -114,6 +125,10 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) { observer.dispatchChange(false, List.of(uri), userHandle); } + for (ContentObserver observer : + mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) { + observer.dispatchChange(false, List.of(uri), userHandle); + } return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java index 0d560f237a07..34cae58d30e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.database.ContentObserver; +import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; @@ -88,6 +89,16 @@ public class FakeSettingsTest extends SysuiTestCase { verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt()); } + @Test + public void testRegisterContentObserverAllUsers() { + mFakeSettings.registerContentObserverForUser( + mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL); + + mFakeSettings.putString("cat", "hat"); + + verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt()); + } + @Test public void testUnregisterContentObserver() { mFakeSettings.registerContentObserver("cat", mContentObserver); @@ -98,4 +109,16 @@ public class FakeSettingsTest extends SysuiTestCase { verify(mContentObserver, never()).dispatchChange( anyBoolean(), any(Collection.class), anyInt()); } + + @Test + public void testUnregisterContentObserverAllUsers() { + mFakeSettings.registerContentObserverForUser( + mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL); + mFakeSettings.unregisterContentObserver(mContentObserver); + + mFakeSettings.putString("cat", "hat"); + + verify(mContentObserver, never()).dispatchChange( + anyBoolean(), any(Collection.class), anyInt()); + } } -- cgit v1.2.3 From ee4c4195b692017955e6df8e0731afa6455f0843 Mon Sep 17 00:00:00 2001 From: Cassie Date: Thu, 22 Jul 2021 15:05:23 -0700 Subject: Do not display "Network Available" when WiFi disabled. Bug: 192655861 Test: manual test Change-Id: Ib8975af13a6f201f3f1d0a4a0bec845d810d5905 --- packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 7cb1421e3f0f..41a3fb0211a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -429,7 +429,7 @@ public class InternetTile extends QSTileImpl { state.icon = ResourceIcon.get(cb.mWifiSignalIconId); } } else if (cb.mNoDefaultNetwork) { - if (cb.mNoNetworksAvailable) { + if (cb.mNoNetworksAvailable || !cb.mEnabled) { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable); } else { @@ -489,7 +489,7 @@ public class InternetTile extends QSTileImpl { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.status_bar_airplane); } else if (cb.mNoDefaultNetwork) { - if (cb.mNoNetworksAvailable) { + if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) { state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable); state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable); } else { -- cgit v1.2.3 From a49d7cbbcc01f54c184ecbd91aab8d7ce5979efe Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Thu, 15 Jul 2021 11:13:40 -0400 Subject: Make screenshot dismiss to the side on timeout Makes the dismissal directions consistent between swipe-dismiss and passive timeout. Bug: 192281614 Fix: 192281614 Test: manual Change-Id: Ib56d60f89c3fea0b5c36e5b3bc539da295699388 --- .../systemui/screenshot/ScreenshotView.java | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index e9e62f26a10e..e660a2bf0c17 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -118,8 +118,8 @@ public class ScreenshotView extends FrameLayout implements private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234; private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400; private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100; - private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350; - private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183; + private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350; + private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350; private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f; private static final float ROUNDED_CORNER_RADIUS = .25f; @@ -978,7 +978,6 @@ public class ScreenshotView extends FrameLayout implements mScrollingScrim.setVisibility(View.GONE); mScrollablePreview.setVisibility(View.GONE); mScreenshotStatic.setTranslationX(0); - mScreenshotPreview.setTranslationY(0); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotPreview.setOnClickListener(null); @@ -994,9 +993,6 @@ public class ScreenshotView extends FrameLayout implements mSmartChips.clear(); mQuickShareChip = null; setAlpha(1); - mDismissButton.setTranslationY(0); - mActionsContainer.setTranslationY(0); - mActionsContainerBackground.setTranslationY(0); mScreenshotSelectorView.stop(); } @@ -1024,22 +1020,19 @@ public class ScreenshotView extends FrameLayout implements setAlpha(1 - animation.getAnimatedFraction()); }); - ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1); - yAnim.setInterpolator(mAccelerateInterpolator); - yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS); - float screenshotStartY = mScreenshotPreview.getTranslationY(); - float dismissStartY = mDismissButton.getTranslationY(); - yAnim.addUpdateListener(animation -> { - float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction()); - mScreenshotPreview.setTranslationY(screenshotStartY + yDelta); - mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta); - mDismissButton.setTranslationY(dismissStartY + yDelta); - mActionsContainer.setTranslationY(yDelta); - mActionsContainerBackground.setTranslationY(yDelta); + ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1); + xAnim.setInterpolator(mAccelerateInterpolator); + xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS); + float deltaX = mDirectionLTR + ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth()) + : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX()); + xAnim.addUpdateListener(animation -> { + float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction()); + mScreenshotStatic.setTranslationX(currXDelta); }); AnimatorSet animSet = new AnimatorSet(); - animSet.play(yAnim).with(alphaAnim); + animSet.play(xAnim).with(alphaAnim); return animSet; } -- cgit v1.2.3 From 66aa586b4d563d4be51bdbda11aa1c2b2e433042 Mon Sep 17 00:00:00 2001 From: Mark Renouf Date: Thu, 12 Aug 2021 20:07:37 +0000 Subject: Log additional UiEvents for long screenshots, include packageName Bug: 195013652 Test: atest ScrollCaptureControllerTest Change-Id: Ief6578b33bb3361980ebd73a803a87f21f985a47 --- .../android/systemui/screenshot/ScreenshotController.java | 2 +- .../com/android/systemui/screenshot/ScreenshotEvent.java | 8 +++++++- .../com/android/systemui/screenshot/ScreenshotView.java | 8 +++++--- .../systemui/screenshot/ScrollCaptureController.java | 14 +++++++++++++- .../systemui/screenshot/ScrollCaptureControllerTest.java | 4 +++- 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 16872b08b9c8..cab2168d44e4 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -660,7 +660,7 @@ public class ScreenshotController { + mLastScrollCaptureResponse.getWindowTitle() + "]"); final ScrollCaptureResponse response = mLastScrollCaptureResponse; - mScreenshotView.showScrollChip(/* onClick */ () -> { + mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> { DisplayMetrics displayMetrics = new DisplayMetrics(); getDefaultDisplay().getRealMetrics(displayMetrics); Bitmap newScreenshot = captureScreenshot( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java index 5cf018813133..2b7bcc04e43c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -71,7 +71,13 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "User has shared a long screenshot") SCREENSHOT_LONG_SCREENSHOT_SHARE(689), @UiEvent(doc = "User has sent a long screenshot to the editor") - SCREENSHOT_LONG_SCREENSHOT_EDIT(690); + SCREENSHOT_LONG_SCREENSHOT_EDIT(690), + @UiEvent(doc = "A long screenshot capture has started") + SCREENSHOT_LONG_SCREENSHOT_STARTED(880), + @UiEvent(doc = "The long screenshot capture failed") + SCREENSHOT_LONG_SCREENSHOT_FAILURE(881), + @UiEvent(doc = "The long screenshot capture completed successfully") + SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882); private final int mId; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java index e9e62f26a10e..e5e690bcb8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java @@ -242,19 +242,21 @@ public class ScreenshotView extends FrameLayout implements /** * Called to display the scroll action chip when support is detected. * + * @param packageName the owning package of the window to be captured * @param onClick the action to take when the chip is clicked. */ - public void showScrollChip(Runnable onClick) { + public void showScrollChip(String packageName, Runnable onClick) { if (DEBUG_SCROLL) { Log.d(TAG, "Showing Scroll option"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName); mScrollChip.setVisibility(VISIBLE); mScrollChip.setOnClickListener((v) -> { if (DEBUG_INPUT) { Log.d(TAG, "scroll chip tapped"); } - mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, + packageName); onClick.run(); }); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java index 6dc68746e3ec..ef7355a09fbf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java @@ -28,6 +28,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult; import com.android.systemui.screenshot.ScrollCaptureClient.Session; @@ -61,6 +62,7 @@ public class ScrollCaptureController { private final Context mContext; private final Executor mBgExecutor; private final ImageTileSet mImageTileSet; + private final UiEventLogger mEventLogger; private final ScrollCaptureClient mClient; private Completer mCaptureCompleter; @@ -69,6 +71,7 @@ public class ScrollCaptureController { private Session mSession; private ListenableFuture mTileFuture; private ListenableFuture mEndFuture; + private String mWindowOwner; static class LongScreenshot { private final ImageTileSet mImageTileSet; @@ -135,11 +138,12 @@ public class ScrollCaptureController { @Inject ScrollCaptureController(Context context, @Background Executor bgExecutor, - ScrollCaptureClient client, ImageTileSet imageTileSet) { + ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) { mContext = context; mBgExecutor = bgExecutor; mClient = client; mImageTileSet = imageTileSet; + mEventLogger = logger; } @VisibleForTesting @@ -157,6 +161,7 @@ public class ScrollCaptureController { ListenableFuture run(ScrollCaptureResponse response) { return CallbackToFutureAdapter.getFuture(completer -> { mCaptureCompleter = completer; + mWindowOwner = response.getPackageName(); mBgExecutor.execute(() -> { float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(), SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT); @@ -173,11 +178,13 @@ public class ScrollCaptureController { if (LogConfig.DEBUG_SCROLL) { Log.d(TAG, "got session " + mSession); } + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner); requestNextTile(0); } catch (InterruptedException | ExecutionException e) { // Failure to start, propagate to caller Log.e(TAG, "session start failed!"); mCaptureCompleter.setException(e); + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner); } } @@ -297,6 +304,11 @@ public class ScrollCaptureController { if (LogConfig.DEBUG_SCROLL) { Log.d(TAG, "finishCapture()"); } + if (mImageTileSet.getHeight() > 0) { + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner); + } else { + mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner); + } mEndFuture = mSession.end(); mEndFuture.addListener(() -> { if (LogConfig.DEBUG_SCROLL) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java index 10c878a92745..6f081c759df7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java @@ -34,6 +34,7 @@ import android.view.ScrollCaptureResponse; import androidx.test.filters.SmallTest; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.screenshot.ScrollCaptureClient.Session; @@ -274,7 +275,8 @@ public class ScrollCaptureControllerTest extends SysuiTestCase { when(client.start(/* response */ any(), /* maxPages */ anyFloat())) .thenReturn(immediateFuture(session)); return new ScrollCaptureController(context, context.getMainExecutor(), - client, new ImageTileSet(context.getMainThreadHandler())); + client, new ImageTileSet(context.getMainThreadHandler()), + new UiEventLoggerFake()); } } } -- cgit v1.2.3 From 7ce358c0058a5b958d2768ffbc06dbc6f02591de Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Tue, 3 Aug 2021 14:16:44 -0400 Subject: Collapse the CallStyle buttons to just an icon instead of truncating text. Fixes: 195019654 Test: post CallStyle notification without custom action; observe text Test: post CallStyle notification with custom action; observe buttons shrink to icon only. Test: repeat tests using various font and screen sizes. Change-Id: Icbd6189a1e03494e481e8672263570ae2657f946 --- .../widget/NotificationActionListLayout.java | 53 ++++++++++++++++------ .../res/drawable/btn_notification_emphasized.xml | 4 +- core/res/res/values/dimens.xml | 3 ++ core/res/res/values/symbols.xml | 1 + 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java index 0c2d2a9bcf41..3191e23a9883 100644 --- a/core/java/com/android/internal/widget/NotificationActionListLayout.java +++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java @@ -53,6 +53,8 @@ public class NotificationActionListLayout extends LinearLayout { private int mEmphasizedHeight; private int mRegularHeight; @DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start; + int mNumNotGoneChildren; + int mNumPriorityChildren; public NotificationActionListLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -76,15 +78,14 @@ public class NotificationActionListLayout extends LinearLayout { && ((EmphasizedNotificationButton) actionView).isPriority(); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int N = getChildCount(); + private void countAndRebuildMeasureOrder() { + final int numChildren = getChildCount(); int textViews = 0; int otherViews = 0; - int notGoneChildren = 0; - int priorityChildren = 0; + mNumNotGoneChildren = 0; + mNumPriorityChildren = 0; - for (int i = 0; i < N; i++) { + for (int i = 0; i < numChildren; i++) { View c = getChildAt(i); if (c instanceof TextView) { textViews++; @@ -92,9 +93,9 @@ public class NotificationActionListLayout extends LinearLayout { otherViews++; } if (c.getVisibility() != GONE) { - notGoneChildren++; + mNumNotGoneChildren++; if (isPriority(c)) { - priorityChildren++; + mNumPriorityChildren++; } } } @@ -119,17 +120,20 @@ public class NotificationActionListLayout extends LinearLayout { if (needRebuild) { rebuildMeasureOrder(textViews, otherViews); } + } + private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth, + boolean collapsePriorityActions) { + final int numChildren = getChildCount(); final boolean constrained = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED; - - final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight; final int otherSize = mMeasureOrderOther.size(); int usedWidth = 0; + int maxPriorityWidth = 0; int measuredChildren = 0; int measuredPriorityChildren = 0; - for (int i = 0; i < N; i++) { + for (int i = 0; i < numChildren; i++) { // Measure shortest children first. To avoid measuring twice, we approximate by looking // at the text length. final boolean isPriority; @@ -154,12 +158,20 @@ public class NotificationActionListLayout extends LinearLayout { // measure in the order of (approx.) size, a large view can still take more than its // share if the others are small. int availableWidth = innerWidth - usedWidth; - int unmeasuredChildren = notGoneChildren - measuredChildren; + int unmeasuredChildren = mNumNotGoneChildren - measuredChildren; int maxWidthForChild = availableWidth / unmeasuredChildren; - if (isPriority) { + if (isPriority && collapsePriorityActions) { + // Collapsing the actions to just the width required to show the icon. + if (maxPriorityWidth == 0) { + maxPriorityWidth = getResources().getDimensionPixelSize( + R.dimen.notification_actions_collapsed_priority_width); + } + maxWidthForChild = maxPriorityWidth + lp.leftMargin + lp.rightMargin; + } else if (isPriority) { // Priority children get a larger maximum share of the total space: // maximum priority share = (nPriority + 1) / (MAX + 1) - int unmeasuredPriorityChildren = priorityChildren - measuredPriorityChildren; + int unmeasuredPriorityChildren = mNumPriorityChildren + - measuredPriorityChildren; int unmeasuredOtherChildren = unmeasuredChildren - unmeasuredPriorityChildren; int widthReservedForOtherChildren = innerWidth * unmeasuredOtherChildren / (Notification.MAX_ACTION_BUTTONS + 1); @@ -187,6 +199,19 @@ public class NotificationActionListLayout extends LinearLayout { } else { mExtraStartPadding = 0; } + return usedWidth; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + countAndRebuildMeasureOrder(); + final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight; + int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth, + false /* collapsePriorityButtons */); + if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) { + usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth, + true /* collapsePriorityButtons */); + } mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding; setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec), diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml index 29c51f2a33c9..7c09fb889c48 100644 --- a/core/res/res/drawable/btn_notification_emphasized.xml +++ b/core/res/res/drawable/btn_notification_emphasized.xml @@ -24,9 +24,9 @@ android:insetBottom="@dimen/button_inset_vertical_material"> - diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index de7a1175b4a3..2baed4359ff6 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -237,6 +237,9 @@ value is calculated in ConversationLayout#updateActionListPadding() --> 36dp + + 60dp + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index adb046e76c88..3a2f28dea2a4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3183,6 +3183,7 @@ + -- cgit v1.2.3 From e2cce552ede6c86e65a205f357863ab3151b5302 Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Mon, 9 Aug 2021 14:41:58 -0400 Subject: Improve restore of QS_AUTO_ADDED_TILES In order to prevent race conditions and effects happening before restore happens, this CL implements the following changes: * QS_AUTO_ADDED_TILES is restored right after QS_TILES * Writes of QS_AUTO_ADDED_TILES are marked as overrideable by restore. That way, the restore will happen even if tiles are auto-added before the restore happens. * If/when the restore happens. Any tiles that had been auto-added and manually removed in the source device are removed from the set of current tiles. * If/when the restore happens, QS_AUTO_ADDED_TILES is resolved to be the merge of the value before and after restore. Test: atest SystemUITests Test: manual restore Fixes: 168687572 Change-Id: Iad151e8310f1a5b099125d20bb673703030501b6 --- .../provider/settings/backup/SecureSettings.java | 2 +- .../android/providers/settings/SettingsHelper.java | 29 ++- .../com/android/systemui/qs/AutoAddTracker.java | 156 ----------- .../src/com/android/systemui/qs/AutoAddTracker.kt | 285 +++++++++++++++++++++ .../src/com/android/systemui/qs/QSHost.java | 1 + .../src/com/android/systemui/qs/QSTileHost.java | 13 + .../android/systemui/qs/AutoAddTrackerTest.java | 226 +++++++++++++--- .../com/android/systemui/qs/QSTileHostTest.java | 34 +-- 8 files changed, 527 insertions(+), 219 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java create mode 100644 packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 60226084c70d..96f127b6a611 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -101,6 +101,7 @@ public class SecureSettings { Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, Settings.Secure.QS_TILES, + Settings.Secure.QS_AUTO_ADDED_TILES, Settings.Secure.CONTROLS_ENABLED, Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, Settings.Secure.DOZE_ENABLED, @@ -118,7 +119,6 @@ public class SecureSettings { Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, Settings.Secure.NOTIFICATION_DISMISS_RTL, - Settings.Secure.QS_AUTO_ADDED_TILES, Settings.Secure.SCREENSAVER_ENABLED, Settings.Secure.SCREENSAVER_COMPONENTS, Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index c57786888e8d..6cfcb51239a3 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -72,8 +72,9 @@ public class SettingsHelper { * {@hide} */ private static final ArraySet sBroadcastOnRestore; + private static final ArraySet sBroadcastOnRestoreSystemUI; static { - sBroadcastOnRestore = new ArraySet(4); + sBroadcastOnRestore = new ArraySet(9); sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS); sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); @@ -83,6 +84,9 @@ public class SettingsHelper { sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); + sBroadcastOnRestoreSystemUI = new ArraySet(2); + sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES); + sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES); } private interface SettingsLookup { @@ -133,6 +137,7 @@ public class SettingsHelper { // Will we need a post-restore broadcast for this element? String oldValue = null; boolean sendBroadcast = false; + boolean sendBroadcastSystemUI = false; final SettingsLookup table; if (destination.equals(Settings.Secure.CONTENT_URI)) { @@ -143,10 +148,12 @@ public class SettingsHelper { table = sGlobalLookup; } - if (sBroadcastOnRestore.contains(name)) { + sendBroadcast = sBroadcastOnRestore.contains(name); + sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name); + + if (sendBroadcast || sendBroadcastSystemUI) { // TODO: http://b/22388012 oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM); - sendBroadcast = true; } try { @@ -193,18 +200,28 @@ public class SettingsHelper { } catch (Exception e) { // If we fail to apply the setting, by definition nothing happened sendBroadcast = false; + sendBroadcastSystemUI = false; } finally { // If this was an element of interest, send the "we just restored it" // broadcast with the historical value now that the new value has // been committed and observers kicked off. - if (sendBroadcast) { + if (sendBroadcast || sendBroadcastSystemUI) { Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED) - .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY) .putExtra(Intent.EXTRA_SETTING_NAME, name) .putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value) .putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue) .putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt); - context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null); + + if (sendBroadcast) { + intent.setPackage("android"); + context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null); + } + if (sendBroadcastSystemUI) { + intent.setPackage( + context.getString(com.android.internal.R.string.config_systemUi)); + context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java deleted file mode 100644 index 38b20ee45946..000000000000 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java +++ /dev/null @@ -1,156 +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.qs; - -import static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT; -import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION; -import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT; -import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER; -import static com.android.systemui.statusbar.phone.AutoTileManager.WORK; - -import android.content.Context; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.ArraySet; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; -import com.android.systemui.util.UserAwareController; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - -import javax.inject.Inject; - -public class AutoAddTracker implements UserAwareController { - - private static final String[][] CONVERT_PREFS = { - {Key.QS_HOTSPOT_ADDED, HOTSPOT}, - {Key.QS_DATA_SAVER_ADDED, SAVER}, - {Key.QS_INVERT_COLORS_ADDED, INVERSION}, - {Key.QS_WORK_ADDED, WORK}, - {Key.QS_NIGHTDISPLAY_ADDED, NIGHT}, - }; - - private final ArraySet mAutoAdded; - private final Context mContext; - private int mUserId; - - public AutoAddTracker(Context context, int userId) { - mContext = context; - mUserId = userId; - mAutoAdded = new ArraySet<>(getAdded()); - } - - /** - * Init method must be called after construction to start listening - */ - public void initialize() { - // TODO: remove migration code and shared preferences keys after P release - if (mUserId == UserHandle.USER_SYSTEM) { - for (String[] convertPref : CONVERT_PREFS) { - if (Prefs.getBoolean(mContext, convertPref[0], false)) { - setTileAdded(convertPref[1]); - Prefs.remove(mContext, convertPref[0]); - } - } - } - mContext.getContentResolver().registerContentObserver( - Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver, - UserHandle.USER_ALL); - } - - @Override - public void changeUser(UserHandle newUser) { - if (newUser.getIdentifier() == mUserId) { - return; - } - mUserId = newUser.getIdentifier(); - mAutoAdded.clear(); - mAutoAdded.addAll(getAdded()); - } - - @Override - public int getCurrentUserId() { - return mUserId; - } - - public boolean isAdded(String tile) { - return mAutoAdded.contains(tile); - } - - public void setTileAdded(String tile) { - if (mAutoAdded.add(tile)) { - saveTiles(); - } - } - - public void setTileRemoved(String tile) { - if (mAutoAdded.remove(tile)) { - saveTiles(); - } - } - - public void destroy() { - mContext.getContentResolver().unregisterContentObserver(mObserver); - } - - private void saveTiles() { - Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, - TextUtils.join(",", mAutoAdded), mUserId); - } - - private Collection getAdded() { - String current = Secure.getStringForUser(mContext.getContentResolver(), - Secure.QS_AUTO_ADDED_TILES, mUserId); - if (current == null) { - return Collections.emptyList(); - } - return Arrays.asList(current.split(",")); - } - - @VisibleForTesting - protected final ContentObserver mObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - mAutoAdded.clear(); - mAutoAdded.addAll(getAdded()); - } - }; - - public static class Builder { - private final Context mContext; - private int mUserId; - - @Inject - public Builder(Context context) { - mContext = context; - } - - public Builder setUserId(int userId) { - mUserId = userId; - return this; - } - - public AutoAddTracker build() { - return new AutoAddTracker(mContext, mUserId); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt new file mode 100644 index 000000000000..7ffa9d931ff0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.UserHandle +import android.provider.Settings +import android.text.TextUtils +import android.util.ArraySet +import android.util.Log +import androidx.annotation.GuardedBy +import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.UserAwareController +import com.android.systemui.util.settings.SecureSettings +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.concurrent.Executor +import javax.inject.Inject + +private const val TAG = "AutoAddTracker" + +/** + * Class to track tiles that have been auto-added + * + * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES]. + * + * It also handles restore gracefully. + */ +class AutoAddTracker @VisibleForTesting constructor( + private val secureSettings: SecureSettings, + private val broadcastDispatcher: BroadcastDispatcher, + private val qsHost: QSHost, + private val dumpManager: DumpManager, + private val mainHandler: Handler?, + private val backgroundExecutor: Executor, + private var userId: Int +) : UserAwareController, Dumpable { + + companion object { + private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) + } + + @GuardedBy("autoAdded") + private val autoAdded = ArraySet() + private var restoredTiles: Set? = null + + override val currentUserId: Int + get() = userId + + private val contentObserver = object : ContentObserver(mainHandler) { + override fun onChange( + selfChange: Boolean, + uris: Collection, + flags: Int, + _userId: Int + ) { + if (_userId != userId) { + // Ignore changes outside of our user. We'll load the correct value on user change + return + } + loadTiles() + } + } + + private val restoreReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_SETTING_RESTORED) return + processRestoreIntent(intent) + } + } + + private fun processRestoreIntent(intent: Intent) { + when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { + Settings.Secure.QS_TILES -> { + restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) + ?.split(",") + ?.toSet() + ?: run { + Log.w(TAG, "Null restored tiles for user $userId") + emptySet() + } + } + Settings.Secure.QS_AUTO_ADDED_TILES -> { + restoredTiles?.let { tiles -> + val restoredAutoAdded = intent + .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) + ?.split(",") + ?: emptyList() + val autoAddedBeforeRestore = intent + .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE) + ?.split(",") + ?: emptyList() + + val tilesToRemove = restoredAutoAdded.filter { it !in tiles } + if (tilesToRemove.isNotEmpty()) { + qsHost.removeTiles(tilesToRemove) + } + val tiles = synchronized(autoAdded) { + autoAdded.clear() + autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) + getTilesFromListLocked() + } + saveTiles(tiles) + } ?: run { + Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + + "${Settings.Secure.QS_TILES} for user $userId") + } + } + else -> {} // Do nothing for other Settings + } + } + + /** + * Init method must be called after construction to start listening + */ + fun initialize() { + dumpManager.registerDumpable(TAG, this) + loadTiles() + secureSettings.registerContentObserverForUser( + secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), + contentObserver, + UserHandle.USER_ALL + ) + registerBroadcastReceiver() + } + + /** + * Unregister listeners, receivers and observers + */ + fun destroy() { + dumpManager.unregisterDumpable(TAG) + secureSettings.unregisterContentObserver(contentObserver) + unregisterBroadcastReceiver() + } + + private fun registerBroadcastReceiver() { + broadcastDispatcher.registerReceiver( + restoreReceiver, + FILTER, + backgroundExecutor, + UserHandle.of(userId) + ) + } + + private fun unregisterBroadcastReceiver() { + broadcastDispatcher.unregisterReceiver(restoreReceiver) + } + + override fun changeUser(newUser: UserHandle) { + if (newUser.identifier == userId) return + unregisterBroadcastReceiver() + userId = newUser.identifier + restoredTiles = null + loadTiles() + registerBroadcastReceiver() + } + + /** + * Returns `true` if the tile has been auto-added before + */ + fun isAdded(tile: String): Boolean { + return synchronized(autoAdded) { + tile in autoAdded + } + } + + /** + * Sets a tile as auto-added. + * + * From here on, [isAdded] will return true for that tile. + */ + fun setTileAdded(tile: String) { + val tiles = synchronized(autoAdded) { + if (autoAdded.add(tile)) { + getTilesFromListLocked() + } else { + null + } + } + tiles?.let { saveTiles(it) } + } + + /** + * Removes a tile from the list of auto-added. + * + * This allows for this tile to be auto-added again in the future. + */ + fun setTileRemoved(tile: String) { + val tiles = synchronized(autoAdded) { + if (autoAdded.remove(tile)) { + getTilesFromListLocked() + } else { + null + } + } + tiles?.let { saveTiles(it) } + } + + private fun getTilesFromListLocked(): String { + return TextUtils.join(",", autoAdded) + } + + private fun saveTiles(tiles: String) { + secureSettings.putStringForUser( + Settings.Secure.QS_AUTO_ADDED_TILES, + tiles, + /* tag */ null, + /* makeDefault */ false, + userId, + /* overrideableByRestore */ true + ) + } + + private fun loadTiles() { + synchronized(autoAdded) { + autoAdded.clear() + autoAdded.addAll(getAdded()) + } + } + + private fun getAdded(): Collection { + val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId) + return current?.split(",") ?: emptySet() + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { + pw.println("Current user: $userId") + pw.println("Added tiles: $autoAdded") + } + + @SysUISingleton + class Builder @Inject constructor( + private val secureSettings: SecureSettings, + private val broadcastDispatcher: BroadcastDispatcher, + private val qsHost: QSHost, + private val dumpManager: DumpManager, + @Main private val handler: Handler, + @Background private val executor: Executor + ) { + private var userId: Int = 0 + + fun setUserId(_userId: Int): Builder { + userId = _userId + return this + } + + fun build(): AutoAddTracker { + return AutoAddTracker( + secureSettings, + broadcastDispatcher, + qsHost, + dumpManager, + handler, + executor, + userId + ) + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 000fd1c4bd2e..9f585bdfaeb0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -37,6 +37,7 @@ public interface QSHost { void removeCallback(Callback callback); TileServices getTileServices(); void removeTile(String tileSpec); + void removeTiles(Collection specs); void unmarkTileAsAutoAdded(String tileSpec); int indexOf(String tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 541ee2c4fe8f..d5349d378305 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -347,6 +347,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)); } + /** + * Remove many tiles at once. + * + * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called + * multiple times). + */ + @Override + public void removeTiles(Collection specs) { + changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)); + } + @Override public void unmarkTileAsAutoAdded(String spec) { if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec); @@ -368,6 +379,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D * @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X */ public void addTile(String spec, int requestPosition) { + if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile"); changeTileSpecs(tileSpecs -> { if (tileSpecs.contains(spec)) return false; @@ -382,6 +394,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener, D } void saveTilesToSettings(List tileSpecs) { + if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile"); mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs), null /* tag */, false /* default */, mCurrentUser, true /* overrideable by restore */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java index de7abf866f6a..922c6b648aab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java @@ -14,13 +14,19 @@ package com.android.systemui.qs; -import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION; import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER; -import static com.android.systemui.statusbar.phone.AutoTileManager.WORK; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; - +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; import android.os.UserHandle; import android.provider.Settings.Secure; import android.testing.AndroidTestingRunner; @@ -28,13 +34,24 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; import com.android.systemui.SysuiTestCase; +import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @RunWithLooper @@ -43,42 +60,38 @@ public class AutoAddTrackerTest extends SysuiTestCase { private static final int USER = 0; + @Mock + private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private QSHost mQSHost; + @Mock + private DumpManager mDumpManager; + @Captor + private ArgumentCaptor mBroadcastReceiverArgumentCaptor; + @Captor + private ArgumentCaptor mIntentFilterArgumentCaptor; + + private Executor mBackgroundExecutor = Runnable::run; // Direct executor private AutoAddTracker mAutoTracker; + private SecureSettings mSecureSettings; @Before public void setUp() { - Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, ""); - } + MockitoAnnotations.initMocks(this); - @Test - public void testMigration() { - Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true); - Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true); - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); + mSecureSettings = new FakeSettings(); - assertTrue(mAutoTracker.isAdded(SAVER)); - assertTrue(mAutoTracker.isAdded(WORK)); - assertFalse(mAutoTracker.isAdded(INVERSION)); + mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER); - // These keys have been removed; retrieving their values should always return the default. - assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true )); - assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false)); - assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true)); - assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false)); - - mAutoTracker.destroy(); + mAutoTracker = createAutoAddTracker(USER); + mAutoTracker.initialize(); } @Test public void testChangeFromBackup() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); - assertFalse(mAutoTracker.isAdded(SAVER)); - Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, SAVER); - mAutoTracker.mObserver.onChange(false); + mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER); assertTrue(mAutoTracker.isAdded(SAVER)); @@ -87,9 +100,6 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testSetAdded() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); - assertFalse(mAutoTracker.isAdded(SAVER)); mAutoTracker.setTileAdded(SAVER); @@ -100,14 +110,12 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testPersist() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); - assertFalse(mAutoTracker.isAdded(SAVER)); mAutoTracker.setTileAdded(SAVER); mAutoTracker.destroy(); - mAutoTracker = new AutoAddTracker(mContext, USER); + mAutoTracker = createAutoAddTracker(USER); + mAutoTracker.initialize(); assertTrue(mAutoTracker.isAdded(SAVER)); @@ -116,22 +124,158 @@ public class AutoAddTrackerTest extends SysuiTestCase { @Test public void testIndependentUsers() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); mAutoTracker.setTileAdded(SAVER); - mAutoTracker = new AutoAddTracker(mContext, USER + 1); + mAutoTracker = createAutoAddTracker(USER + 1); + mAutoTracker.initialize(); assertFalse(mAutoTracker.isAdded(SAVER)); } @Test public void testChangeUser() { - mAutoTracker = new AutoAddTracker(mContext, USER); - mAutoTracker.initialize(); mAutoTracker.setTileAdded(SAVER); - mAutoTracker = new AutoAddTracker(mContext, USER + 1); + mAutoTracker = createAutoAddTracker(USER + 1); mAutoTracker.changeUser(UserHandle.of(USER)); assertTrue(mAutoTracker.isAdded(SAVER)); } + + @Test + public void testBroadcastReceiverRegistered() { + verify(mBroadcastDispatcher).registerReceiver( + any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER))); + + assertTrue( + mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED)); + } + + @Test + public void testBroadcastReceiverChangesWithUser() { + mAutoTracker.changeUser(UserHandle.of(USER + 1)); + + InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher); + inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any()); + inOrder.verify(mBroadcastDispatcher) + .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1))); + } + + @Test + public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() { + verify(mBroadcastDispatcher).registerReceiver( + mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any()); + + // These tiles were present in the original device + String restoredTiles = "saver,work,internet,cast"; + Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + // And these tiles have been auto-added in the original device + // (no auto-added before restore) + String restoredAutoAddTiles = "work"; + Intent restoreAutoAddTilesIntent = + makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); + + // Then, don't remove any current tiles + verify(mQSHost, never()).removeTiles(any()); + assertEquals(restoredAutoAddTiles, + mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); + } + + @Test + public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() { + verify(mBroadcastDispatcher) + .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any()); + + // These tiles were present in the original device + String restoredTiles = "saver,internet,cast"; + Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + // And these tiles have been auto-added in the original device + // (no auto-added before restore) + String restoredAutoAddTiles = "work"; + Intent restoreAutoAddTilesIntent = + makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); + + // Then, remove work tile + verify(mQSHost).removeTiles(List.of("work")); + assertEquals(restoredAutoAddTiles, + mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); + } + + @Test + public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() { + verify(mBroadcastDispatcher) + .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any()); + + // These tiles were present in the original device + String restoredTiles = "saver,internet,cast"; + Intent restoreTilesIntent = + makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + // And these tiles have been auto-added in the original device + // (no auto-added before restore) + String restoredAutoAddTiles = "work"; + Intent restoreAutoAddTilesIntent = + makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); + + // Then, remove work tile + verify(mQSHost).removeTiles(List.of("work")); + assertEquals(restoredAutoAddTiles, + mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER)); + } + + @Test + public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() { + verify(mBroadcastDispatcher) + .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any()); + + // These tiles were present in the original device + String restoredTiles = "saver,internet,cast"; + Intent restoreTilesIntent = + makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent); + + // And these tiles have been auto-added in the original device + // (no auto-added before restore) + String restoredAutoAddTiles = "work"; + Intent restoreAutoAddTilesIntent = + makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent); + + // Then, remove work tile + verify(mQSHost).removeTiles(List.of("work")); + + String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER); + assertEquals(2, setting.split(",").length); + assertTrue(setting.contains("work")); + assertTrue(setting.contains("inversion")); + } + + + private Intent makeRestoreIntent( + String settingName, String previousValue, String restoredValue) { + Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED); + intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName); + intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue); + intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue); + return intent; + } + + private AutoAddTracker createAutoAddTracker(int user) { + // Null handler wil dispatch sync. + return new AutoAddTracker( + mSecureSettings, + mBroadcastDispatcher, + mQSHost, + mDumpManager, + null, + mBackgroundExecutor, + user + ); + } } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 9e97f801be3e..84bc12f6e922 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -18,14 +18,11 @@ package com.android.systemui.qs; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static junit.framework.TestCase.assertFalse; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -67,12 +64,12 @@ import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.tuner.TunerService; +import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; 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; @@ -120,7 +117,6 @@ public class QSTileHostTest extends SysuiTestCase { private UiEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; - @Mock private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; @@ -134,14 +130,15 @@ public class QSTileHostTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mLooper = TestableLooper.get(this); mHandler = new Handler(mLooper.getLooper()); + + mSecureSettings = new FakeSettings(); + mSecureSettings.putStringForUser( + QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false); mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister); setUpTileFactory(); - - when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt())) - .thenReturn(""); } private void setUpTileFactory() { @@ -364,6 +361,16 @@ public class QSTileHostTest extends SysuiTestCase { .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId())); } + @Test + public void testRemoveTiles() { + List tiles = List.of("spec1", "spec2", "spec3"); + mQSTileHost.saveTilesToSettings(tiles); + + mQSTileHost.removeTiles(List.of("spec1", "spec2")); + + assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs); + } + private class TestQSTileHost extends QSTileHost { TestQSTileHost(Context context, StatusBarIconController iconController, QSFactory defaultFactory, Handler mainHandler, Looper bgLooper, @@ -389,14 +396,11 @@ public class QSTileHostTest extends SysuiTestCase { @Override void saveTilesToSettings(List tileSpecs) { super.saveTilesToSettings(tileSpecs); - - ArgumentCaptor specs = ArgumentCaptor.forClass(String.class); - verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING), - specs.capture(), isNull(), eq(false), anyInt(), eq(true)); - // After tiles are changed, make sure to call onTuningChanged with the new setting if it // changed - onTuningChanged(TILES_SETTING, specs.getValue()); + String specs = mSecureSettings.getStringForUser( + QSTileHost.TILES_SETTING, mUserTracker.getUserId()); + onTuningChanged(TILES_SETTING, specs); } } -- cgit v1.2.3 From fe59586648042e7e8e45362d4489989a938d0947 Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Thu, 5 Aug 2021 18:31:19 +0000 Subject: Add non-system overlay flag for usb perm dialog Add private flag SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS for UsbPermissionActivity. With this flag, UsbPermissionActivity will not been overlapped with views which are displayed on system alert window Bug: 183610267 Test: atest UsbPermissionActivityTest#testHideNonSystemOverlay Test: check the window private flag of UsbPermissionActivity contains SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS or not. Change-Id: I0863ff347ac3c2300f0dc649a630f55473ee180a (cherry picked from commit 9a03ae1184692d7e12a35269e4a19d0b27c2012a) --- .../systemui/usb/UsbPermissionActivity.java | 3 + packages/SystemUI/tests/AndroidManifest.xml | 7 ++ .../systemui/usb/UsbPermissionActivityTest.kt | 89 ++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java index 98b4209ede00..bfa50bcee270 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java @@ -36,6 +36,7 @@ import android.os.UserHandle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.TextView; @@ -63,6 +64,8 @@ public class UsbPermissionActivity extends AlertActivity public void onCreate(Bundle icicle) { super.onCreate(icicle); + getWindow().addPrivateFlags( + WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); Intent intent = getIntent(); mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index f1c687ff3224..2c9c98032245 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -93,6 +93,13 @@ + + + ( + UsbPermissionActivityTestable::class.java, false, false) + + private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java) + .apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra(UsbManager.EXTRA_PACKAGE, "com.android.systemui") + putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast( + mContext, + 334, + Intent("NO_ACTION"), + PendingIntent.FLAG_MUTABLE)) + putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory( + "manufacturer", + "model", + "description", + "version", + "uri", + object : IUsbSerialReader.Stub() { + override fun getSerial(packageName: String): String { + return "serial" + } + })) + } + + @Before + fun setUp() { + activityRule.launchActivity(activityIntent) + } + + @After + fun tearDown() { + activityRule.finishActivity() + } + + @Test + @Throws(Exception::class) + fun testHideNonSystemOverlay() { + assertThat(activityRule.activity.window.attributes.privateFlags and + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) + .isEqualTo(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) + } +} -- cgit v1.2.3 From fdc4bff3198fcaedfa29f45d92525c0351acd583 Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Thu, 24 Jun 2021 11:25:07 -0700 Subject: Fix issue when adding existing connections via remote connection services. When an existing connection is added to represent an IMS conference participant, it is added with an associated parent conference specified. This was NOT passed along to the connection manager via the RemoteConnectionService API since there is no equivalent API present for that operation. To work around this, we stash the parent conference's ID into the connection extras. We will use that later so that Telecom can know which conference is the parent of this call. Also, removing restriction in RemoteConnectionService which would ignore conferences with no children. This assumption was incorrect for VOLTE conferences since they will start with no children. As a consequence we would ALWAYS skip adding IMS conferences to the connection manager, which would mean that it had no way of knowing about the conference that the existing connections are associated with. Test: Manual test with connection manager APIS on live network; make conference and verify that the correct objects are being created wrapped by the connection manager. Bug: 188420526 Change-Id: Ie58afed7a3b7eeaa7e329e80479d273e4c50ec82 Change-Id: I4250f9459c7a1b82936583a10e93d049fdfb4c5d --- telecomm/java/android/telecom/Connection.java | 15 +++++++++++++ .../android/telecom/RemoteConnectionService.java | 26 ++++++++++++++++------ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 759afd72d539..f2f1645b335d 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -774,6 +774,21 @@ public abstract class Connection extends Conferenceable { public static final String EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE = "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE"; + /** + * The Telecom call ID of the conference an existing connection should be added to. This is + * required when {@link com.android.services.telephony.TelephonyConnectionService} adds a + * {@link Conference} to Telecom using the + * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection, Conference)} + * API. That API specifies a parent conference associated with the new existing connection + * being added, and there is no equivalent as part of the {@link RemoteConnectionService} API. + * This extra key is used to stack the ID of the conference to which the existing connection + * will be added so that Telecom can link it up correctly when the {@link RemoteConference} + * is added to Telecom by the connection manager. + * @hide + */ + public static final String EXTRA_ADD_TO_CONFERENCE_ID = + "android.telecom.extra.ADD_TO_CONFERENCE_ID"; + /** * Extra key set from a {@link ConnectionService} when using the remote connection APIs * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle, diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index bf6a6ef793ff..efe35d21c003 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -239,13 +239,9 @@ final class RemoteConnectionService { conference.addConnection(c); } } - if (conference.getConnections().size() == 0) { - // A conference was created, but none of its connections are ones that have been - // created by, and therefore being tracked by, this remote connection service. It - // is of no interest to us. - Log.d(this, "addConferenceCall - skipping"); - return; - } + // We used to skip adding empty conferences; however in the world of IMS conference + // calls we need to add them to the remote connection service because they will always + // start with no participants. conference.setState(parcel.getState()); conference.setConnectionCapabilities(parcel.getConnectionCapabilities()); @@ -379,6 +375,8 @@ final class RemoteConnectionService { @Override public void addExistingConnection(String callId, ParcelableConnection connection, Session.Info sessionInfo) { + Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId, + connection); String callingPackage = mOurConnectionServiceImpl.getApplicationContext(). getOpPackageName(); int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo() @@ -390,6 +388,20 @@ final class RemoteConnectionService { Bundle newExtras = new Bundle(); newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, connection.getPhoneAccount()); + if (connection.getParentCallId() != null) { + RemoteConference parentConf = mConferenceById.get(connection.getParentCallId()); + // If there is a parent being set, we need to stash the conference ID here. + // Telephony can add an existing connection while specifying a parent conference. + // There is no equivalent version of that operation as part of the remote connection + // API, so we will stash the pre-defined parent's ID in the extras. When the + // connectionmanager copies over the extras from the remote connection to the + // actual one, it'll get passed to Telecom so that it can make the association. + if (parentConf != null) { + newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId()); + Log.i(this, "addExistingConnection: stash parent of %s as %s", + connection.getParentCallId(), parentConf.getId()); + } + } remoteConnection.putExtras(newExtras); mConnectionById.put(callId, remoteConnection); remoteConnection.registerCallback(new RemoteConnection.Callback() { -- cgit v1.2.3 From 70a328921b8fd6e243e082fe668e8856d15a8146 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 13 Aug 2021 16:15:37 -0400 Subject: FooterView color logic cleanup Bug reports showed that when night mode eneded due to schedule, the text color was updated but not the background color. Just from analyzing the code, this appears to be due to onConfigurationChanged not being called on the footer view, but NSSL.onUiModeChanged was calling into FooterView.setTextColor. So logically, this seems like a likely fix, even though I can't repro the bug. Fixes: 190135611 Test: manual theme change continues to update footer colors. Test: manual language change updates footer text. Change-Id: If6fdb527eee244561dcd3975d859de93609662a5 --- .../statusbar/notification/row/FooterView.java | 31 +++++++++------------- .../stack/NotificationStackScrollLayout.java | 2 +- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 86c90c7bcb2e..9eb95c409009 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -28,15 +27,12 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; public class FooterView extends StackScrollerDecorView { - private final int mClearAllTopPadding; private FooterViewButton mDismissButton; private FooterViewButton mManageButton; private boolean mShowHistory; public FooterView(Context context, AttributeSet attrs) { super(context, attrs); - mClearAllTopPadding = context.getResources().getDimensionPixelSize( - R.dimen.clear_all_padding_top); } @Override @@ -55,11 +51,6 @@ public class FooterView extends StackScrollerDecorView { mManageButton = findViewById(R.id.manage_text); } - public void setTextColor(@ColorInt int color) { - mManageButton.setTextColor(color); - mDismissButton.setTextColor(color); - } - public void setManageButtonClickListener(OnClickListener listener) { mManageButton.setOnClickListener(listener); } @@ -95,21 +86,25 @@ public class FooterView extends StackScrollerDecorView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int textColor = getResources().getColor(R.color.notif_pill_text); - Resources.Theme theme = getContext().getTheme(); - mDismissButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mDismissButton.setTextColor(textColor); - mManageButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mManageButton = findViewById(R.id.manage_text); + updateColors(); mDismissButton.setText(R.string.clear_all_notifications_text); - mManageButton.setTextColor(textColor); mDismissButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); showHistory(mShowHistory); } + /** + * Update the text and background colors for the current color palette and night mode setting. + */ + public void updateColors() { + Resources.Theme theme = mContext.getTheme(); + int textColor = getResources().getColor(R.color.notif_pill_text, theme); + mDismissButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mDismissButton.setTextColor(textColor); + mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mManageButton.setTextColor(textColor); + } + @Override public ExpandableViewState createExpandableViewState() { return new FooterViewState(); 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 289c32f17b31..0660daab3720 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 @@ -4231,7 +4231,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); mSectionsManager.setHeaderForegroundColor(textColor); - mFooterView.setTextColor(textColor); + mFooterView.updateColors(); mEmptyShadeView.setTextColor(textColor); } -- cgit v1.2.3 From a2ab2772d70984b1c24d5d4509b9c7a5abea71f9 Mon Sep 17 00:00:00 2001 From: shubang Date: Fri, 13 Aug 2021 15:42:15 -0700 Subject: TIF: fix issue of using caller-aware methods after clearCallingIdentity() Bug: 189824175 Test: atest android.media.tv.cts.TvInputManagerTest Change-Id: Iced6c2245c0099bc4bcdaceb51f8cce4dbc0a392 --- services/core/java/com/android/server/tv/TvInputManagerService.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 36a854e5374c..28947083854b 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2304,10 +2304,9 @@ public final class TvInputManagerService extends SystemService { public void requestChannelBrowsable(Uri channelUri, int userId) throws RemoteException { final String callingPackageName = getCallingPackageName(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), + Binder.getCallingUid(), userId, "requestChannelBrowsable"); final long identity = Binder.clearCallingIdentity(); - final int callingUid = Binder.getCallingUid(); - final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "requestChannelBrowsable"); try { Intent intent = new Intent(TvContract.ACTION_CHANNEL_BROWSABLE_REQUESTED); List list = getContext().getPackageManager() -- cgit v1.2.3 From a00cda677956e0553a6de4df8f25ebc9b2b3039e Mon Sep 17 00:00:00 2001 From: Michael Wright Date: Sat, 14 Aug 2021 00:19:12 +0100 Subject: Only update the display when brightness and state are valid. If either are invalid then we're in an indeterminant state and need to wait until that's resolved. This should only happens at boot and then is immediately corrected when we set our initial desired state. By not checking the validity of the to-be-applied state, we're actually turning the display to our assumed state briefly (i.e. OFF) even though we don't know what state we actually want yet. Fixes: 196566901 Fixes: 196274559 Fixes: 195071558 Test: Reboot device, no flicker in boot animation Change-Id: Ie5a4c186e3af31eb8810f5c7014a4e2cc7cdbf91 --- .../core/java/com/android/server/display/DisplayPowerState.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java index 6af192371e3d..147050cd271f 100644 --- a/services/core/java/com/android/server/display/DisplayPowerState.java +++ b/services/core/java/com/android/server/display/DisplayPowerState.java @@ -411,7 +411,7 @@ final class DisplayPowerState { * Updates the state of the screen and backlight asynchronously on a separate thread. */ private final class PhotonicModulator extends Thread { - private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off + private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN; private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT; private final Object mLock = new Object(); @@ -494,7 +494,9 @@ final class DisplayPowerState { if (!backlightChanged) { mBacklightChangeInProgress = false; } - if (!stateChanged && !backlightChanged) { + boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState); + boolean changed = stateChanged || backlightChanged; + if (!valid || !changed) { try { mLock.wait(); } catch (InterruptedException ex) { -- cgit v1.2.3 From a88d234f4f6211c293aa50b07f6b282d9f70d11a Mon Sep 17 00:00:00 2001 From: Austin Wang Date: Wed, 11 Aug 2021 16:14:17 +0800 Subject: Auto adjust screen brightness when display state on while docked Enable the auto screen brightness for display on when docked. DOZE_AOD_DOCKED has STATE_ON display state in cases when we don't need to worry about registering for the brightness sensor when the display is on (ie: getSelectivelyRegisterProxSensor=false OR brightness sensor doesn't use prox) With this change, devices with getSelectivelyRegisterProxSensor=true and brightnessSensorUsesProx=true will still not register for the brightness sensor when the display state is on. Bug: 194151347 Test: atest DozeScreenBrightnessTest Test: manually verify under DOZE_AOD_DOCKED on devices with and w/o under display prox sensors / brightness sensors that use prox Change-Id: I04f88e7639a9cb0510a985d220da91fd1fb54313 --- packages/SystemUI/res/values/config.xml | 5 ++ .../systemui/doze/DozeScreenBrightness.java | 15 ++++- .../systemui/statusbar/phone/DozeParameters.java | 8 +++ .../systemui/doze/DozeConfigurationUtil.java | 1 + .../systemui/doze/DozeScreenBrightnessTest.java | 71 +++++++++++++++++++++- 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index d274c917c26d..b6d5b3a6760a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -194,6 +194,11 @@ low powered state yet. --> true + + true + true diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 470d2f364c1c..98d2739836a9 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -31,6 +31,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.view.Display; +import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.BrightnessSensor; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; @@ -63,6 +64,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi private final Optional mLightSensorOptional; private final WakefulnessLifecycle mWakefulnessLifecycle; private final DozeParameters mDozeParameters; + private final DockManager mDockManager; private final int[] mSensorToBrightness; private final int[] mSensorToScrimOpacity; private final int mScreenBrightnessDim; @@ -87,7 +89,8 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi @BrightnessSensor Optional lightSensorOptional, DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, - DozeParameters dozeParameters) { + DozeParameters dozeParameters, + DockManager dockManager) { mContext = context; mDozeService = service; mSensorManager = sensorManager; @@ -96,6 +99,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; + mDockManager = dockManager; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; @@ -122,13 +126,20 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi @Override public void onScreenState(int state) { - if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { + boolean isDockedScreenOn = state == Display.STATE_ON && mDockManager.isDocked(); + if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND + || (isDockedScreenOn && shouldRegisterLightSensorWhenScreenOnDocked())) { setLightSensorEnabled(true); } else { setLightSensorEnabled(false); } } + private boolean shouldRegisterLightSensorWhenScreenOnDocked() { + return !mDozeParameters.brightnessUsesProx() + || !mDozeParameters.getSelectivelyRegisterSensorsUsingProx(); + } + private void onDestroy() { setLightSensorEnabled(false); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 5a6db213d87f..36f6c4fd57a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -267,6 +267,13 @@ public class DozeParameters implements TunerService.Tunable, return mResources.getBoolean(R.bool.doze_long_press_uses_prox); } + /** + * Whether the brightness sensor uses the proximity sensor. + */ + public boolean brightnessUsesProx() { + return mResources.getBoolean(R.bool.doze_brightness_uses_prox); + } + /** * Callback to listen for DozeParameter changes. */ @@ -303,6 +310,7 @@ public class DozeParameters implements TunerService.Tunable, pw.print("getPickupVibrationThreshold(): "); pw.println(getPickupVibrationThreshold()); pw.print("getSelectivelyRegisterSensorsUsingProx(): "); pw.println(getSelectivelyRegisterSensorsUsingProx()); + pw.print("brightnessUsesProx(): "); pw.println(brightnessUsesProx()); } interface Callback { 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 d6226aa53f67..a32cb9b6baa9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java @@ -43,6 +43,7 @@ public class DozeConfigurationUtil { when(params.singleTapUsesProx()).thenReturn(true); when(params.longPressUsesProx()).thenReturn(true); when(params.getQuickPickupAodDuration()).thenReturn(500); + when(params.brightnessUsesProx()).thenReturn(true); doneHolder[0] = true; return params; diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 4e8b59c95681..deb7d31d87a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -29,6 +29,7 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -47,6 +48,7 @@ import android.view.Display; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.concurrency.FakeExecutor; @@ -82,6 +84,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { WakefulnessLifecycle mWakefulnessLifecycle; @Mock DozeParameters mDozeParameters; + @Mock + DockManager mDockManager; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor); @@ -109,7 +113,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { mSensor = fakeSensorManager.getFakeLightSensor(); mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); mScreen.onScreenState(Display.STATE_ON); } @@ -156,6 +160,67 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { assertEquals(maxBrightness, mServiceFake.screenBrightness); } + @Test + public void testAodDocked_doNotSelectivelyUseProx_usesLightSensor() { + // GIVEN the device doesn't need to selectively register for prox sensors and + // brightness sensor uses prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(false); + when(mDozeParameters.brightnessUsesProx()).thenReturn(true); + + // GIVEN the device is docked and the display state changes to ON + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is updated + assertEquals(3, mServiceFake.screenBrightness); + } + + @Test + public void testAodDocked_brightnessDoesNotUseProx_usesLightSensor() { + // GIVEN the device doesn't need to selectively register for prox sensors but + // the brightness sensor doesn't use prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); + when(mDozeParameters.brightnessUsesProx()).thenReturn(false); + + // GIVEN the device is docked and the display state changes to ON + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is updated + assertEquals(3, mServiceFake.screenBrightness); + } + + + @Test + public void testAodDocked_noProx_brightnessUsesProx_doNotUseLightSensor() { + final int startBrightness = mServiceFake.screenBrightness; + + // GIVEN the device needs to selectively register for prox sensors and + // the brightness sensor uses prox + when(mDozeParameters.getSelectivelyRegisterSensorsUsingProx()).thenReturn(true); + when(mDozeParameters.brightnessUsesProx()).thenReturn(true); + + // GIVEN the device is docked and the display state is on + when(mDockManager.isDocked()).thenReturn(true); + mScreen.onScreenState(Display.STATE_ON); + waitForSensorManager(); + + // WHEN new sensor event sent + mSensor.sendSensorEvent(3); + + // THEN brightness is NOT changed + assertNotSame(3, mServiceFake.screenBrightness); + assertEquals(startBrightness, mServiceFake.screenBrightness); + } + @Test public void testPausingAod_doesNotResetBrightness() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); @@ -175,7 +240,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE); reset(mDozeHost); @@ -216,7 +281,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { public void testNullSensor() throws Exception { mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager, Optional.empty() /* sensor */, mDozeHost, null /* handler */, - mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters); + mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager); mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); -- cgit v1.2.3 From 4c0e8a62e6ad66a2c96b4af58d4368aac15745c2 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 13 Aug 2021 14:39:52 -0400 Subject: Fix ContrastColorUtil.findContrastColorAgainstDark to return the valid color Fixes: 196600502 Test: atest ContrastColorUtilTest Change-Id: I590be0ba30c03e867bf08558483239818b3aff82 --- .../android/internal/util/ContrastColorUtil.java | 19 +++-- .../internal/util/ContrastColorUtilTest.java | 99 ++++++++++++++++++++++ 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java index 8b3c1337c0c8..7a712e50e6a8 100644 --- a/core/java/com/android/internal/util/ContrastColorUtil.java +++ b/core/java/com/android/internal/util/ContrastColorUtil.java @@ -291,10 +291,10 @@ public class ContrastColorUtil { * Finds a suitable color such that there's enough contrast. * * @param color the color to start searching from. - * @param other the color to ensure contrast against. Assumed to be lighter than {@param color} - * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. + * @param other the color to ensure contrast against. Assumed to be lighter than {@code color} + * @param findFg if true, we assume {@code color} is a foreground, otherwise a background. * @param minRatio the minimum contrast ratio required. - * @return a color with the same hue as {@param color}, potentially darkened to meet the + * @return a color with the same hue as {@code color}, potentially darkened to meet the * contrast ratio. */ public static int findContrastColor(int color, int other, boolean findFg, double minRatio) { @@ -331,7 +331,7 @@ public class ContrastColorUtil { * @param color the color to start searching from. * @param backgroundColor the color to ensure contrast against. * @param minRatio the minimum contrast ratio required. - * @return the same color as {@param color} with potentially modified alpha to meet contrast + * @return the same color as {@code color} with potentially modified alpha to meet contrast */ public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) { int fg = color; @@ -361,10 +361,10 @@ public class ContrastColorUtil { * Finds a suitable color such that there's enough contrast. * * @param color the color to start searching from. - * @param other the color to ensure contrast against. Assumed to be darker than {@param color} - * @param findFg if true, we assume {@param color} is a foreground, otherwise a background. + * @param other the color to ensure contrast against. Assumed to be darker than {@code color} + * @param findFg if true, we assume {@code color} is a foreground, otherwise a background. * @param minRatio the minimum contrast ratio required. - * @return a color with the same hue as {@param color}, potentially darkened to meet the + * @return a color with the same hue as {@code color}, potentially lightened to meet the * contrast ratio. */ public static int findContrastColorAgainstDark(int color, int other, boolean findFg, @@ -393,7 +393,8 @@ public class ContrastColorUtil { low = l; } } - return findFg ? fg : bg; + hsl[2] = high; + return ColorUtilsFromCompat.HSLToColor(hsl); } public static int ensureTextContrastOnBlack(int color) { @@ -452,7 +453,7 @@ public class ContrastColorUtil { } /** - * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT} + * Resolves {@code color} to an actual color if it is {@link Notification#COLOR_DEFAULT} */ public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) { if (color == Notification.COLOR_DEFAULT) { diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java new file mode 100644 index 000000000000..9da720cbfa87 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.internal.util; + +import static androidx.core.graphics.ColorUtils.calculateContrast; + +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.Color; + +import androidx.test.filters.SmallTest; + +import junit.framework.TestCase; + +public class ContrastColorUtilTest extends TestCase { + + @SmallTest + public void testEnsureTextContrastAgainstDark() { + int darkBg = 0xFF35302A; + + int blueContrastColor = ContrastColorUtil.ensureTextContrast(Color.BLUE, darkBg, true); + assertContrastIsWithinRange(blueContrastColor, darkBg, 4.5, 4.75); + + int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, darkBg, true); + assertContrastIsWithinRange(redContrastColor, darkBg, 4.5, 4.75); + + final int darkGreen = 0xff008800; + int greenContrastColor = ContrastColorUtil.ensureTextContrast(darkGreen, darkBg, true); + assertContrastIsWithinRange(greenContrastColor, darkBg, 4.5, 4.75); + + int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.DKGRAY, darkBg, true); + assertContrastIsWithinRange(grayContrastColor, darkBg, 4.5, 4.75); + + int selfContrastColor = ContrastColorUtil.ensureTextContrast(darkBg, darkBg, true); + assertContrastIsWithinRange(selfContrastColor, darkBg, 4.5, 4.75); + } + + @SmallTest + public void testEnsureTextContrastAgainstLight() { + int lightBg = 0xFFFFF8F2; + + final int lightBlue = 0xff8888ff; + int blueContrastColor = ContrastColorUtil.ensureTextContrast(lightBlue, lightBg, false); + assertContrastIsWithinRange(blueContrastColor, lightBg, 4.5, 4.75); + + int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, lightBg, false); + assertContrastIsWithinRange(redContrastColor, lightBg, 4.5, 4.75); + + int greenContrastColor = ContrastColorUtil.ensureTextContrast(Color.GREEN, lightBg, false); + assertContrastIsWithinRange(greenContrastColor, lightBg, 4.5, 4.75); + + int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.LTGRAY, lightBg, false); + assertContrastIsWithinRange(grayContrastColor, lightBg, 4.5, 4.75); + + int selfContrastColor = ContrastColorUtil.ensureTextContrast(lightBg, lightBg, false); + assertContrastIsWithinRange(selfContrastColor, lightBg, 4.5, 4.75); + } + + private void assertContrastIsWithinRange(int foreground, int background, + double minContrast, double maxContrast) { + assertContrastIsAtLeast(foreground, background, minContrast); + assertContrastIsAtMost(foreground, background, maxContrast); + } + + private void assertContrastIsAtLeast(int foreground, int background, double minContrast) { + try { + assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast); + } catch (AssertionError e) { + throw new AssertionError( + String.format("Insufficient contrast: foreground=#%08x background=#%08x", + foreground, background), e); + } + } + + private void assertContrastIsAtMost(int foreground, int background, double maxContrast) { + try { + assertThat(calculateContrast(foreground, background)).isAtMost(maxContrast); + } catch (AssertionError e) { + throw new AssertionError( + String.format("Excessive contrast: foreground=#%08x background=#%08x", + foreground, background), e); + } + } + +} -- cgit v1.2.3 From d8054270e7f9095eba483100ad18dbfe29f2f182 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Thu, 5 Aug 2021 09:34:34 -0400 Subject: Use tertiary accent color for expander with unread count. Fixes: 196586577 Test: visual inspection Test: atest android.app.NotificationTest Change-Id: I09b3999ad1c51615fbf7c8027dc65e6750721f18 --- core/java/android/app/Notification.java | 36 +++++++++++++++++++--- .../src/android/app/NotificationTest.java | 12 ++++++-- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4b054f49d910..8ac8e5e2224e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5387,8 +5387,8 @@ public class Notification implements Parcelable contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); // Use different highlighted colors for conversations' unread count if (p.mHighlightExpander) { - pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor); - textColor = Colors.flattenAlpha(bgColor, pillColor); + pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); + textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor); } contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); @@ -12305,6 +12305,8 @@ public class Notification implements Parcelable private int mSecondaryTextColor = COLOR_INVALID; private int mPrimaryAccentColor = COLOR_INVALID; private int mSecondaryAccentColor = COLOR_INVALID; + private int mTertiaryAccentColor = COLOR_INVALID; + private int mOnAccentTextColor = COLOR_INVALID; private int mErrorColor = COLOR_INVALID; private int mContrastColor = COLOR_INVALID; private int mRippleAlpha = 0x33; @@ -12362,7 +12364,7 @@ public class Notification implements Parcelable if (isColorized) { if (rawColor == COLOR_DEFAULT) { - int[] attrs = {R.attr.colorAccentTertiary}; + int[] attrs = {R.attr.colorAccentSecondary}; try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { mBackgroundColor = getColor(ta, 0, Color.WHITE); } @@ -12379,6 +12381,8 @@ public class Notification implements Parcelable mContrastColor = mPrimaryTextColor; mPrimaryAccentColor = mPrimaryTextColor; mSecondaryAccentColor = mSecondaryTextColor; + mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); + mOnAccentTextColor = mBackgroundColor; mErrorColor = mPrimaryTextColor; mRippleAlpha = 0x33; } else { @@ -12389,6 +12393,8 @@ public class Notification implements Parcelable R.attr.textColorSecondary, R.attr.colorAccent, R.attr.colorAccentSecondary, + R.attr.colorAccentTertiary, + R.attr.textColorOnAccent, R.attr.colorError, R.attr.colorControlHighlight }; @@ -12399,8 +12405,10 @@ public class Notification implements Parcelable mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID); mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID); mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID); - mErrorColor = getColor(ta, 6, COLOR_INVALID); - mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff)); + mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID); + mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID); + mErrorColor = getColor(ta, 8, COLOR_INVALID); + mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff)); } mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, mBackgroundColor, nightMode); @@ -12420,6 +12428,14 @@ public class Notification implements Parcelable if (mSecondaryAccentColor == COLOR_INVALID) { mSecondaryAccentColor = mContrastColor; } + if (mTertiaryAccentColor == COLOR_INVALID) { + mTertiaryAccentColor = mContrastColor; + } + if (mOnAccentTextColor == COLOR_INVALID) { + mOnAccentTextColor = ColorUtils.setAlphaComponent( + ContrastColorUtil.resolvePrimaryColor( + ctx, mTertiaryAccentColor, nightMode), 0xFF); + } if (mErrorColor == COLOR_INVALID) { mErrorColor = mPrimaryTextColor; } @@ -12485,6 +12501,16 @@ public class Notification implements Parcelable return mSecondaryAccentColor; } + /** @return the theme's tertiary accent color for colored UI elements. */ + public @ColorInt int getTertiaryAccentColor() { + return mTertiaryAccentColor; + } + + /** @return the theme's text color to be used on the tertiary accent color. */ + public @ColorInt int getOnAccentTextColor() { + return mOnAccentTextColor; + } + /** * @return the contrast-adjusted version of the color provided by the app, or the * primary text color when colorized. diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index cd07d464ee65..685671b083c4 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -399,6 +399,8 @@ public class NotificationTest { assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor()); assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor()); assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor()); + assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor()); + assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor()); assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor()); assertEquals(cDay.getContrastColor(), cNight.getContrastColor()); assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha()); @@ -413,20 +415,26 @@ public class NotificationTest { assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); + assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); + assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getRippleAlpha()).isAtLeast(0x00); assertThat(c.getRippleAlpha()).isAtMost(0xff); - // Assert that various colors have sufficient contrast + // Assert that various colors have sufficient contrast with the background assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5); - // This accent color is only used for emphasized buttons + // These colors are only used for emphasized buttons; they do not need contrast assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1); + assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1); + + // The text that is used within the accent color DOES need to have contrast + assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5); } private void assertContrastIsAtLeast(int foreground, int background, double minContrast) { -- cgit v1.2.3 From 2bd5f502fd3a94ca40572edc21f8d752b90d6f57 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Fri, 13 Aug 2021 13:16:58 -0400 Subject: Make foreground color selection in SmartReplyView and RemoteInputView match Notification. Fixes: 196585371 Test: post colorized notification with luminance between .2 and .5; validate that smart replies and remote input use black text to match the notification. Change-Id: I5ea69181755ed5c2b48352a0379ae4e898202e21 Merged-In: I5ea69181755ed5c2b48352a0379ae4e898202e21 --- core/java/android/app/Notification.java | 3 ++- .../src/com/android/systemui/statusbar/policy/RemoteInputView.java | 3 +-- .../src/com/android/systemui/statusbar/policy/SmartReplyView.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4b054f49d910..b4a2de2a46ee 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6247,8 +6247,9 @@ public class Notification implements Parcelable * * @param color the color to check * @return true if the color has higher contrast with white than black + * @hide */ - private static boolean isColorDark(int color) { + public static boolean isColorDark(int color) { // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 84d7c05ddc14..5d7d4809dd57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -77,7 +77,6 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.NotificationRemoteInputManager; @@ -204,7 +203,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene final int stroke = colorized ? mContext.getResources().getDimensionPixelSize( R.dimen.remote_input_view_text_stroke) : 0; if (colorized) { - final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor); + final boolean dark = Notification.Builder.isColorDark(backgroundColor); final int foregroundColor = dark ? Color.WHITE : Color.BLACK; final int inverseColor = dark ? Color.BLACK : Color.WHITE; editBgColor = backgroundColor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 41b1dd12639a..4e33529f3c36 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -628,7 +628,7 @@ public class SmartReplyView extends ViewGroup { mCurrentBackgroundColor = backgroundColor; mCurrentColorized = colorized; - final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor); + final boolean dark = Notification.Builder.isColorDark(backgroundColor); mCurrentTextColor = ContrastColorUtil.ensureTextContrast( dark ? mDefaultTextColorDarkBg : mDefaultTextColor, -- cgit v1.2.3 From a7cb583f3a7b13b06e09c4377e0e01145f7e0464 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Mon, 16 Aug 2021 08:49:37 -0700 Subject: Use an exact alarm for location timeout. Use an exact alarm for the location timeout to avoid keeping the GPS active for too long. Bug: 194385761 Test: atest DeviceIdleTest Test: atest FrameworksMockingServicesTests:DeviceIdleControllerTest Change-Id: I52b7ec6d993705ee605e3780a29a484c0d48e633 --- .../service/java/com/android/server/DeviceIdleController.java | 4 ++++ .../src/com/android/server/DeviceIdleControllerTest.java | 1 + 2 files changed, 5 insertions(+) diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 45588e831cb9..9eb7bb7149ef 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -3941,6 +3941,10 @@ public class DeviceIdleController extends SystemService if (idleUntil) { mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler); + } else if (mState == STATE_LOCATING) { + // Use setExact so we don't keep the GPS active for too long. + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler); } else { if (mConstants.USE_WINDOW_ALARMS) { mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index acf50b4569c6..e472b062388e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -323,6 +323,7 @@ public class DeviceIdleControllerTest { when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock); doNothing().when(mWakeLock).acquire(); doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any()); + doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any()); doNothing().when(mAlarmManager) .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any()); doReturn(mock(Sensor.class)).when(mSensorManager) -- cgit v1.2.3 From d61e4732c78c56182dff98508c41f61871a3d939 Mon Sep 17 00:00:00 2001 From: Selim Cinek Date: Fri, 13 Aug 2021 15:03:06 +0200 Subject: Fixed an issue where the statusbar was made opaque even though it wasnt Because on Pixel 4 and other devices, the light reveal scrim doesn't have full alpha, the notification panel was marked opaque which removed the wallpaper. This could lead to the wallpaper not being visible in a couple of situations where it should have been, leaving AOD exposed. Because of a separate bug (b/196306312), whenever the wallpaper is hidden, we'd see a color layer, which would lead to the screen becoming flickery and awake on AOD. Fixes: 194760238 Test: atest SystemUITests Merged-In: I5a7e80e2ced8f15f1b215549d0c051f1d61d2953 Change-Id: I5a7e80e2ced8f15f1b215549d0c051f1d61d2953 --- .../android/systemui/statusbar/LightRevealScrim.kt | 32 ++++++++++- .../NotificationShadeWindowController.java | 6 +- .../NotificationShadeWindowControllerImpl.java | 7 +-- .../systemui/statusbar/phone/StatusBar.java | 15 ++++- .../systemui/statusbar/LightRevealScrimTest.kt | 66 ++++++++++++++++++++++ .../NotificationShadeWindowControllerImplTest.java | 2 +- 6 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 538db6168408..21ed9da896a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -149,7 +149,10 @@ class PowerButtonReveal( */ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - lateinit var revealAmountListener: Consumer + /** + * Listener that is called if the scrim's opaqueness changes + */ + lateinit var isScrimOpaqueChangedListener: Consumer /** * How much of the underlying views are revealed, in percent. 0 means they will be completely @@ -161,7 +164,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, field = value revealEffect.setRevealAmountOnScrim(value, this) - revealAmountListener.accept(value) + updateScrimOpaque() invalidate() } } @@ -200,6 +203,31 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, } } + /** + * Is the scrim currently fully opaque + */ + var isScrimOpaque = false + private set(value) { + if (field != value) { + field = value + isScrimOpaqueChangedListener.accept(field) + } + } + + private fun updateScrimOpaque() { + isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE + } + + override fun setAlpha(alpha: Float) { + super.setAlpha(alpha) + updateScrimOpaque() + } + + override fun setVisibility(visibility: Int) { + super.setVisibility(visibility) + updateScrimOpaque() + } + /** * Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated * via local matrix in [onDraw] so we never need to construct a new shader. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index f0d779ce1e0f..6ea79af8b9ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -182,10 +182,10 @@ public interface NotificationShadeWindowController extends RemoteInputController default void setFaceAuthDisplayBrightness(float brightness) {} /** - * How much {@link LightRevealScrim} obscures the UI. - * @param amount 0 when opaque, 1 when not transparent + * If {@link LightRevealScrim} obscures the UI. + * @param opaque if the scrim is opaque */ - default void setLightRevealScrimAmount(float amount) {} + default void setLightRevealScrimOpaque(boolean opaque) {} /** * Custom listener to pipe data back to plugins about whether or not the status bar would be diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 246810a2d70b..c26782b017c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -605,12 +605,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } @Override - public void setLightRevealScrimAmount(float amount) { - boolean lightRevealScrimOpaque = amount == 0; - if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) { + public void setLightRevealScrimOpaque(boolean opaque) { + if (mCurrentState.mLightRevealScrimOpaque == opaque) { return; } - mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque; + mCurrentState.mLightRevealScrimOpaque = opaque; apply(mCurrentState); } 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 394e4ad76df1..4bdc4f059ff1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1258,8 +1258,19 @@ public class StatusBar extends SystemUI implements DemoMode, mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); - mLightRevealScrim.setRevealAmountListener( - mNotificationShadeWindowController::setLightRevealScrimAmount); + mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { + Runnable updateOpaqueness = () -> { + mNotificationShadeWindowController.setLightRevealScrimOpaque( + mLightRevealScrim.isScrimOpaque()); + }; + if (opaque) { + // Delay making the view opaque for a frame, because it needs some time to render + // otherwise this can lead to a flicker where the scrim doesn't cover the screen + mLightRevealScrim.post(updateOpaqueness); + } else { + updateOpaqueness.run(); + } + }); mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim); updateLightRevealScrimVisibility(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt new file mode 100644 index 000000000000..97fe25d9a619 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt @@ -0,0 +1,66 @@ +/* + * 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 + +import android.testing.AndroidTestingRunner +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.function.Consumer + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class LightRevealScrimTest : SysuiTestCase() { + + private lateinit var scrim: LightRevealScrim + private var isOpaque = false + + @Before + fun setUp() { + scrim = LightRevealScrim(context, null) + scrim.isScrimOpaqueChangedListener = Consumer { opaque -> + isOpaque = opaque + } + scrim.revealAmount = 0f + assertTrue("Scrim is not opaque in initial setup", scrim.isScrimOpaque) + } + + @Test + fun testAlphaSetsOpaque() { + scrim.alpha = 0.5f + assertFalse("Scrim is opaque even though alpha is set", scrim.isScrimOpaque) + } + + @Test + fun testVisibilitySetsOpaque() { + scrim.visibility = View.INVISIBLE + assertFalse("Scrim is opaque even though it's invisible", scrim.isScrimOpaque) + scrim.visibility = View.GONE + assertFalse("Scrim is opaque even though it's gone", scrim.isScrimOpaque) + } + + @Test + fun testRevealSetsOpaque() { + scrim.revealAmount = 0.5f + assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index ddd78541d113..90b8a74d88be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -146,7 +146,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController.attach(); clearInvocations(mWindowManager); - mNotificationShadeWindowController.setLightRevealScrimAmount(0f); + mNotificationShadeWindowController.setLightRevealScrimOpaque(true); verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue(); } -- cgit v1.2.3 From 37a0b6de89f7fb321fbeac02ec1a012817b8e682 Mon Sep 17 00:00:00 2001 From: Winson Date: Fri, 18 Jun 2021 11:17:50 -0700 Subject: Fix parsing code parcelling errors Address problems reading/writing: - ParsingPackageImpl mKeySetMapping - ParsingPackageImpl mQueriesIntent Bug: 187043377 Bug: 195962697 Test: atest com.android.server.pm.test.parsing.parcelling Merged-In: I5b33315f8248d5fcbdef2cc04ecf77cc18dbd7b6 Change-Id: I5b33315f8248d5fcbdef2cc04ecf77cc18dbd7b6 (cherry picked from commit f93af7ef7ebe9d139a34e615b97393a41ebabb56) --- .../content/pm/parsing/ParsingPackageImpl.java | 6 +-- .../content/pm/parsing/ParsingPackageUtils.java | 63 ++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f932bc250e28..d851b612999c 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -1005,7 +1005,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { sForInternedStringList.parcel(this.requestedPermissions, dest, flags); sForInternedStringList.parcel(this.implicitPermissions, dest, flags); sForStringSet.parcel(this.upgradeKeySets, dest, flags); - dest.writeMap(this.keySetMapping); + ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping); sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags); dest.writeTypedList(this.activities); dest.writeTypedList(this.receivers); @@ -1024,7 +1024,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { dest.writeBoolean(this.use32BitAbi); dest.writeBoolean(this.visibleToInstantApps); dest.writeBoolean(this.forceQueryable); - dest.writeParcelableList(this.queriesIntents, flags); + dest.writeTypedList(this.queriesIntents, flags); sForInternedStringList.parcel(this.queriesPackages, dest, flags); dest.writeString(this.appComponentFactory); dest.writeString(this.backupAgentName); @@ -1166,7 +1166,7 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.requestedPermissions = sForInternedStringList.unparcel(in); this.implicitPermissions = sForInternedStringList.unparcel(in); this.upgradeKeySets = sForStringSet.unparcel(in); - this.keySetMapping = in.readHashMap(boot); + this.keySetMapping = ParsingPackageUtils.readKeySetMapping(in); this.protectedBroadcasts = sForInternedStringList.unparcel(in); this.activities = in.createTypedArrayList(ParsedActivity.CREATOR); diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index ab0ed51fb909..a56a3ea9b2f6 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -84,6 +84,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.FileUtils; +import android.os.Parcel; import android.os.RemoteException; import android.os.Trace; import android.os.ext.SdkExtensions; @@ -2834,6 +2835,68 @@ public class ParsingPackageUtils { return sa.getNonResourceString(index); } + /** + * Writes the keyset mapping to the provided package. {@code null} mappings are permitted. + */ + public static void writeKeySetMapping(@NonNull Parcel dest, + @NonNull Map> keySetMapping) { + if (keySetMapping == null) { + dest.writeInt(-1); + return; + } + + final int N = keySetMapping.size(); + dest.writeInt(N); + + for (String key : keySetMapping.keySet()) { + dest.writeString(key); + ArraySet keys = keySetMapping.get(key); + if (keys == null) { + dest.writeInt(-1); + continue; + } + + final int M = keys.size(); + dest.writeInt(M); + for (int j = 0; j < M; j++) { + dest.writeSerializable(keys.valueAt(j)); + } + } + } + + /** + * Reads a keyset mapping from the given parcel at the given data position. May return + * {@code null} if the serialized mapping was {@code null}. + */ + @NonNull + public static ArrayMap> readKeySetMapping(@NonNull Parcel in) { + final int N = in.readInt(); + if (N == -1) { + return null; + } + + ArrayMap> keySetMapping = new ArrayMap<>(); + for (int i = 0; i < N; ++i) { + String key = in.readString(); + final int M = in.readInt(); + if (M == -1) { + keySetMapping.put(key, null); + continue; + } + + ArraySet keys = new ArraySet<>(M); + for (int j = 0; j < M; ++j) { + PublicKey pk = (PublicKey) in.readSerializable(); + keys.add(pk); + } + + keySetMapping.put(key, keys); + } + + return keySetMapping; + } + + /** * Callback interface for retrieving information that may be needed while parsing * a package. -- cgit v1.2.3 From 35b1dde19be373d10a25c9214b932819543ebe70 Mon Sep 17 00:00:00 2001 From: Huihong Luo Date: Fri, 13 Aug 2021 10:28:10 -0700 Subject: Turn on Webview Overlays Change the default value of the sysprop to be true. Bug: 173671170 Test: install webview apks from Vasiliy, make sure surface control is used. Change-Id: Idf495a2be3b2bdba4fdaa380a5dbe9fd96158a97 --- libs/hwui/Properties.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index b8fa55a18dac..109b5352fe30 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -84,7 +84,7 @@ float Properties::defaultSdrWhitePoint = 200.f; bool Properties::useHintManager = true; int Properties::targetCpuTimePercentage = 70; -bool Properties::enableWebViewOverlays = false; +bool Properties::enableWebViewOverlays = true; StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI; @@ -139,7 +139,7 @@ bool Properties::load() { targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70); if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70; - enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, false); + enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } -- cgit v1.2.3 From dd7ed60de0efbc682ee98605faa128fa39bb99dc Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Thu, 29 Jul 2021 21:49:07 +0000 Subject: Revert "Revert "Migrate boot animation from GLES 1.0 to GLES2.0."" This reverts commit 2ded80094ee534cfbf360e89562f9d6f1cee33e0. Reason for revert: Working on fixing the regression that happened on Wembly, so we can resume feature work. Change-Id: I9a624beffdaf97f131046fd4441a251ed3a448ad Test: adb shell /system/bin/bootanimation Bug: 190093578 (cherry picked from commit 8eedc0293d53d31f96c2f9cd2f773149aaf5d63f) --- cmds/bootanimation/Android.bp | 2 +- cmds/bootanimation/BootAnimation.cpp | 271 ++++++++++++++++++++++++----------- cmds/bootanimation/BootAnimation.h | 10 +- 3 files changed, 194 insertions(+), 89 deletions(-) diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp index b2b66c27f795..3534624a58a2 100644 --- a/cmds/bootanimation/Android.bp +++ b/cmds/bootanimation/Android.bp @@ -71,7 +71,7 @@ cc_library_shared { "libui", "libjnigraphics", "libEGL", - "libGLESv1_CM", + "libGLESv2", "libgui", ], } diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 3109c5c1e075..6e27aff5ae7b 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -52,9 +52,8 @@ #include #include #include - -#include -#include +#include +#include #include #include "BootAnimation.h" @@ -108,6 +107,56 @@ static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress"; static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays"; static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1; static constexpr size_t TEXT_POS_LEN_MAX = 16; +static const char U_TEXTURE[] = "uTexture"; +static const char U_FADE[] = "uFade"; +static const char U_CROP_AREA[] = "uCropArea"; +static const char A_UV[] = "aUv"; +static const char A_POSITION[] = "aPosition"; +static const char VERTEX_SHADER_SOURCE[] = R"( + precision mediump float; + attribute vec4 aPosition; + attribute highp vec2 aUv; + varying highp vec2 vUv; + void main() { + gl_Position = aPosition; + vUv = aUv; + })"; +static const char IMAGE_FRAG_SHADER_SOURCE[] = R"( + precision mediump float; + uniform sampler2D uTexture; + uniform float uFade; + varying highp vec2 vUv; + void main() { + vec4 color = texture2D(uTexture, vUv); + gl_FragColor = vec4(color.x, color.y, color.z, 1.0 - uFade); + })"; +static const char TEXT_FRAG_SHADER_SOURCE[] = R"( + precision mediump float; + uniform sampler2D uTexture; + uniform vec4 uCropArea; + varying highp vec2 vUv; + void main() { + vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x), + mix(uCropArea.y, uCropArea.w, vUv.y)); + gl_FragColor = texture2D(uTexture, uv); + })"; + +static GLfloat quadPositions[] = { + -0.5f, -0.5f, + +0.5f, -0.5f, + +0.5f, +0.5f, + +0.5f, +0.5f, + -0.5f, +0.5f, + -0.5f, -0.5f +}; +static GLfloat quadUVs[] = { + 0.0f, 1.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 0.0f, + 0.0f, 1.0f +}; // --------------------------------------------------------------------------- @@ -209,7 +258,6 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, const int w = bitmapInfo.width; const int h = bitmapInfo.height; - GLint crop[4] = { 0, h, w, -h }; texture->w = w; texture->h = h; @@ -237,11 +285,10 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, break; } - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); return NO_ERROR; } @@ -263,7 +310,6 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { const int w = bitmapInfo.width; const int h = bitmapInfo.height; - GLint crop[4] = { 0, h, w, -h }; int tw = 1 << (31 - __builtin_clz(w)); int th = 1 << (31 - __builtin_clz(h)); if (tw < w) tw <<= 1; @@ -297,7 +343,10 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) { break; } - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); *width = w; *height = h; @@ -470,7 +519,9 @@ status_t BootAnimation::readyToRun() { eglInitialize(display, nullptr, nullptr); EGLConfig config = getEglConfig(display); EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr); - EGLContext context = eglCreateContext(display, config, nullptr, nullptr); + // Initialize egl context with client version number 2.0. + EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes); EGLint w, h; eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); @@ -503,11 +554,6 @@ status_t BootAnimation::readyToRun() { void BootAnimation::projectSceneToWindow() { glViewport(0, 0, mWidth, mHeight); glScissor(0, 0, mWidth, mHeight); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glOrthof(0, static_cast(mWidth), 0, static_cast(mHeight), -1, 1); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); } void BootAnimation::resizeSurface(int newWidth, int newHeight) { @@ -600,8 +646,68 @@ void BootAnimation::findBootAnimationFile() { } } +GLuint compileShader(GLenum shaderType, const GLchar *source) { + GLuint shader = glCreateShader(shaderType); + glShaderSource(shader, 1, &source, 0); + glCompileShader(shader); + GLint isCompiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) { + SLOGE("Compile shader failed. Shader type: %d", shaderType); + return 0; + } + return shader; +} + +GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) { + GLuint program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + GLint isLinked = 0; + glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked); + if (isLinked == GL_FALSE) { + SLOGE("Linking shader failed. Shader handles: vert %d, frag %d", + vertexShader, fragmentShader); + return 0; + } + return program; +} + +void BootAnimation::initShaders() { + GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE); + GLuint imageFragmentShader = + compileShader(GL_FRAGMENT_SHADER, (const GLchar *)IMAGE_FRAG_SHADER_SOURCE); + GLuint textFragmentShader = + compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE); + + // Initialize image shader. + mImageShader = linkShader(vertexShader, imageFragmentShader); + GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION); + GLint uvLocation = glGetAttribLocation(mImageShader, A_UV); + mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE); + mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE); + glEnableVertexAttribArray(positionLocation); + glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions); + glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs); + glEnableVertexAttribArray(uvLocation); + + // Initialize text shader. + mTextShader = linkShader(vertexShader, textFragmentShader); + positionLocation = glGetAttribLocation(mTextShader, A_POSITION); + uvLocation = glGetAttribLocation(mTextShader, A_UV); + mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE); + mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA); + glEnableVertexAttribArray(positionLocation); + glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions); + glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs); + glEnableVertexAttribArray(uvLocation); +} + bool BootAnimation::threadLoop() { bool result; + initShaders(); + // We have no bootanimation file, so we use the stock android logo // animation. if (mZipFileName.isEmpty()) { @@ -623,6 +729,8 @@ bool BootAnimation::threadLoop() { } bool BootAnimation::android() { + glActiveTexture(GL_TEXTURE0); + SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime()); initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png"); @@ -631,19 +739,14 @@ bool BootAnimation::android() { mCallbacks->init({}); // clear screen - glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glClearColor(0,0,0,1); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); - glEnable(GL_TEXTURE_2D); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - // Blend state glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); const nsecs_t startTime = systemTime(); do { @@ -666,12 +769,12 @@ bool BootAnimation::android() { glEnable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, mAndroid[1].name); - glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h); - glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h); + drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h); + drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h); glEnable(GL_BLEND); glBindTexture(GL_TEXTURE_2D, mAndroid[0].name); - glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h); + drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h); EGLBoolean res = eglSwapBuffers(mDisplay, mSurface); if (res == EGL_FALSE) @@ -798,10 +901,10 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) { status = initTexture(font->map, &font->texture.w, &font->texture.h); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } else if (fallback != nullptr) { status = initTexture(&font->texture, mAssets, fallback); } else { @@ -816,40 +919,11 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) { return status; } -void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth, - const int frameHeight, const Animation::Part& part, - const int fadedFramesCount) { - glEnable(GL_BLEND); - glEnableClientState(GL_VERTEX_ARRAY); - glDisable(GL_TEXTURE_2D); - // avoid creating a hole due to mixing result alpha with GL_REPLACE texture - glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - - const float alpha = static_cast(fadedFramesCount) / part.framesToFadeCount; - glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha); - - const float frameStartX = static_cast(frameLeft); - const float frameStartY = static_cast(frameBottom); - const float frameEndX = frameStartX + frameWidth; - const float frameEndY = frameStartY + frameHeight; - const GLfloat frameRect[] = { - frameStartX, frameStartY, - frameEndX, frameStartY, - frameEndX, frameEndY, - frameStartX, frameEndY - }; - glVertexPointer(2, GL_FLOAT, 0, frameRect); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - glDisableClientState(GL_VERTEX_ARRAY); - glDisable(GL_BLEND); -} - void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) { glEnable(GL_BLEND); // Allow us to draw on top of the animation glBindTexture(GL_TEXTURE_2D, font.texture.name); + glUseProgram(mTextShader); + glUniform1i(mTextTextureLocation, 0); const int len = strlen(str); const int strWidth = font.char_width * len; @@ -865,8 +939,6 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* *y = mHeight + *y - font.char_height; } - int cropRect[4] = { 0, 0, font.char_width, -font.char_height }; - for (int i = 0; i < len; i++) { char c = str[i]; @@ -878,13 +950,13 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters const int row = charPos / FONT_NUM_COLS; const int col = charPos % FONT_NUM_COLS; - cropRect[0] = col * font.char_width; // Left of column - cropRect[1] = row * font.char_height * 2; // Top of row - // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line - cropRect[1] += bold ? 2 * font.char_height : font.char_height; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect); - - glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height); + // Bold fonts are expected in the second half of each row. + float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS; + float u0 = ((float)col) / FONT_NUM_COLS; + float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2; + float u1 = u0 + 1.0f / FONT_NUM_COLS; + glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1); + drawTexturedQuad(*x, *y, font.char_width, font.char_height); *x += font.char_width; } @@ -1166,19 +1238,16 @@ bool BootAnimation::movie() { // Blend required to draw time on top of animation frames. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); - glBindTexture(GL_TEXTURE_2D, 0); glEnable(GL_TEXTURE_2D); - glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - + glBindTexture(GL_TEXTURE_2D, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); bool clockFontInitialized = false; if (mClockEnabled) { clockFontInitialized = @@ -1218,6 +1287,34 @@ bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part, (lastDisplayedProgress == 0 || lastDisplayedProgress == 100); } +// Linear mapping from range to range +float mapLinear(float x, float a1, float a2, float b1, float b2) { + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); +} + +void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) { + // Map coordinates from screen space to world space. + float x0 = mapLinear(xStart, 0, mWidth, -1, 1); + float y0 = mapLinear(yStart, 0, mHeight, -1, 1); + float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1); + float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1); + // Update quad vertex positions. + quadPositions[0] = x0; + quadPositions[1] = y0; + quadPositions[2] = x1; + quadPositions[3] = y0; + quadPositions[4] = x1; + quadPositions[5] = y1; + quadPositions[6] = x1; + quadPositions[7] = y1; + quadPositions[8] = x0; + quadPositions[9] = y1; + quadPositions[10] = x0; + quadPositions[11] = y0; + glDrawArrays(GL_TRIANGLES, 0, + sizeof(quadPositions) / sizeof(quadPositions[0]) / 2); +} + bool BootAnimation::playAnimation(const Animation& animation) { const size_t pcount = animation.parts.size(); nsecs_t frameDuration = s2ns(1) / animation.fps; @@ -1230,7 +1327,6 @@ bool BootAnimation::playAnimation(const Animation& animation) { for (size_t i=0 ; i 0) { glBindTexture(GL_TEXTURE_2D, frame.tid); } else { - if (part.count != 1) { - glGenTextures(1, &frame.tid); - glBindTexture(GL_TEXTURE_2D, frame.tid); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } + glGenTextures(1, &frame.tid); + glBindTexture(GL_TEXTURE_2D, frame.tid); int w, h; initTexture(frame.map, &w, &h); } @@ -1300,16 +1392,21 @@ bool BootAnimation::playAnimation(const Animation& animation) { // specify the y center as ceiling((mHeight - frame.trimHeight) / 2) // which is equivalent to mHeight - (yc + frame.trimHeight) const int frameDrawY = mHeight - (yc + frame.trimHeight); - glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight); + float fade = 0; // if the part hasn't been stopped yet then continue fading if necessary if (exitPending() && part.hasFadingPhase()) { - fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part, - ++fadedFramesCount); + fade = static_cast(++fadedFramesCount) / part.framesToFadeCount; if (fadedFramesCount >= part.framesToFadeCount) { fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading } } + glUseProgram(mImageShader); + glUniform1i(mImageTextureLocation, 0); + glUniform1f(mImageFadeLocation, fade); + glEnable(GL_BLEND); + drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight); + glDisable(GL_BLEND); if (mClockEnabled && mTimeIsAccurate && validClock(part)) { drawClock(animation.clockFont, part.clockPosX, part.clockPosY); diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h index f8a31c6d8790..7b616d91c58b 100644 --- a/cmds/bootanimation/BootAnimation.h +++ b/cmds/bootanimation/BootAnimation.h @@ -31,7 +31,7 @@ #include #include -#include +#include namespace android { @@ -166,6 +166,7 @@ private: status_t initTexture(Texture* texture, AssetManager& asset, const char* name); status_t initTexture(FileMap* map, int* width, int* height); status_t initFont(Font* font, const char* fallback); + void initShaders(); bool android(); bool movie(); void drawText(const char* str, const Font& font, bool bold, int* x, int* y); @@ -173,6 +174,7 @@ private: void drawProgress(int percent, const Font& font, const int xPos, const int yPos); void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight, const Animation::Part& part, int fadedFramesCount); + void drawTexturedQuad(float xStart, float yStart, float width, float height); bool validClock(const Animation::Part& part); Animation* loadAnimation(const String8&); bool playAnimation(const Animation&); @@ -218,6 +220,12 @@ private: sp mTimeCheckThread = nullptr; sp mCallbacks; Animation* mAnimation = nullptr; + GLuint mImageShader; + GLuint mTextShader; + GLuint mImageFadeLocation; + GLuint mImageTextureLocation; + GLuint mTextCropAreaLocation; + GLuint mTextTextureLocation; }; // --------------------------------------------------------------------------- -- cgit v1.2.3 From f7882cb918b17034f5c5f90126f92b6e5b8ecfe8 Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Tue, 20 Jul 2021 11:21:17 -0400 Subject: Pause Primary Prox when Secondary Running. This reduces usage of the primary threshold sensor by turning it off when the secondary sensor is "safe" to use. Fixes: 194021865 Test: atest SystemUITests Change-Id: I95a10370bdecdd909b36259e47c1e450335eb778 --- .../systemui/util/sensors/ProximitySensor.java | 48 ++++++++---- .../util/sensors/ProximitySensorDualTest.java | 88 +++++++++++++++------- 2 files changed, 92 insertions(+), 44 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 90e022a52d7a..bd1103982017 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -87,15 +87,23 @@ public class ProximitySensor implements ThresholdSensor { && (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow() || !event.getBelow())) { - mSecondaryThresholdSensor.pause(); + chooseSensor(); if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) { // Only check the secondary as long as the primary thinks we're near. - mCancelSecondaryRunnable = null; + if (mCancelSecondaryRunnable != null) { + mCancelSecondaryRunnable.run(); + mCancelSecondaryRunnable = null; + } return; } else { // Check this sensor again in a moment. - mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed( - mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS); + mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> { + // This is safe because we know that mSecondaryThresholdSensor + // is loaded, otherwise we wouldn't be here. + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.resume(); + }, + SECONDARY_PING_INTERVAL_MS); } } logDebug("Secondary sensor event: " + event.getBelow() + "."); @@ -159,12 +167,8 @@ public class ProximitySensor implements ThresholdSensor { * of what is reported by the primary sensor. */ public void setSecondarySafe(boolean safe) { - mSecondarySafe = safe; - if (!mSecondarySafe) { - mSecondaryThresholdSensor.pause(); - } else { - mSecondaryThresholdSensor.resume(); - } + mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe; + chooseSensor(); } /** @@ -209,16 +213,30 @@ public class ProximitySensor implements ThresholdSensor { return; } if (!mInitializedListeners) { + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.pause(); mPrimaryThresholdSensor.register(mPrimaryEventListener); - if (!mSecondarySafe) { - mSecondaryThresholdSensor.pause(); - } mSecondaryThresholdSensor.register(mSecondaryEventListener); mInitializedListeners = true; } logDebug("Registering sensor listener"); - mPrimaryThresholdSensor.resume(); + mRegistered = true; + chooseSensor(); + } + + private void chooseSensor() { + mExecution.assertIsMainThread(); + if (!mRegistered || mPaused || mListeners.isEmpty()) { + return; + } + if (mSecondarySafe) { + mSecondaryThresholdSensor.resume(); + mPrimaryThresholdSensor.pause(); + } else { + mPrimaryThresholdSensor.resume(); + mSecondaryThresholdSensor.pause(); + } } /** @@ -312,7 +330,7 @@ public class ProximitySensor implements ThresholdSensor { } if (!mSecondarySafe && !event.getBelow()) { - mSecondaryThresholdSensor.pause(); + chooseSensor(); } mLastEvent = event; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java index a34c5986f36c..0e9d96c61e54 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java @@ -16,6 +16,8 @@ package com.android.systemui.util.sensors; +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; @@ -338,30 +340,25 @@ public class ProximitySensorDualTest extends SysuiTestCase { @Test public void testSecondaryCancelsSecondary() { TestableListener listener = new TestableListener(); - ThresholdSensor.Listener cancelingListener = new ThresholdSensor.Listener() { - @Override - public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) { - mProximitySensor.pause(); - } - }; + ThresholdSensor.Listener cancelingListener = event -> mProximitySensor.pause(); mProximitySensor.register(listener); mProximitySensor.register(cancelingListener); - assertNull(listener.mLastEvent); - assertEquals(0, listener.mCallCount); + assertThat(listener.mLastEvent).isNull(); + assertThat(listener.mCallCount).isEqualTo(0); mThresholdSensorPrimary.triggerEvent(true, 0); - assertNull(listener.mLastEvent); - assertEquals(0, listener.mCallCount); + assertThat(listener.mLastEvent).isNull(); + assertThat(listener.mCallCount).isEqualTo(0); mThresholdSensorSecondary.triggerEvent(true, 0); - assertTrue(listener.mLastEvent.getBelow()); - assertEquals(1, listener.mCallCount); + assertThat(listener.mLastEvent.getBelow()).isTrue(); + assertThat(listener.mCallCount).isEqualTo(1); // The proximity sensor should now be canceled. Advancing the clock should do nothing. - assertEquals(0, mFakeExecutor.numPending()); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); mThresholdSensorSecondary.triggerEvent(false, 1); - assertTrue(listener.mLastEvent.getBelow()); - assertEquals(1, listener.mCallCount); + assertThat(listener.mLastEvent.getBelow()).isTrue(); + assertThat(listener.mCallCount).isEqualTo(1); mProximitySensor.unregister(listener); } @@ -372,33 +369,66 @@ public class ProximitySensorDualTest extends SysuiTestCase { TestableListener listener = new TestableListener(); - // WE immediately register the secondary sensor. + // We immediately register the secondary sensor. mProximitySensor.register(listener); - assertFalse(mThresholdSensorPrimary.isPaused()); - assertFalse(mThresholdSensorSecondary.isPaused()); - assertNull(listener.mLastEvent); - assertEquals(0, listener.mCallCount); + assertThat(mThresholdSensorPrimary.isPaused()).isTrue(); + assertThat(mThresholdSensorSecondary.isPaused()).isFalse(); + assertThat(listener.mLastEvent).isNull(); + assertThat(listener.mCallCount).isEqualTo(0); mThresholdSensorPrimary.triggerEvent(true, 0); - assertNull(listener.mLastEvent); - assertEquals(0, listener.mCallCount); + assertThat(listener.mLastEvent).isNull(); + assertThat(listener.mCallCount).isEqualTo(0); mThresholdSensorSecondary.triggerEvent(true, 0); - assertTrue(listener.mLastEvent.getBelow()); - assertEquals(1, listener.mCallCount); + assertThat(listener.mLastEvent.getBelow()).isTrue(); + assertThat(listener.mCallCount).isEqualTo(1); // The secondary sensor should now remain resumed indefinitely. - assertFalse(mThresholdSensorSecondary.isPaused()); + assertThat(mThresholdSensorSecondary.isPaused()).isFalse(); mThresholdSensorSecondary.triggerEvent(false, 1); - assertFalse(listener.mLastEvent.getBelow()); - assertEquals(2, listener.mCallCount); + assertThat(listener.mLastEvent.getBelow()).isFalse(); + assertThat(listener.mCallCount).isEqualTo(2); // The secondary is still running, and not polling with the executor. - assertFalse(mThresholdSensorSecondary.isPaused()); - assertEquals(0, mFakeExecutor.numPending()); + assertThat(mThresholdSensorSecondary.isPaused()).isFalse(); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); mProximitySensor.unregister(listener); } + @Test + public void testSecondaryPausesPrimary() { + TestableListener listener = new TestableListener(); + + mProximitySensor.register(listener); + + assertThat(mThresholdSensorPrimary.isPaused()).isFalse(); + assertThat(mThresholdSensorSecondary.isPaused()).isTrue(); + + mProximitySensor.setSecondarySafe(true); + + assertThat(mThresholdSensorPrimary.isPaused()).isTrue(); + assertThat(mThresholdSensorSecondary.isPaused()).isFalse(); + } + + @Test + public void testSecondaryResumesPrimary() { + mProximitySensor.setSecondarySafe(true); + + TestableListener listener = new TestableListener(); + mProximitySensor.register(listener); + + assertThat(mThresholdSensorPrimary.isPaused()).isTrue(); + assertThat(mThresholdSensorSecondary.isPaused()).isFalse(); + + mProximitySensor.setSecondarySafe(false); + + assertThat(mThresholdSensorPrimary.isPaused()).isFalse(); + assertThat(mThresholdSensorSecondary.isPaused()).isTrue(); + + + } + private static class TestableListener implements ThresholdSensor.Listener { ThresholdSensor.ThresholdSensorEvent mLastEvent; int mCallCount = 0; -- cgit v1.2.3 From ddf464bc3239e10cf5edd7c166f71a4c71f69ea0 Mon Sep 17 00:00:00 2001 From: Curtis Belmonte Date: Fri, 13 Aug 2021 15:15:48 -0700 Subject: Update icon for BiometricPrompt fingerprint animations Use the new fingerprint icon for the fingerprint -> error and error -> fingerprint animations shown on BiometricPrompt. Test: Manual: 1. Enroll a fingerprint 2. Trigger BiometricPrompt (e.g. Internet > Settings > Share) 3. Try authenticating with an unenrolled fingerprint 4. Verify the updated animations are shown Fixes: 196453178 Change-Id: Ief3cf752d12897955886e0787701774bd6a0403e --- .../drawable/fingerprint_dialog_error_to_fp.xml | 483 +++++++------------- .../drawable/fingerprint_dialog_fp_to_error.xml | 486 +++++++-------------- 2 files changed, 317 insertions(+), 652 deletions(-) diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml index 33263a9131a0..0ae5dc745478 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml @@ -1,413 +1,242 @@ - + + + xmlns:aapt="http://schemas.android.com/aapt"> - + - - - - - - - + + + - - - - - - - - - - + + + + + + + + + - + - + - + - + - + - + - - - - - - - + - + - + - + - + - + - + - + - + - - - - - - - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - + - + - + - + - + - + - + - + - + - + + + + + + + - + - + - + - + - + - + - + - + @@ -416,14 +245,10 @@ - + - \ No newline at end of file + diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml index b899828cd85c..fc2c7d00f3a7 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml @@ -1,391 +1,235 @@ - + + + xmlns:aapt="http://schemas.android.com/aapt"> - + - - - - - - - + + + - - - - - - - - - - + + + + + + + + + - + - + - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - + - + - - - - - - - + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - + + + + + + + - + - + + + + + + + - + - + + + + + + + - + - + - + - + @@ -394,14 +238,10 @@ - + - \ No newline at end of file + -- cgit v1.2.3 From 358d4a8ae2452b2aca2a453ae8a7cb6f837385e3 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:23:42 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I4ddce4e02e9512d2f42d7cb44df57524f5e8e6d8 --- packages/SettingsLib/SearchWidget/res/values-te/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml index c5ece7454b22..dbad586f48d1 100644 --- a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml @@ -17,5 +17,5 @@ - "సెట్టింగ్‌లను వెతకండి" + "సెట్టింగ్‌లను సెర్చ్ చేయండి" -- cgit v1.2.3 From 3b34b471d55f12be5a804d8dca5863d12a49137b Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:28:04 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Id652e05d24cf6c3978cc31918cebb3d8a84c8671 --- packages/SettingsLib/SearchWidget/res/values-te/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml index c5ece7454b22..dbad586f48d1 100644 --- a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml @@ -17,5 +17,5 @@ - "సెట్టింగ్‌లను వెతకండి" + "సెట్టింగ్‌లను సెర్చ్ చేయండి" -- cgit v1.2.3 From 5c07e879b6a9dc0be7f1bd71d42bd393eced41ab Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:32:21 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I2fb43bc06d388e49d33779385861e5c760f76a0c --- packages/SettingsLib/SearchWidget/res/values-te/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml index c5ece7454b22..dbad586f48d1 100644 --- a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml @@ -17,5 +17,5 @@ - "సెట్టింగ్‌లను వెతకండి" + "సెట్టింగ్‌లను సెర్చ్ చేయండి" -- cgit v1.2.3 From ac9f98ab35c058b43bb9b0f1fb1fa9ccd0e4b058 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:34:20 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: Ibd66b9de0d927c82d264adf21052dc8c95c3fbee --- packages/SettingsLib/SearchWidget/res/values-te/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml index c5ece7454b22..dbad586f48d1 100644 --- a/packages/SettingsLib/SearchWidget/res/values-te/strings.xml +++ b/packages/SettingsLib/SearchWidget/res/values-te/strings.xml @@ -17,5 +17,5 @@ - "సెట్టింగ్‌లను వెతకండి" + "సెట్టింగ్‌లను సెర్చ్ చేయండి" -- cgit v1.2.3 From d40ba9889569c6852c324ac8d8214a452100069a Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:41:10 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I36676446dfe296dfa7fbedecb3cb42be4bbe09ff --- packages/SettingsLib/res/values-mr/strings.xml | 2 +- packages/SettingsLib/res/values-ta/strings.xml | 14 +++++++------- packages/SettingsLib/res/values-te/arrays.xml | 4 ++-- packages/SettingsLib/res/values-te/strings.xml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index b405806ce115..2b37b3262a90 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -307,7 +307,7 @@ "तुम्ही पूर्वी ऑथोराइझ केलेल्या सर्व संगणकांवरुन USB डीबग करण्यासाठी अ‍ॅक्सेस रीव्होक करायचा?" "विकास सेटिंग्जला अनुमती द्यायची?" "या सेटिंग्जचा हेतू फक्त विकास वापरासाठी आहे. त्यामुळे तुमचे डिव्हाइस आणि त्यावरील ॲप्लिकेशन ब्रेक होऊ शकतात किंवा नेहमीपेक्षा वेगळे वर्तन करू शकतात." - "USB वर अ‍ॅप्स पडताळून पाहा" + "USB वर अ‍ॅप्स पडताळून पहा" "हानिकारक वर्तनासाठी ADB/ADT द्वारे इंस्टॉल अ‍ॅप्स तपासा." "नावांशिवाय ब्‍लूटूथ डिव्‍हाइस (फक्‍त MAC पत्ते) दाखवले जातील" "रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते." diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index b10eae99b943..524bf034130c 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -83,7 +83,7 @@ "செயலில் உள்ளது" "மீடியா ஆடியோ" "ஃபோன் அழைப்புகள்" - "கோப்பு இடமாற்றம்" + "ஃபைல் இடமாற்றம்" "உள்ளீட்டுச் சாதனம்" "இணைய அணுகல்" "தொடர்புப் பகிர்தல்" @@ -100,7 +100,7 @@ "ஃபைலைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது" "வரைபடத்துடன் இணைக்கப்பட்டது" "SAP உடன் இணைக்கப்பட்டது" - "கோப்பு இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" + "ஃபைல் இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" "உள்ளீட்டுச் சாதனத்துடன் இணைக்கப்பட்டது" "சாதனத்துடன் இணைந்தது" "சாதனத்துடன் உள்ளூர் இண்டர்நெட்டைப் பகிர்தல்" @@ -109,7 +109,7 @@ "சிம் அணுகலுக்குப் பயன்படுத்தும்" "மீடியாவின் ஆடியோவிற்குப் பயன்படுத்து" "மொபைல் ஆடியோவைப் பயன்படுத்து" - "கோப்பு பரிமாற்றத்திற்காகப் பயன்படுத்து" + "ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து" "உள்ளீட்டுக்குப் பயன்படுத்து" "செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்" "இணை" @@ -405,11 +405,11 @@ "WebView செயல்படுத்தல்" "WebView செயல்படுத்தலை அமை" "இனி இந்தத் தேர்வைப் பயன்படுத்த முடியாது. மீண்டும் முயலவும்." - "கோப்பு முறைமையாக்கத்திற்கு மாற்று" + "ஃபைல் முறைமையாக்கத்திற்கு மாற்று" "மாற்று…" - "ஏற்கனவே கோப்பு என்க்ரிப்ட் செய்யப்பட்டது" - "கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" - "தரவுப் பகிர்வை, கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." + "ஏற்கனவே ஃபைல் என்க்ரிப்ட் செய்யப்பட்டது" + "ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" + "தரவுப் பகிர்வை, ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." "அழித்து மாற்று…" "படத்தின் வண்ணப் பயன்முறை" "sRGBஐப் பயன்படுத்தும்" diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml index 5068819def5f..4eeeb2b2fc3e 100644 --- a/packages/SettingsLib/res/values-te/arrays.xml +++ b/packages/SettingsLib/res/values-te/arrays.xml @@ -25,7 +25,7 @@ "స్కాన్ చేస్తోంది…" "కనెక్ట్ చేస్తోంది..." "ప్రామాణీకరిస్తోంది…" - "IP చిరునామాను పొందుతోంది…" + "IP అడ్రస్‌ను పొందుతోంది…" "కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "డిస్‌కనెక్ట్ చేస్తోంది..." @@ -39,7 +39,7 @@ "స్కాన్ చేస్తోంది…" "%1$sకి కనెక్ట్ చేస్తోంది…" "%1$sతో ప్రామాణీకరిస్తోంది…" - "%1$s నుండి IP చిరునామాను పొందుతోంది…" + "%1$s నుండి IP అడ్రస్‌ను పొందుతోంది…" "%1$sకి కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "%1$s నుండి డిస్‌కనెక్ట్ చేస్తోంది…" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index ba4a1d81358e..0f1e481c006a 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -26,7 +26,7 @@ "డిస్‌కనెక్ట్ అయ్యింది" "డిజేబుల్ చేయబడింది" "IP కాన్ఫిగరేషన్ వైఫల్యం" - "తక్కువ నాణ్యతా నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" + "తక్కువ క్వాలిటీ నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" "WiFi కనెక్షన్ వైఫల్యం" "ప్రామాణీకరణ సమస్య" "కనెక్ట్ చేయడం సాధ్యపడదు" -- cgit v1.2.3 From ae7644f3bfab919dc2db2b96fc547bb977a3e217 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 20:50:36 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I9b66642bfc61b946331c18850ef5d0b16fc85d71 --- packages/SettingsLib/res/values-mr/strings.xml | 2 +- packages/SettingsLib/res/values-ta/strings.xml | 14 +++++++------- packages/SettingsLib/res/values-te/arrays.xml | 4 ++-- packages/SettingsLib/res/values-te/strings.xml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 22929758515d..908ac1794128 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -306,7 +306,7 @@ "तुम्ही पूर्वी ऑथोराइझ केलेल्या सर्व संगणकांवरुन USB डीबग करण्यासाठी अ‍ॅक्सेस रीव्होक करायचा?" "विकास सेटिंग्जला अनुमती द्यायची?" "या सेटिंग्जचा हेतू फक्त विकास वापरासाठी आहे. त्यामुळे तुमचे डिव्हाइस आणि त्यावरील ॲप्लिकेशन ब्रेक होऊ शकतात किंवा नेहमीपेक्षा वेगळे वर्तन करू शकतात." - "USB वर अ‍ॅप्स पडताळून पाहा" + "USB वर अ‍ॅप्स पडताळून पहा" "हानिकारक वर्तनासाठी ADB/ADT द्वारे इंस्टॉल अ‍ॅप्स तपासा." "नावांशिवाय ब्‍लूटूथ डिव्‍हाइस (फक्‍त MAC पत्ते) दाखवले जातील" "रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते." diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index a03088250cc5..87e4f6899dc6 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -83,7 +83,7 @@ "செயலில் உள்ளது" "மீடியா ஆடியோ" "ஃபோன் அழைப்புகள்" - "கோப்பு இடமாற்றம்" + "ஃபைல் இடமாற்றம்" "உள்ளீட்டுச் சாதனம்" "இணைய அணுகல்" "தொடர்புப் பகிர்தல்" @@ -100,7 +100,7 @@ "ஃபைலைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது" "வரைபடத்துடன் இணைக்கப்பட்டது" "SAP உடன் இணைக்கப்பட்டது" - "கோப்பு இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" + "ஃபைல் இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" "உள்ளீட்டுச் சாதனத்துடன் இணைக்கப்பட்டது" "சாதனத்துடன் இணைந்தது" "சாதனத்துடன் உள்ளூர் இண்டர்நெட்டைப் பகிர்தல்" @@ -109,7 +109,7 @@ "சிம் அணுகலுக்குப் பயன்படுத்தும்" "மீடியாவின் ஆடியோவிற்குப் பயன்படுத்து" "மொபைல் ஆடியோவைப் பயன்படுத்து" - "கோப்பு பரிமாற்றத்திற்காகப் பயன்படுத்து" + "ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து" "உள்ளீட்டுக்குப் பயன்படுத்து" "செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்" "இணை" @@ -404,11 +404,11 @@ "WebView செயல்படுத்தல்" "WebView செயல்படுத்தலை அமை" "இனி இந்தத் தேர்வைப் பயன்படுத்த முடியாது. மீண்டும் முயலவும்." - "கோப்பு முறைமையாக்கத்திற்கு மாற்று" + "ஃபைல் முறைமையாக்கத்திற்கு மாற்று" "மாற்று…" - "ஏற்கனவே கோப்பு என்க்ரிப்ட் செய்யப்பட்டது" - "கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" - "தரவுப் பகிர்வை, கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." + "ஏற்கனவே ஃபைல் என்க்ரிப்ட் செய்யப்பட்டது" + "ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" + "தரவுப் பகிர்வை, ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." "அழித்து மாற்று…" "படத்தின் வண்ணப் பயன்முறை" "sRGBஐப் பயன்படுத்தும்" diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml index e9897f1d1442..e7e480ec07f9 100644 --- a/packages/SettingsLib/res/values-te/arrays.xml +++ b/packages/SettingsLib/res/values-te/arrays.xml @@ -25,7 +25,7 @@ "స్కాన్ చేస్తోంది…" "కనెక్ట్ చేస్తోంది..." "ప్రామాణీకరిస్తోంది…" - "IP చిరునామాను పొందుతోంది…" + "IP అడ్రస్‌ను పొందుతోంది…" "కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "డిస్‌కనెక్ట్ చేస్తోంది..." @@ -39,7 +39,7 @@ "స్కాన్ చేస్తోంది…" "%1$sకి కనెక్ట్ చేస్తోంది…" "%1$sతో ప్రామాణీకరిస్తోంది…" - "%1$s నుండి IP చిరునామాను పొందుతోంది…" + "%1$s నుండి IP అడ్రస్‌ను పొందుతోంది…" "%1$sకి కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "%1$s నుండి డిస్‌కనెక్ట్ చేస్తోంది…" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 21befac608ec..324498757595 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -26,7 +26,7 @@ "డిస్‌కనెక్ట్ అయ్యింది" "డిజేబుల్ చేయబడింది" "IP కాన్ఫిగరేషన్ వైఫల్యం" - "తక్కువ నాణ్యతా నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" + "తక్కువ క్వాలిటీ నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" "WiFi కనెక్షన్ వైఫల్యం" "ప్రామాణీకరణ సమస్య" "కనెక్ట్ చేయడం సాధ్యపడదు" -- cgit v1.2.3 From c8e007b5de4402ece7817428f4fd702eaa0a3e86 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 21:04:48 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I49de9da8a2273fb25bf25f8d2fe2fea25b60a0cb --- packages/SettingsLib/res/values-mr/strings.xml | 2 +- packages/SettingsLib/res/values-ta/strings.xml | 14 +++++++------- packages/SettingsLib/res/values-te/arrays.xml | 4 ++-- packages/SettingsLib/res/values-te/strings.xml | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 864f177fcec9..631cb3b9b628 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -307,7 +307,7 @@ "तुम्ही पूर्वी ऑथोराइझ केलेल्या सर्व संगणकांवरुन USB डीबग करण्यासाठी अ‍ॅक्सेस रीव्होक करायचा?" "विकास सेटिंग्जला अनुमती द्यायची?" "या सेटिंग्जचा हेतू फक्त विकास वापरासाठी आहे. त्यामुळे तुमचे डिव्हाइस आणि त्यावरील ॲप्लिकेशन ब्रेक होऊ शकतात किंवा नेहमीपेक्षा वेगळे वर्तन करू शकतात." - "USB वर अ‍ॅप्स पडताळून पाहा" + "USB वर अ‍ॅप्स पडताळून पहा" "हानिकारक वर्तनासाठी ADB/ADT द्वारे इंस्टॉल अ‍ॅप्स तपासा." "नावांशिवाय ब्‍लूटूथ डिव्‍हाइस (फक्‍त MAC पत्ते) दाखवले जातील" "रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते." diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 4a3b32a3a90f..839853629569 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -84,7 +84,7 @@ "செயலில் உள்ளது" "மீடியா ஆடியோ" "ஃபோன் அழைப்புகள்" - "கோப்பு இடமாற்றம்" + "ஃபைல் இடமாற்றம்" "உள்ளீட்டுச் சாதனம்" "இணைய அணுகல்" "தொடர்புப் பகிர்தல்" @@ -101,7 +101,7 @@ "ஃபைலைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது" "வரைபடத்துடன் இணைக்கப்பட்டது" "SAP உடன் இணைக்கப்பட்டது" - "கோப்பு இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" + "ஃபைல் இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" "உள்ளீட்டுச் சாதனத்துடன் இணைக்கப்பட்டது" "சாதனத்துடன் இணைந்தது" "சாதனத்துடன் உள்ளூர் இண்டர்நெட்டைப் பகிர்தல்" @@ -110,7 +110,7 @@ "சிம் அணுகலுக்குப் பயன்படுத்தும்" "மீடியாவின் ஆடியோவிற்குப் பயன்படுத்து" "மொபைல் ஆடியோவைப் பயன்படுத்து" - "கோப்பு பரிமாற்றத்திற்காகப் பயன்படுத்து" + "ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து" "உள்ளீட்டுக்குப் பயன்படுத்து" "செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்" "இணை" @@ -412,11 +412,11 @@ "WebView செயல்படுத்தல்" "WebView செயல்படுத்தலை அமை" "இனி இந்தத் தேர்வைப் பயன்படுத்த முடியாது. மீண்டும் முயலவும்." - "கோப்பு முறைமையாக்கத்திற்கு மாற்று" + "ஃபைல் முறைமையாக்கத்திற்கு மாற்று" "மாற்று…" - "ஏற்கனவே கோப்பு என்க்ரிப்ட் செய்யப்பட்டது" - "கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" - "தரவுப் பகிர்வை, கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." + "ஏற்கனவே ஃபைல் என்க்ரிப்ட் செய்யப்பட்டது" + "ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" + "தரவுப் பகிர்வை, ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." "அழித்து மாற்று…" "படத்தின் வண்ணப் பயன்முறை" "sRGBஐப் பயன்படுத்தும்" diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml index b1cba1a82e62..e0ba52006c66 100644 --- a/packages/SettingsLib/res/values-te/arrays.xml +++ b/packages/SettingsLib/res/values-te/arrays.xml @@ -25,7 +25,7 @@ "స్కాన్ చేస్తోంది…" "కనెక్ట్ చేస్తోంది..." "ప్రామాణీకరిస్తోంది…" - "IP చిరునామాను పొందుతోంది…" + "IP అడ్రస్‌ను పొందుతోంది…" "కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "డిస్‌కనెక్ట్ చేస్తోంది..." @@ -39,7 +39,7 @@ "స్కాన్ చేస్తోంది…" "%1$sకి కనెక్ట్ చేస్తోంది…" "%1$sతో ప్రామాణీకరిస్తోంది…" - "%1$s నుండి IP చిరునామాను పొందుతోంది…" + "%1$s నుండి IP అడ్రస్‌ను పొందుతోంది…" "%1$sకి కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "%1$s నుండి డిస్‌కనెక్ట్ చేస్తోంది…" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 6df6d2d00298..8903f1432f5a 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -26,7 +26,7 @@ "డిస్‌కనెక్ట్ అయ్యింది" "డిజేబుల్ చేయబడింది" "IP కాన్ఫిగరేషన్ వైఫల్యం" - "తక్కువ నాణ్యతా నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" + "తక్కువ క్వాలిటీ నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" "WiFi కనెక్షన్ వైఫల్యం" "ప్రామాణీకరణ సమస్య" "కనెక్ట్ చేయడం సాధ్యపడదు" -- cgit v1.2.3 From 053d431db787d7f3992d6c2dd800d3539041a0c8 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Mon, 16 Aug 2021 21:09:58 -0700 Subject: Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I05f55d7498bd29db20ae3cf2bc6a8b39a50a3b5c --- packages/SettingsLib/res/values-ar/strings.xml | 2 +- packages/SettingsLib/res/values-es-rUS/strings.xml | 2 +- packages/SettingsLib/res/values-et/strings.xml | 2 +- packages/SettingsLib/res/values-hi/strings.xml | 2 +- packages/SettingsLib/res/values-in/strings.xml | 2 +- packages/SettingsLib/res/values-iw/strings.xml | 2 +- packages/SettingsLib/res/values-mn/strings.xml | 2 +- packages/SettingsLib/res/values-mr/strings.xml | 2 +- packages/SettingsLib/res/values-sq/strings.xml | 2 +- packages/SettingsLib/res/values-ta/strings.xml | 18 +++++----- packages/SettingsLib/res/values-te/arrays.xml | 16 ++++----- packages/SettingsLib/res/values-te/strings.xml | 40 +++++++++++----------- 12 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 881b76bcf62d..e397035ea0ac 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -511,7 +511,7 @@ "المنبّهات والتذكيرات" "السماح بضبط المنبّهات والتذكيرات" "المنبّهات والتذكيرات" - "عليك السماح لهذا التطبيق بضبط المنبّهات وتحديد مواعيد للإجراءات الحساسة زمنيًا. يسمح هذا الأذن بتشغيل التطبيق في الخلفية، ما قد يستهلك المزيد من شحن البطارية.\n\nإذا كان هذا الإذن غير مسموح به، لن تعمل الأحداث المستندة إلى وقت والمنبّهات الحالية التي يحدِّد هذا التطبيق موعدها." + "يمكنك السماح لهذا التطبيق بضبط المنبّهات وجدولة الإجراءات لتنفيذها في الوقت المناسب. ويسمح هذا الإذن بتشغيل التطبيق في الخلفية، ما قد يستهلك المزيد من البطارية.\n\nفي حال عدم تفعيل هذا الإذن، لن تعمل المنبهات الحالية والأحداث المستندة إلى الوقت المضبوطة في هذا التطبيق." "جدول زمني، جدولة، منبّه، تذكير، ساعة" "تفعيل" "تفعيل ميزة \"عدم الإزعاج\"" diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 7755cb74b04f..048039f6af81 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -426,7 +426,7 @@ "Protanomalía (rojo-verde)" "Tritanomalía (azul-amarillo)" "Corrección de color" - "Ajusta cómo se muestran los colores en tu dispositivo. Esto puede ser útil cuando quieres:<br/><br/> <ol> <li> Ver colores con más exactitud</li> <li> Quitar colores para tener un enfoque más claro</li> </ol>" + "Ajusta cómo se muestran los colores en tu dispositivo. Esto puede ser útil cuando quieres:<br/><br/> <ol> <li>&nbsp;Ver colores con más exactitud</li> <li>&nbsp;Quitar colores para tener un enfoque más claro</li> </ol>" "Reemplazado por %1$s" "%1$s - %2$s" "Tiempo restante: aproximadamente %1$s" diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index d835cb95ed98..60469a1d2b17 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -507,7 +507,7 @@ "Alarmid ja meeldetuletused" "Luba alarmide ja meeldetuletuste määramine" "Alarmid ja meeldetuletused" - "Lubage sellel rakendusel määrata alarme ja ajastada kiire tähtajaga toiminguid. See võimaldab rakendusel töötada taustal, mistõttu võib akukasutus olla suurem.\n\nKui see luba on välja lülitatud, siis olemasolevad alarmid ja selle rakenduse ajastatud ajapõhised sündmused ei tööta." + "Lubage sellel rakendusel määrata alarme ja ajastada ajakriitilisi toiminguid. See võimaldab rakendusel töötada taustal, mistõttu võib akukasutus olla suurem.\n\nKui see luba on välja lülitatud, siis olemasolevad alarmid ja selle rakenduse ajastatud ajapõhised sündmused ei tööta." "ajakava, äratus, meeldetuletus, kell" "Lülita sisse" "Valiku Mitte segada sisselülitamine" diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index bf9b72e45a88..630000fe6921 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -527,7 +527,7 @@ "कनेक्ट करने में समस्या हो रही है. डिवाइस को बंद करके चालू करें" "वायर वाला ऑडियो डिवाइस" "सहायता और सुझाव" - "डिवाइस की मेमोरी" + "डिवाइस का स्टोरेज" "शेयर किया गया डेटा" "शेयर किए गए डेटा को देखें और उसमें बदलाव करें" "इस उपयोगकर्ता के साथ किसी तरह का डेटा शेयर नहीं किया गया है." diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index 4b37650563ac..c1403082e37b 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -507,7 +507,7 @@ "Alarm dan pengingat" "Izinkan menyetel alarm dan pengingat" "Alarm & pengingat" - "Izinkan aplikasi ini menyetel alarm dan menjadwalkan tindakan berjangka waktu. Hal ini memungkinkan aplikasi berjalan di latar belakang, sehingga mungkin menggunakan lebih banyak daya baterai.\n\nJika izin ini dinonaktifkan, alarm dan acara berjangka waktu yang dijadwalkan oleh aplikasi ini tidak akan berfungsi." + "Izinkan aplikasi ini menyetel alarm dan menjadwalkan tindakan yang sensitif waktu. Hal ini memungkinkan aplikasi berjalan di latar belakang, sehingga mungkin menggunakan lebih banyak daya baterai.\n\nJika izin ini dinonaktifkan, alarm dan acara berbasis waktu yang dijadwalkan oleh aplikasi ini tidak akan berfungsi." "jadwal, alarm, pengingat, jam" "Aktifkan" "Aktifkan mode Jangan Ganggu" diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 71cc9a031364..9f2d8a2d346e 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -426,7 +426,7 @@ "פרוטנומליה (אדום-ירוק)" "טריטנומליה (כחול-צהוב)" "תיקון צבע" - "‏ניתן לשנות את האופן שבו צבעים מוצגים במכשיר. שינוי כזה עשוי לעזור:<br/><br/> <ol> <li> להבחין בצבעים בצורה יותר מדויקת</li> <li> להסיר צבעים מסוימים כדי להתמקד</li> </ol>" + "‏ניתן לשנות את האופן שבו צבעים מוצגים במכשיר. שינוי כזה עשוי לעזור:<br/><br/> <ol> <li>&nbsp;להבחין בצבעים בצורה יותר מדויקת</li> <li>&nbsp;להסיר צבעים מסוימים כדי להתמקד</li> </ol>" "נעקף על ידי %1$s" "%1$s%2$s" "הזמן הנותר: בערך %1$s" diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 25769936ebfc..474ded9972c7 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -426,7 +426,7 @@ "Протаномаль (улаан-ногоон)" "Тританомаль (цэнхэр-шар)" "Өнгө тохируулах" - "Таны төхөөрөмж дээр өнгийг хэрхэн үзүүлэхийг тохируулна уу. Энэ нь танд дараахыг хийхийг хүссэн үед хэрэг болж магадгүй:<br/><br/> <ol> <li>Өнгийг илүү оновчтой харах</li> <li>Танд төвлөрөхөд туслахын тулд өнгийг хасах</li> </ol>" + "Таны төхөөрөмж дээр өнгийг хэрхэн үзүүлэхийг тохируулна уу. Энэ нь танд дараахыг хийхийг хүссэн үед хэрэг болж магадгүй:<br/><br/> <ol> <li>&nbsp;Өнгийг илүү оновчтой харах</li> <li>&nbsp;Танд төвлөрөхөд туслахын тулд өнгийг хасах</li> </ol>" "Давхарласан %1$s" "%1$s - %2$s" "Ойролцоогоор %1$s үлдсэн" diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 864f177fcec9..631cb3b9b628 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -307,7 +307,7 @@ "तुम्ही पूर्वी ऑथोराइझ केलेल्या सर्व संगणकांवरुन USB डीबग करण्यासाठी अ‍ॅक्सेस रीव्होक करायचा?" "विकास सेटिंग्जला अनुमती द्यायची?" "या सेटिंग्जचा हेतू फक्त विकास वापरासाठी आहे. त्यामुळे तुमचे डिव्हाइस आणि त्यावरील ॲप्लिकेशन ब्रेक होऊ शकतात किंवा नेहमीपेक्षा वेगळे वर्तन करू शकतात." - "USB वर अ‍ॅप्स पडताळून पाहा" + "USB वर अ‍ॅप्स पडताळून पहा" "हानिकारक वर्तनासाठी ADB/ADT द्वारे इंस्टॉल अ‍ॅप्स तपासा." "नावांशिवाय ब्‍लूटूथ डिव्‍हाइस (फक्‍त MAC पत्ते) दाखवले जातील" "रिमोट डिव्हाइसमध्ये सहन न होणारा मोठा आवाज किंवा नियंत्रणाचा अभाव यासारखी आवाजाची समस्या असल्यास ब्लूटूथ संपूर्ण आवाज वैशिष्ट्य बंद करते." diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index 78b35aad1b20..e39b4200b83b 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -507,7 +507,7 @@ "Alarmet dhe alarmet rikujtuese" "Lejo caktimin e alarmeve dhe alarmeve rikujtuese" "Alarmet dhe alarmet rikujtuese" - "Lejo që ky aplikacion të caktojë alarmet dhe të planifikojë veprime që kanë një afat të caktuar. Kjo lejon që aplikacioni të ekzekutohet në sfond, gjë që mund të përdorë më shumë bateri.\n\nNëse kjo leje është caktuar si joaktive, alarmet ekzistuese dhe ngjarjet me bazë kohore të planifikuara nga ky apliikacion nuk do të funksionojnë." + "Lejo që ky aplikacion të caktojë alarmet dhe të planifikojë veprime që kanë një afat të caktuar. Kjo mundëson që aplikacioni të ekzekutohet në sfond, gjë që mund të përdorë më shumë bateri.\n\nNëse kjo leje është caktuar si joaktive, alarmet ekzistuese dhe ngjarjet në bazë kohore të planifikuara nga ky aplikacion nuk do të funksionojnë." "planifiko, alarm, alarm rikujtues, ora" "Aktivizo" "Aktivizo \"Mos shqetëso\"" diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 6255d4132953..839853629569 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -84,7 +84,7 @@ "செயலில் உள்ளது" "மீடியா ஆடியோ" "ஃபோன் அழைப்புகள்" - "கோப்பு இடமாற்றம்" + "ஃபைல் இடமாற்றம்" "உள்ளீட்டுச் சாதனம்" "இணைய அணுகல்" "தொடர்புப் பகிர்தல்" @@ -98,10 +98,10 @@ "செவித்துணை கருவிகளுடன் இணைக்கப்பட்டது" "மீடியா ஆடியோவுடன் இணைக்கப்பட்டது" "மொபைல் ஆடியோவுடன் இணைக்கப்பட்டது" - "கோப்பைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது" + "ஃபைலைப் பரிமாற்றும் சேவையகத்துடன் இணைக்கப்பட்டது" "வரைபடத்துடன் இணைக்கப்பட்டது" "SAP உடன் இணைக்கப்பட்டது" - "கோப்பு இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" + "ஃபைல் இடமாற்றும் சேவையகத்துடன் இணைக்கப்படவில்லை" "உள்ளீட்டுச் சாதனத்துடன் இணைக்கப்பட்டது" "சாதனத்துடன் இணைந்தது" "சாதனத்துடன் உள்ளூர் இண்டர்நெட்டைப் பகிர்தல்" @@ -110,7 +110,7 @@ "சிம் அணுகலுக்குப் பயன்படுத்தும்" "மீடியாவின் ஆடியோவிற்குப் பயன்படுத்து" "மொபைல் ஆடியோவைப் பயன்படுத்து" - "கோப்பு பரிமாற்றத்திற்காகப் பயன்படுத்து" + "ஃபைல் பரிமாற்றத்திற்காகப் பயன்படுத்து" "உள்ளீட்டுக்குப் பயன்படுத்து" "செவித்துணை கருவிகளுக்குப் பயன்படுத்தவும்" "இணை" @@ -412,11 +412,11 @@ "WebView செயல்படுத்தல்" "WebView செயல்படுத்தலை அமை" "இனி இந்தத் தேர்வைப் பயன்படுத்த முடியாது. மீண்டும் முயலவும்." - "கோப்பு முறைமையாக்கத்திற்கு மாற்று" + "ஃபைல் முறைமையாக்கத்திற்கு மாற்று" "மாற்று…" - "ஏற்கனவே கோப்பு என்க்ரிப்ட் செய்யப்பட்டது" - "கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" - "தரவுப் பகிர்வை, கோப்பு சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." + "ஏற்கனவே ஃபைல் என்க்ரிப்ட் செய்யப்பட்டது" + "ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றுதல்" + "தரவுப் பகிர்வை, ஃபைல் சார்ந்த முறைமையாக்கத்திற்கு மாற்றவும்.\n !!எச்சரிக்கை!! இது எல்லா தரவையும் அழிக்கும்.\n இது ஆல்பா நிலை அம்சமாக இருப்பதால் சரியாகச் செயல்படாமல் போகக்கூடும்.\n தொடர, \'அழித்து, மாற்று…\' என்பதை அழுத்தவும்." "அழித்து மாற்று…" "படத்தின் வண்ணப் பயன்முறை" "sRGBஐப் பயன்படுத்தும்" @@ -426,7 +426,7 @@ "நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)" "நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)" "வண்ணத்திருத்தம்" - "சாதனத்தில் வண்ணங்கள் காண்பிக்கப்படும் விதத்தைச் சரிசெய்யலாம். இதன் மூலம் நீங்கள் விரும்பும்போதெல்லாம்:<br/><br/> <ol> <li>&nbsp;வண்ணங்களை மிகத் தெளிவாகப் பார்க்கலாம்</li> <li>கவனம் சிதறாமல் இருக்க வண்ணங்களை நீக்கலாம்</li> </ol>" + "சாதனத்தில் வண்ணங்கள் காண்பிக்கப்படும் விதத்தைச் சரிசெய்யலாம். இதன் மூலம் நீங்கள் விரும்பும்போதெல்லாம்:<br/><br/> <ol> <li>&nbsp;வண்ணங்களை மிகத் தெளிவாகப் பார்க்கலாம்</li> <li>&nbsp;கவனம் சிதறாமல் இருக்க வண்ணங்களை நீக்கலாம்</li> </ol>" "%1$s மூலம் மேலெழுதப்பட்டது" "%1$s - %2$s" "கிட்டத்தட்ட %1$s மீதமுள்ளது" diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml index 99efbde3a90a..e0ba52006c66 100644 --- a/packages/SettingsLib/res/values-te/arrays.xml +++ b/packages/SettingsLib/res/values-te/arrays.xml @@ -25,7 +25,7 @@ "స్కాన్ చేస్తోంది…" "కనెక్ట్ చేస్తోంది..." "ప్రామాణీకరిస్తోంది…" - "IP చిరునామాను పొందుతోంది…" + "IP అడ్రస్‌ను పొందుతోంది…" "కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "డిస్‌కనెక్ట్ చేస్తోంది..." @@ -39,7 +39,7 @@ "స్కాన్ చేస్తోంది…" "%1$sకి కనెక్ట్ చేస్తోంది…" "%1$sతో ప్రామాణీకరిస్తోంది…" - "%1$s నుండి IP చిరునామాను పొందుతోంది…" + "%1$s నుండి IP అడ్రస్‌ను పొందుతోంది…" "%1$sకి కనెక్ట్ చేయబడింది" "తాత్కాలికంగా రద్దు చేయబడింది" "%1$s నుండి డిస్‌కనెక్ట్ చేస్తోంది…" @@ -138,15 +138,15 @@ "స్టీరియో" - "ఆడియో నాణ్యత (990kbps/909kbps) కోసం అనుకూలీకరించబడింది" - "సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత (660kbps/606kbps)" - "కనెక్షన్ నాణ్యత (330kbps/303kbps) కోసం అనుకూలీకరించబడింది" + "ఆడియో క్వాలిటీ (990kbps/909kbps) కోసం అనుకూలీకరించబడింది" + "సమతుల్య ఆడియో మరియు కనెక్షన్ క్వాలిటీ (660kbps/606kbps)" + "కనెక్షన్ క్వాలిటీ (330kbps/303kbps) కోసం అనుకూలీకరించబడింది" "ఉత్తమ కృషి (అనుకూల బిట్ రేట్)" - "ఆడియో నాణ్యత కోసం అనుకూలీకరించబడింది" - "సమతుల్య ఆడియో మరియు కనెక్షన్ నాణ్యత" - "కనెక్షన్ నాణ్యత కోసం అనుకూలీకరించబడింది" + "ఆడియో క్వాలిటీ కోసం అనుకూలీకరించబడింది" + "సమతుల్య ఆడియో మరియు కనెక్షన్ క్వాలిటీ" + "కనెక్షన్ క్వాలిటీ కోసం అనుకూలీకరించబడింది" "ఉత్తమ కృషి (అనుకూల బిట్ రేట్)" diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index 4f74d2492d43..8903f1432f5a 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -26,19 +26,19 @@ "డిస్‌కనెక్ట్ అయ్యింది" "డిజేబుల్ చేయబడింది" "IP కాన్ఫిగరేషన్ వైఫల్యం" - "తక్కువ నాణ్యతా నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" + "తక్కువ క్వాలిటీ నెట్‌వర్క్ కారణంగా కనెక్ట్ చేయబడలేదు" "WiFi కనెక్షన్ వైఫల్యం" "ప్రామాణీకరణ సమస్య" "కనెక్ట్ చేయడం సాధ్యపడదు" "\'%1$s\'కు కనెక్ట్ చేయడం సాధ్యపడదు" "పాస్‌వర్డ్‌ను చెక్ చేసి, మళ్లీ ప్రయత్నించండి" "పరిధిలో లేదు" - "స్వయంచాలకంగా కనెక్ట్ కాదు" + "ఆటోమేటిక్‌గా కనెక్ట్ కాదు" "ఇంటర్నెట్ యాక్సెస్ లేదు" "%1$s ద్వారా సేవ్ చేయబడింది" "డేటా నియంత్రణ నెట్‌వర్క్‌కు కనెక్ట్ చేయబడింది" - "%1$s ద్వారా స్వయంచాలకంగా కనెక్ట్ చేయబడింది" - "నెట్‌వర్క్ రేటింగ్ ప్రదాత ద్వారా స్వయంచాలకంగా కనెక్ట్ చేయబడింది" + "%1$s ద్వారా ఆటోమేటిక్‌గా కనెక్ట్ చేయబడింది" + "నెట్‌వర్క్ రేటింగ్ ప్రదాత ద్వారా ఆటోమేటిక్‌గా కనెక్ట్ చేయబడింది" "%1$s ద్వారా కనెక్ట్ చేయబడింది" "%1$s ద్వారా కనెక్ట్ చేయబడింది" "%1$s ద్వారా అందుబాటులో ఉంది" @@ -83,14 +83,14 @@ "L: %1$s బ్యాటరీ, R: %2$s బ్యాటరీ" "యాక్టివ్‌గా ఉంది" "మీడియా ఆడియో" - "ఫోన్ కాల్‌లు" + "ఫోన్ కాల్స్‌" "ఫైల్ బదిలీ" "ఇన్‌పుట్ పరికరం" "ఇంటర్నెట్ యాక్సెస్" "కాంటాక్ట్ షేరింగ్" "పరిచయ భాగస్వామ్యం కోసం ఉపయోగించు" "ఇంటర్నెట్ కనెక్షన్ భాగస్వామ్యం" - "వచన సందేశాలు" + "వచన మెసేజ్‌లు" "SIM యాక్సెస్" "HD ఆడియో: %1$s" "HD ఆడియో" @@ -116,7 +116,7 @@ "జత చేయి" "జత చేయి" "రద్దు చేయి" - "జత చేయడం వలన కనెక్ట్ చేయబడినప్పుడు మీ పరిచయాలకు మరియు కాల్ చరిత్రకు ప్రాప్యతను మంజూరు చేస్తుంది." + "జత చేయడం వలన కనెక్ట్ చేయబడినప్పుడు మీ పరిచయాలకు మరియు కాల్ చరిత్రకు యాక్సెస్‌ను మంజూరు చేస్తుంది." "%1$sతో జత చేయడం సాధ్యపడలేదు." "పిన్ లేదా పాస్‌కీ చెల్లని కారణంగా %1$sతో పెయిర్ చేయడం సాధ్యపడలేదు." "%1$sతో కమ్యూనికేట్ చేయడం సాధ్యపడదు." @@ -199,7 +199,7 @@ "ఆఫీస్" "డెవలపర్ ఆప్షన్‌లు" "డెవలపర్ ఎంపికలను ప్రారంభించండి" - "అనువర్తన అభివృద్ధి కోసం ఎంపికలను సెట్ చేయండి" + "యాప్‌ అభివృద్ధి కోసం ఎంపికలను సెట్ చేయండి" "ఈ వినియోగదారు కోసం డెవలపర్ ఎంపికలు అందుబాటులో లేవు" "VPN సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు" "టీథరింగ్ సెట్టింగ్‌లు ఈ వినియోగదారుకి అందుబాటులో లేవు" @@ -227,12 +227,12 @@ "Wi‑Fi పెయిరింగ్ కోడ్" "పెయిరింగ్ విఫలమైంది" "పరికరం అదే నెట్‌వర్క్‌కు కనెక్ట్ చేయబడి ఉందని నిర్ధారించుకోండి." - "QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి" + "QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చేయండి" "పరికరం పెయిర్ చేయబడుతోంది…" "పరికరాన్ని పెయిర్ చేయడం విఫలమైంది. QR కోడ్ తప్పుగా ఉండడం గాని, లేదా పరికరం అదే నెట్‌వర్క్‌కు కనెక్ట్ అయి లేకపోవడం గాని జరిగింది." "IP అడ్రస్ & పోర్ట్" "QR కోడ్‌ను స్కాన్ చేయండి" - "QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చెయ్యండి" + "QR కోడ్‌ను స్కాన్ చేయడం ద్వారా Wi-Fiని ఉపయోగించి పరికరాన్ని పెయిర్ చేయండి" "దయచేసి Wi-Fi నెట్‌వర్క్‌కు కనెక్ట్ చేయండి" "adb, డీబగ్, dev" "బగ్ రిపోర్ట్ షార్ట్‌కట్" @@ -271,8 +271,8 @@ "బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్‌లు" "బ్లూటూత్ ఆడియో ఛానెల్ మోడ్" "బ్లూటూత్ ఆడియో కోడెక్‌ని సక్రియం చేయండి\nఎంపిక: ఛానెల్ మోడ్" - "బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ నాణ్యత" - "బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ నాణ్యత" + "బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ క్వాలిటీ" + "బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ క్వాలిటీ" "ప్రసారం చేస్తోంది: %1$s" "ప్రైవేట్ DNS" "ప్రైవేట్ DNS మోడ్‌ను ఎంచుకోండి" @@ -301,10 +301,10 @@ "ఎల్లప్పుడూ మొబైల్ డేటాను యాక్టివ్‌గా ఉంచు, Wi‑Fi యాక్టివ్‌గా ఉన్నా కూడా (వేగవంతమైన నెట్‌వర్క్ మార్పు కోసం)." "అందుబాటులో ఉంటే టెథెరింగ్ హార్డ్‌వేర్ వేగవృద్ధిని ఉపయోగించండి" "USB డీబగ్గింగ్‌ను అనుమతించాలా?" - "USB డీబగ్గింగ్ అనేది అభివృద్ధి ప్రయోజనాల కోసం మాత్రమే ఉద్దేశించబడింది. మీ కంప్యూటర్ మరియు మీ పరికరం మధ్య డేటాను కాపీ చేయడానికి, నోటిఫికేషన్ లేకుండా మీ పరికరంలో అనువర్తనాలను ఇన్‌స్టాల్ చేయడానికి మరియు లాగ్ డేటాను చదవడానికి దీన్ని ఉపయోగించండి." + "USB డీబగ్గింగ్ అనేది అభివృద్ధి ప్రయోజనాల కోసం మాత్రమే ఉద్దేశించబడింది. మీ కంప్యూటర్ మరియు మీ పరికరం మధ్య డేటాను కాపీ చేయడానికి, నోటిఫికేషన్ లేకుండా మీ పరికరంలో యాప్‌లను ఇన్‌స్టాల్ చేయడానికి మరియు లాగ్ డేటాను చదవడానికి దీన్ని ఉపయోగించండి." "వైర్‌లెస్ డీబగ్గింగ్‌ను అనుమతించాలా?" "వైర్‌లెస్ డీబగ్గింగ్ అనేది అభివృద్ధి ప్రయోజనాల కోసం మాత్రమే ఉద్దేశించబడింది. మీ కంప్యూటర్, పరికరాల మధ్య డేటాను కాపీ చేయడానికి, నోటిఫికేషన్ లేకుండా మీ పరికరంలో యాప్‌లను ఇన్‌స్టాల్ చేయడానికి, లాగ్ డేటాను చదవడానికి దీన్ని ఉపయోగించండి." - "మీరు గతంలో ప్రామాణీకరించిన అన్ని కంప్యూటర్‌ల నుండి USB డీబగ్గింగ్‌కు ప్రాప్యతను ఉపసంహరించాలా?" + "మీరు గతంలో ప్రామాణీకరించిన అన్ని కంప్యూటర్‌ల నుండి USB డీబగ్గింగ్‌కు యాక్సెస్‌ను ఉపసంహరించాలా?" "అభివృద్ధి సెట్టింగ్‌లను అనుమతించాలా?" "ఈ సెట్టింగ్‌లు అభివృద్ధి వినియోగం కోసం మాత్రమే ఉద్దేశించబడినవి. వీటి వలన మీ పరికరం మరియు దీనిలోని యాప్‌లు విచ్ఛిన్నం కావచ్చు లేదా తప్పుగా ప్రవర్తించవచ్చు." "USB ద్వారా యాప్‌లను వెరిఫై చేయి" @@ -314,7 +314,7 @@ "బ్లూటూత్ Gabeldorsche ఫీచర్ స్ట్యాక్‌ను ఎనేబుల్ చేస్తుంది." "మెరుగైన కనెక్టివిటీ ఫీచర్‌ను ఎనేబుల్ చేస్తుంది." "స్థానిక టెర్మినల్" - "స్థానిక షెల్ ప్రాప్యతను అందించే టెర్మినల్ యాప్‌ను ప్రారంభించు" + "స్థానిక షెల్ యాక్సెస్‌ను అందించే టెర్మినల్ యాప్‌ను ప్రారంభించు" "HDCP చెకింగ్‌" "HDCP తనిఖీ ప్రవర్తనను సెట్ చేయండి" "డీబగ్గింగ్" @@ -383,7 +383,7 @@ "డెస్క్‌టాప్ బ్యాకప్ పాస్‌వర్డ్" "డెస్క్‌టాప్ పూర్తి బ్యాకప్‌లు ప్రస్తుతం రక్షించబడలేదు" "డెస్క్‌టాప్ పూర్తి బ్యాకప్‌ల కోసం పాస్‌వర్డ్‌ను మార్చడానికి లేదా తీసివేయడానికి నొక్కండి" - "కొత్త బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేసారు" + "కొత్త బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేశారు" "కొత్త పాస్‌వర్డ్ మరియు నిర్ధారణ సరిపోలడం లేదు" "బ్యాకప్ పాస్‌వర్డ్‌ను సెట్ చేయడంలో వైఫల్యం" "లోడ్ చేస్తోంది…" @@ -408,7 +408,7 @@ "ట్రాన్స్‌కోడింగ్ నోటిఫికేషన్‌లను చూపండి" "ట్రాన్స్‌కోడింగ్ కాష్‌ను డిజేబుల్ చేయండి" "అమలులో ఉన్న సర్వీస్‌లు" - "ప్రస్తుతం అమలులో ఉన్న సర్వీస్‌లను వీక్షించండి, కంట్రోల్‌ చేయండి" + "ప్రస్తుతం అమలులో ఉన్న సర్వీస్‌లను చూడండి, కంట్రోల్‌ చేయండి" "వెబ్ వీక్షణ అమలు" "వెబ్ వీక్షణ అమలుని సెట్ చేయండి" "ఈ ఎంపిక ఇప్పుడు లేదు. మళ్లీ ప్రయత్నించండి." @@ -529,7 +529,7 @@ "సహాయం & ఫీడ్‌బ్యాక్" "స్టోరేజ్" "షేర్ చేసిన డేటా" - "షేర్ చేసిన డేటాను చూసి, సవరించండి" + "షేర్ చేసిన డేటాను చూసి, ఎడిట్ చేయండి" "ఈ యూజర్ కోసం షేర్ చేసిన డేటా ఏదీ లేదు." "షేర్ చేసిన డేటా పొందడంలో ఎర్రర్ ఏర్పడింది. మళ్లీ ట్రై చేయండి." "షేర్ చేసిన డేటా ID: %d" @@ -541,8 +541,8 @@ "లీజు గడువు %sతో ముగుస్తుంది" "షేర్ చేసిన డేటాను తొలగించు" "మీరు ఖచ్చితంగా ఈ షేర్ చేసిన డేటాను తొలగించాలనుకుంటున్నారా?" - "వినియోగదారులు వారి స్వంత అనువర్తనాలను మరియు కంటెంట్‌ను కలిగి ఉన్నారు" - "మీరు మీ ఖాతా నుండి అనువర్తనాలకు మరియు కంటెంట్‌కు ప్రాప్యతను పరిమితం చేయవచ్చు" + "వినియోగదారులు వారి స్వంత యాప్‌లను మరియు కంటెంట్‌ను కలిగి ఉన్నారు" + "మీరు మీ ఖాతా నుండి యాప్‌లకు మరియు కంటెంట్‌కు యాక్సెస్‌ను పరిమితం చేయవచ్చు" "యూజర్" "పరిమితం చేయబడిన ప్రొఫైల్" "కొత్త వినియోగదారుని జోడించాలా?" -- cgit v1.2.3 From d56251bae4a305f5668adce8bfbfa1b31b8dc5e3 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Fri, 2 Jul 2021 17:05:03 +0800 Subject: Fix lint error for those module used in mainline module Bug: 187861723 Test: rebuild Change-Id: I3a8c9e00f164d0f0dd381b188ecfd0c4a758a277 Merged-In: I3a8c9e00f164d0f0dd381b188ecfd0c4a758a277 --- packages/SettingsLib/ActionBarShadow/Android.bp | 2 +- .../SettingsLib/ActionBarShadow/lint-baseline.xml | 37 ---------- .../ActionButtonsPreference/lint-baseline.xml | 81 ---------------------- .../res/drawable/half_rounded_left_bk.xml | 2 + .../res/drawable/half_rounded_right_bk.xml | 28 ++++---- .../res/drawable/rounded_bk.xml | 28 ++++---- .../ActionButtonsPreference/res/values/styles.xml | 4 +- packages/SettingsLib/BarChartPreference/Android.bp | 2 +- .../BarChartPreference/lint-baseline.xml | 15 ---- .../BarChartPreference/res/values/styles.xml | 4 +- packages/SettingsLib/HelpUtils/lint-baseline.xml | 15 ---- .../src/com/android/settingslib/HelpUtils.java | 28 +++++++- packages/SettingsLib/ProgressBar/lint-baseline.xml | 15 ---- .../ProgressBar/res/layout/progress_header.xml | 2 + .../RestrictedLockUtils/lint-baseline.xml | 26 ------- .../android/settingslib/RestrictedLockUtils.java | 15 ++++ .../SettingsLib/SettingsSpinner/lint-baseline.xml | 15 ---- .../widget/settingsspinner/SettingsSpinner.java | 4 ++ 18 files changed, 88 insertions(+), 235 deletions(-) delete mode 100644 packages/SettingsLib/ActionBarShadow/lint-baseline.xml delete mode 100644 packages/SettingsLib/ActionButtonsPreference/lint-baseline.xml delete mode 100644 packages/SettingsLib/BarChartPreference/lint-baseline.xml delete mode 100644 packages/SettingsLib/HelpUtils/lint-baseline.xml delete mode 100644 packages/SettingsLib/ProgressBar/lint-baseline.xml delete mode 100644 packages/SettingsLib/RestrictedLockUtils/lint-baseline.xml delete mode 100644 packages/SettingsLib/SettingsSpinner/lint-baseline.xml diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp index 800ab671cedb..4a07d49fcde5 100644 --- a/packages/SettingsLib/ActionBarShadow/Android.bp +++ b/packages/SettingsLib/ActionBarShadow/Android.bp @@ -19,5 +19,5 @@ android_library { ], sdk_version: "system_current", - min_sdk_version: "21", + min_sdk_version: "28", } diff --git a/packages/SettingsLib/ActionBarShadow/lint-baseline.xml b/packages/SettingsLib/ActionBarShadow/lint-baseline.xml deleted file mode 100644 index 4d5de5f46894..000000000000 --- a/packages/SettingsLib/ActionBarShadow/lint-baseline.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/packages/SettingsLib/ActionButtonsPreference/lint-baseline.xml b/packages/SettingsLib/ActionButtonsPreference/lint-baseline.xml deleted file mode 100644 index 95b7e3b8033d..000000000000 --- a/packages/SettingsLib/ActionButtonsPreference/lint-baseline.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml index 16a85d694bf8..8a25726ecd17 100644 --- a/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml +++ b/packages/SettingsLib/ActionButtonsPreference/res/drawable/half_rounded_left_bk.xml @@ -17,6 +17,8 @@ - + + + + + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 76f30a80114a..00b33a416df4 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -65,6 +65,7 @@ import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.qs.ReduceBrightColorsController; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.screenrecord.RecordingController; @@ -364,6 +365,7 @@ public class Dependency { @Inject Lazy mUiEventLogger; @Inject Lazy mFeatureFlagsLazy; @Inject Lazy mContentInsetsProviderLazy; + @Inject Lazy mInternetDialogFactory; @Inject public Dependency() { @@ -578,6 +580,7 @@ public class Dependency { mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); mProviders.put(EdgeBackGestureHandler.Factory.class, mEdgeBackGestureHandlerFactoryLazy::get); + mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get); mProviders.put(UiEventLogger.class, mUiEventLogger::get); mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get); mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 41a3fb0211a7..65e93c6f31a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -28,6 +28,7 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.Html; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; import android.widget.Switch; @@ -51,6 +52,7 @@ import com.android.systemui.qs.AlphaControlledSignalTileView; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.IconState; import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators; @@ -75,6 +77,8 @@ public class InternetTile extends QSTileImpl { private int mLastTileState = -1; protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback(); + private final InternetDialogFactory mInternetDialogFactory; + final Handler mHandler; @Inject public InternetTile( @@ -86,10 +90,13 @@ public class InternetTile extends QSTileImpl { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - NetworkController networkController + NetworkController networkController, + InternetDialogFactory internetDialogFactory ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); + mInternetDialogFactory = internetDialogFactory; + mHandler = mainHandler; mController = networkController; mDataController = mController.getMobileDataController(); mController.observe(getLifecycle(), mSignalCallback); @@ -114,7 +121,13 @@ public class InternetTile extends QSTileImpl { @Override protected void handleClick(@Nullable View view) { - mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0); + if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0); + } else { + mHandler.post(() -> { + mInternetDialogFactory.create(true); + }); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java new file mode 100644 index 000000000000..9ab6d47d6af1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.dialog; + +import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE; +import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE; + +import android.annotation.ColorInt; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settingslib.Utils; +import com.android.systemui.R; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * Adapter for showing Wi-Fi networks. + */ +public class InternetAdapter extends RecyclerView.Adapter { + + private static final String TAG = "InternetAdapter"; + private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG"; + private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key"; + private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final InternetDialogController mInternetDialogController; + + protected View mHolderView; + protected Context mContext; + + public InternetAdapter(InternetDialogController controller) { + mInternetDialogController = controller; + } + + @Override + public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, + int viewType) { + mContext = viewGroup.getContext(); + mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item, + viewGroup, false); + return new InternetViewHolder(mHolderView, mInternetDialogController); + } + + @Override + public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) { + List wifiList = getWifiEntryList(); + if (wifiList != null && wifiList.size() != 0) { + int count = getItemCount(); + if (wifiList.size() > count) { + wifiList = getWifiEntryList().subList(0, count - 1); + } + + if (position < wifiList.size()) { + viewHolder.onBind(wifiList.get(position)); + } + } else if (DEBUG) { + Log.d(TAG, "onBindViewHolder, Wi-Fi entry list = null"); + } + } + + private List getWifiEntryList() { + if (mInternetDialogController.getWifiEntryList() == null) { + return null; + } + + return mInternetDialogController.getWifiEntryList().stream() + .filter(wifiAp -> wifiAp.getConnectedState() + != WifiEntry.CONNECTED_STATE_CONNECTED) + .limit(getItemCount()) + .collect(Collectors.toList()); + } + + /** + * The total number of networks (mobile network and entries of Wi-Fi) should be four in + * {@link InternetDialog}. + * + * Airplane mode is ON (mobile network is gone): + * Return four Wi-Fi's entries if no connected Wi-Fi. + * Return three Wi-Fi's entries if one connected Wi-Fi. + * Airplane mode is OFF (mobile network is visible): + * Return three Wi-Fi's entries if no connected Wi-Fi. + * Return two Wi-Fi's entries if one connected Wi-Fi. + * + * @return The total number of networks. + */ + @Override + public int getItemCount() { + boolean hasConnectedWifi = mInternetDialogController.getConnectedWifiEntry() != null; + if (mInternetDialogController.isAirplaneModeEnabled()) { + return hasConnectedWifi ? 3 : 4; + } else { + return hasConnectedWifi ? 2 : 3; + } + } + + /** + * ViewHolder for binding Wi-Fi view. + */ + static class InternetViewHolder extends RecyclerView.ViewHolder { + + final LinearLayout mContainerLayout; + final LinearLayout mWifiListLayout; + final LinearLayout mWifiNetworkLayout; + final ImageView mWifiIcon; + final TextView mWifiTitleText; + final TextView mWifiSummaryText; + final ImageView mWifiLockedIcon; + final Context mContext; + final InternetDialogController mInternetDialogController; + + InternetViewHolder(View view, InternetDialogController internetDialogController) { + super(view); + mContext = view.getContext(); + mInternetDialogController = internetDialogController; + mContainerLayout = view.requireViewById(R.id.internet_container); + mWifiListLayout = view.requireViewById(R.id.wifi_list); + mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout); + mWifiIcon = view.requireViewById(R.id.wifi_icon); + mWifiTitleText = view.requireViewById(R.id.wifi_title); + mWifiSummaryText = view.requireViewById(R.id.wifi_summary); + mWifiLockedIcon = view.requireViewById(R.id.wifi_locked_icon); + } + + void onBind(WifiEntry wifiEntry) { + int security = wifiEntry.getSecurity(); + try { + mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry)); + if (isOpenNetwork(security)) { + mWifiLockedIcon.setVisibility(View.GONE); + } else { + mWifiLockedIcon.setVisibility(View.VISIBLE); + mWifiLockedIcon.setImageDrawable( + mContext.getDrawable(R.drawable.ic_friction_lock_closed)); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + setWifiNetworkLayout(wifiEntry.getTitle(), + Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY)); + + mWifiListLayout.setOnClickListener(v -> { + if (!isOpenNetwork(security)) { + // Popup Wi-Fi password dialog condition: + // 1. The access point is a non-open network. + // 2. The Wi-Fi connection is not connected with this access point. + final Intent intent = new Intent(ACTION_WIFI_DIALOG); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey()); + intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false); + mContext.startActivity(intent); + } + mInternetDialogController.connect(wifiEntry); + }); + } + + /** Return true if this is an open network AccessPoint. */ + boolean isOpenNetwork(int security) { + return security == SECURITY_NONE + || security == SECURITY_OWE; + } + + void setWifiNetworkLayout(CharSequence title, CharSequence summary) { + mWifiNetworkLayout.setVisibility(View.VISIBLE); + mWifiTitleText.setText(title); + if (TextUtils.isEmpty(summary)) { + mWifiTitleText.setGravity(Gravity.CENTER); + mWifiSummaryText.setVisibility(View.GONE); + return; + } else { + mWifiTitleText.setGravity(Gravity.BOTTOM); + mWifiSummaryText.setGravity(Gravity.TOP); + mWifiSummaryText.setVisibility(View.VISIBLE); + } + mWifiSummaryText.setText(summary); + } + + Drawable getWifiDrawable(WifiEntry wifiEntry) throws Throwable { + Drawable drawable = mContext.getDrawable( + com.android.internal.R.drawable.ic_wifi_signal_0); + + AtomicReference shared = new AtomicReference<>(); + final @ColorInt int tint = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.colorControlNormal); + Drawable signalDrawable = mContext.getDrawable( + Utils.getWifiIconResource(wifiEntry.getLevel())); + signalDrawable.setTint(tint); + shared.set(signalDrawable); + drawable = shared.get(); + return drawable; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java new file mode 100644 index 000000000000..64809f3c9475 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.qs.tiles.dialog; + +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; + +import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyDisplayInfo; +import android.telephony.TelephonyManager; +import android.text.Html; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.Space; +import android.widget.Switch; +import android.widget.TextView; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.settingslib.Utils; +import com.android.systemui.Prefs; +import com.android.systemui.R; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.wifitrackerlib.WifiEntry; + +import java.util.List; + +import androidx.annotation.VisibleForTesting; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks. + */ +@SysUISingleton +public class InternetDialog extends SystemUIDialog implements + InternetDialogController.InternetDialogCallback, Window.Callback { + private static final String TAG = "InternetDialog"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final Handler mHandler; + private final LinearLayoutManager mLayoutManager; + private final Runnable mHideProgressBarRunnable = () -> { + setProgressBarVisible(false); + }; + + @VisibleForTesting + protected InternetAdapter mAdapter; + @VisibleForTesting + protected WifiManager mWifiManager; + @VisibleForTesting + protected View mDialogView; + + private InternetDialogFactory mInternetDialogFactory; + private SubscriptionManager mSubscriptionManager; + private TelephonyManager mTelephonyManager; + private AlertDialog mAlertDialog; + private UiEventLogger mUiEventLogger; + private Context mContext; + private InternetDialogController mInternetDialogController; + private TextView mInternetDialogTitle; + private TextView mInternetDialogSubTitle; + private ProgressBar mProgressBar; + private LinearLayout mInternetListLayout; + private LinearLayout mConnectedWifListLayout; + private LinearLayout mConnectedWifList; + private LinearLayout mMobileNetworkLayout; + private LinearLayout mMobileNetworkList; + private LinearLayout mTurnWifiOnLayout; + private LinearLayout mSeeAllLayout; + private Space mSpace; + private RecyclerView mWifiRecyclerView; + private ImageView mConnectedWifiIcon; + private ImageView mWifiSettingsIcon; + private TextView mConnectedWifiTitleText; + private TextView mConnectedWifiSummaryText; + private ImageView mSignalIcon; + private TextView mMobileTitleText; + private TextView mMobileSummaryText; + private Switch mMobileDataToggle; + private Switch mWiFiToggle; + private Button mDoneButton; + private Drawable mBackgroundOn; + private WifiEntry mConnectedWifiEntry; + private int mListMaxHeight; + private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private boolean mIsProgressBarVisible; + + private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> { + // Set max height for list + if (mInternetListLayout.getHeight() > mListMaxHeight) { + ViewGroup.LayoutParams params = mInternetListLayout.getLayoutParams(); + params.height = mListMaxHeight; + mInternetListLayout.setLayoutParams(params); + } + }; + + public InternetDialog(Context context, InternetDialogFactory internetDialogFactory, + InternetDialogController internetDialogController, + boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler) { + super(context, R.style.Theme_SystemUI_Dialog_Internet); + if (DEBUG) { + Log.d(TAG, "Init InternetDialog"); + } + mContext = context; + mHandler = handler; + mInternetDialogFactory = internetDialogFactory; + mInternetDialogController = internetDialogController; + mSubscriptionManager = mInternetDialogController.getSubscriptionManager(); + mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId(); + mTelephonyManager = mInternetDialogController.getTelephonyManager(); + mWifiManager = mInternetDialogController.getWifiManager(); + + mLayoutManager = new LinearLayoutManager(mContext) { + @Override + public boolean canScrollVertically() { + return false; + } + }; + mListMaxHeight = context.getResources().getDimensionPixelSize( + R.dimen.internet_dialog_list_max_height); + mUiEventLogger = uiEventLogger; + mAdapter = new InternetAdapter(mInternetDialogController); + if (!aboveStatusBar) { + getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); + } + show(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (DEBUG) { + Log.d(TAG, "onCreate"); + } + mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW); + mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, + null); + final Window window = getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); + lp.gravity = Gravity.BOTTOM; + lp.setFitInsetsTypes(statusBars() | navigationBars()); + lp.setFitInsetsSides(WindowInsets.Side.all()); + lp.setFitInsetsIgnoringVisibility(true); + window.setAttributes(lp); + window.setContentView(mDialogView); + window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + window.setWindowAnimations(R.style.Animation_InternetDialog); + window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + + mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); + mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); + mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress); + mInternetListLayout = mDialogView.requireViewById(R.id.internet_list); + mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout); + mMobileNetworkList = mDialogView.requireViewById(R.id.mobile_network_list); + mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout); + mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout); + mConnectedWifList = mDialogView.requireViewById(R.id.wifi_connected_list); + mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon); + mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title); + mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary); + mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon); + mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout); + mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout); + mSpace = mDialogView.requireViewById(R.id.space); + mDoneButton = mDialogView.requireViewById(R.id.done); + mSignalIcon = mDialogView.requireViewById(R.id.signal_icon); + mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title); + mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary); + mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle); + mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle); + mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on); + mInternetListLayout.getViewTreeObserver().addOnGlobalLayoutListener( + mInternetListLayoutListener); + mInternetDialogTitle.setText(getDialogTitleText()); + mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL); + + setOnClickListener(); + mTurnWifiOnLayout.setBackground(null); + mWifiRecyclerView.setLayoutManager(mLayoutManager); + mWifiRecyclerView.setAdapter(mAdapter); + } + + @Override + public void onStart() { + super.onStart(); + if (DEBUG) { + Log.d(TAG, "onStart"); + } + mInternetDialogController.onStart(this); + } + + @Override + public void onStop() { + super.onStop(); + if (DEBUG) { + Log.d(TAG, "onStop"); + } + mHandler.removeCallbacks(mHideProgressBarRunnable); + mMobileNetworkLayout.setOnClickListener(null); + mMobileDataToggle.setOnCheckedChangeListener(null); + mConnectedWifListLayout.setOnClickListener(null); + mSeeAllLayout.setOnClickListener(null); + mWiFiToggle.setOnCheckedChangeListener(null); + mDoneButton.setOnClickListener(null); + mInternetDialogController.onStop(); + mInternetDialogFactory.destroyDialog(); + } + + @Override + public void dismissDialog() { + if (DEBUG) { + Log.d(TAG, "dismissDialog"); + } + mInternetDialogFactory.destroyDialog(); + dismiss(); + } + + void updateDialog() { + if (DEBUG) { + Log.d(TAG, "updateDialog"); + } + if (mInternetDialogController.isAirplaneModeEnabled()) { + mInternetDialogSubTitle.setVisibility(View.GONE); + } else { + mInternetDialogSubTitle.setText(getSubtitleText()); + } + showProgressBar(); + setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()); + setConnectedWifiLayout(); + boolean isWifiEnabled = mWifiManager.isWifiEnabled(); + mWiFiToggle.setChecked(isWifiEnabled); + int visible = isWifiEnabled ? View.VISIBLE : View.GONE; + mWifiRecyclerView.setVisibility(visible); + mAdapter.notifyDataSetChanged(); + mSeeAllLayout.setVisibility(visible); + mSpace.setVisibility(isWifiEnabled ? View.GONE : View.VISIBLE); + } + + private void setOnClickListener() { + mMobileNetworkLayout.setOnClickListener(v -> { + if (mInternetDialogController.isMobileDataEnabled()) { + if (!mInternetDialogController.activeNetworkIsCellular()) { + mInternetDialogController.connectCarrierNetwork(); + } + } + }); + mMobileDataToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (!isChecked && shouldShowMobileDialog()) { + showTurnOffMobileDialog(); + } else if (!shouldShowMobileDialog()) { + mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId, + isChecked, false); + } + }); + mConnectedWifListLayout.setOnClickListener(v -> { + // TODO(b/191475923): Need to launch the detailed page of Wi-Fi entry. + }); + mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); + mWiFiToggle.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + buttonView.setChecked(isChecked); + mWifiManager.setWifiEnabled(isChecked); + mSpace.setVisibility(isChecked ? View.GONE : View.VISIBLE); + }); + mDoneButton.setOnClickListener(v -> dismiss()); + } + + private void setMobileDataLayout(boolean isCellularNetwork) { + if (mInternetDialogController.isAirplaneModeEnabled() + || !mInternetDialogController.hasCarrier()) { + mMobileNetworkLayout.setVisibility(View.GONE); + } else { + mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled()); + mMobileNetworkLayout.setVisibility(View.VISIBLE); + mMobileTitleText.setText(getMobileNetworkTitle()); + mMobileSummaryText.setText( + Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY)); + mSignalIcon.setImageDrawable(getSignalStrengthDrawable()); + int titleColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_primary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorPrimary); + int summaryColor = isCellularNetwork ? mContext.getColor( + R.color.connected_network_tertiary_color) : Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorTertiary); + mMobileTitleText.setTextColor(titleColor); + mMobileSummaryText.setTextColor(summaryColor); + mMobileNetworkLayout.setBackground(isCellularNetwork ? mBackgroundOn : null); + } + } + + private void setConnectedWifiLayout() { + if (!mWifiManager.isWifiEnabled() + || mInternetDialogController.getConnectedWifiEntry() == null) { + mConnectedWifListLayout.setBackground(null); + mConnectedWifListLayout.setVisibility(View.GONE); + return; + } + mConnectedWifListLayout.setVisibility(View.VISIBLE); + mConnectedWifiTitleText.setText(getConnectedWifiTitle()); + mConnectedWifiSummaryText.setText(getConnectedWifiSummary()); + mConnectedWifiIcon.setImageDrawable(getConnectedWifiDrawable()); + mConnectedWifiTitleText.setTextColor( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifiSummaryText.setTextColor( + mContext.getColor(R.color.connected_network_tertiary_color)); + mWifiSettingsIcon.setColorFilter( + mContext.getColor(R.color.connected_network_primary_color)); + mConnectedWifListLayout.setBackground(mBackgroundOn); + } + + void onClickSeeMoreButton() { + mInternetDialogController.launchNetworkSetting(); + } + + CharSequence getDialogTitleText() { + return mInternetDialogController.getDialogTitleText(); + } + + CharSequence getSubtitleText() { + return mInternetDialogController.getSubtitleText(mIsProgressBarVisible); + } + + private Drawable getConnectedWifiDrawable() { + try { + return mInternetDialogController.getWifiConnectedDrawable(mConnectedWifiEntry); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + private Drawable getSignalStrengthDrawable() { + return mInternetDialogController.getSignalStrengthDrawable(); + } + + CharSequence getMobileNetworkTitle() { + return mInternetDialogController.getMobileNetworkTitle(); + } + + String getMobileNetworkSummary() { + return mInternetDialogController.getMobileNetworkSummary(); + } + + String getConnectedWifiTitle() { + return mInternetDialogController.getConnectedWifiTitle(); + } + + String getConnectedWifiSummary() { + return mInternetDialogController.getConnectedWifiSummary(); + } + + private void showProgressBar() { + if (mWifiManager == null || !mWifiManager.isWifiEnabled()) { + setProgressBarVisible(false); + return; + } + setProgressBarVisible(true); + List wifiScanResults = mWifiManager.getScanResults(); + if (wifiScanResults != null && wifiScanResults.size() > 0) { + mContext.getMainThreadHandler().postDelayed(mHideProgressBarRunnable, + 2000 /* delay millis */); + } + } + + private void setProgressBarVisible(boolean visible) { + if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null + && mAdapter.mHolderView.isAttachedToWindow()) { + mIsProgressBarVisible = true; + } + mIsProgressBarVisible = visible; + mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.INVISIBLE); + mInternetDialogSubTitle.setText(getSubtitleText()); + } + + private boolean shouldShowMobileDialog() { + boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, + false); + if (mInternetDialogController.isMobileDataEnabled() && !flag) { + return true; + } + return false; + } + + private void showTurnOffMobileDialog() { + CharSequence carrierName = + mSubscriptionManager.getDefaultDataSubscriptionInfo().getCarrierName(); + boolean isInService = mInternetDialogController.isVoiceStateInService(); + if (TextUtils.isEmpty(carrierName) || !isInService) { + carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier); + } + mAlertDialog = new Builder(mContext) + .setTitle(R.string.mobile_data_disable_title) + .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName)) + .setNegativeButton(android.R.string.cancel, (d, w) -> { + mMobileDataToggle.setChecked(true); + }) + .setPositiveButton( + com.android.internal.R.string.alert_windows_notification_turn_off_action, + (d, w) -> { + mInternetDialogController.setMobileDataEnabled(mContext, + mDefaultDataSubId, false, false); + mMobileDataToggle.setChecked(false); + Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true); + }) + .create(); + mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true)); + mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + SystemUIDialog.setShowForAllUsers(mAlertDialog, true); + SystemUIDialog.registerDismissListener(mAlertDialog); + SystemUIDialog.setWindowOnTop(mAlertDialog); + mAlertDialog.show(); + } + + @Override + public void onRefreshCarrierInfo() { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSimStateChanged() { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSubscriptionsChanged(int defaultDataSubId) { + mDefaultDataSubId = defaultDataSubId; + mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onServiceStateChanged(ServiceState serviceState) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mAdapter.notifyDataSetChanged(); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) { + mHandler.post(() -> updateDialog()); + } + + @Override + public void onAccessPointsChanged(List wifiEntryList, WifiEntry connectedEntry) { + mConnectedWifiEntry = connectedEntry; + mAdapter.notifyDataSetChanged(); + mHandler.post(() -> updateDialog()); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (mAlertDialog != null && !mAlertDialog.isShowing()) { + if (!hasFocus && isShowing()) { + dismiss(); + } + } + } + + @Override + public void onWifiStateReceived(Context context, Intent intent) { + if (intent == null) { + return; + } + + String action = intent.getAction(); + if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + mInternetDialogController.scanWifiAccessPoints(); + showProgressBar(); + return; + } + + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mHandler.post(() -> updateDialog()); + } + } + + public enum InternetDialogEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The Internet dialog became visible on the screen.") + INTERNET_DIALOG_SHOW(843); + + private final int mId; + + InternetDialogEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt new file mode 100644 index 000000000000..d68ad4ba48e3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.qs.tiles.dialog + +import android.content.Context +import android.os.Handler +import android.util.Log +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import javax.inject.Inject + +private const val TAG = "InternetDialogFactory" +private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) + +/** + * Factory to create [InternetDialog] objects. + */ +@SysUISingleton +class InternetDialogFactory @Inject constructor( + @Main private val handler: Handler, + private val internetDialogController: InternetDialogController, + private val context: Context, + private val uiEventLogger: UiEventLogger +) { + companion object { + var internetDialog: InternetDialog? = null + } + + /** Creates a [InternetDialog]. */ + fun create(aboveStatusBar: Boolean) { + if (internetDialog != null) { + if (DEBUG) { + Log.d(TAG, "InternetDialog is showing, do not create it twice.") + } + return + } else { + internetDialog = InternetDialog(context, this, internetDialogController, aboveStatusBar, + uiEventLogger, handler) + } + } + + fun destroyDialog() { + if (DEBUG) { + Log.d(TAG, "destroyDialog") + } + internetDialog = null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index c49de7ab0e5d..075fa174067c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -68,9 +68,12 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; +import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; +import com.android.systemui.qs.tiles.dialog.InternetDialogUtil; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; @@ -191,6 +194,8 @@ public class NetworkControllerImpl extends BroadcastReceiver private boolean mUserSetup; private boolean mSimDetected; private boolean mForceCellularValidated; + private InternetDialogFactory mInternetDialogFactory; + private Handler mMainHandler; private ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @@ -221,7 +226,9 @@ public class NetworkControllerImpl extends BroadcastReceiver DemoModeController demoModeController, CarrierConfigTracker carrierConfigTracker, FeatureFlags featureFlags, - DumpManager dumpManager) { + DumpManager dumpManager, + @Main Handler handler, + InternetDialogFactory internetDialogFactory) { this(context, connectivityManager, telephonyManager, telephonyListenerManager, @@ -242,6 +249,8 @@ public class NetworkControllerImpl extends BroadcastReceiver featureFlags, dumpManager); mReceiverHandler.post(mRegisterListeners); + mMainHandler = handler; + mInternetDialogFactory = internetDialogFactory; } @VisibleForTesting @@ -480,6 +489,9 @@ public class NetworkControllerImpl extends BroadcastReceiver filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED); + if (InternetDialogUtil.isProviderModelEnabled(mContext)) { + filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY); + } mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; @@ -788,6 +800,9 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = Config.readConfig(mContext); mReceiverHandler.post(this::handleConfigurationChanged); break; + case Settings.Panel.ACTION_INTERNET_CONNECTIVITY: + mMainHandler.post(() -> mInternetDialogFactory.create(true)); + break; default: int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, SubscriptionManager.INVALID_SUBSCRIPTION_ID); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java new file mode 100644 index 000000000000..a8f6f5361d78 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -0,0 +1,102 @@ +package com.android.systemui.qs.tiles.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class InternetAdapterTest extends SysuiTestCase { + + private static final String WIFI_TITLE = "Wi-Fi Title"; + private static final String WIFI_SUMMARY = "Wi-Fi Summary"; + private InternetDialogController mInternetDialogController = mock( + InternetDialogController.class); + private InternetAdapter mInternetAdapter; + private InternetAdapter.InternetViewHolder mViewHolder; + @Mock + private WifiEntry mWifiEntry = mock(WifiEntry.class); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInternetAdapter = new InternetAdapter(mInternetDialogController); + mViewHolder = (InternetAdapter.InternetViewHolder) mInternetAdapter + .onCreateViewHolder(new LinearLayout(mContext), 0); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry)); + } + + @Test + public void getItemCount_withApmOnWifiOnNoConnectedWifi_returnFour() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(4); + } + + @Test + public void getItemCount_withApmOnWifiOnHasConnectedWifi_returnThree() { + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(3); + } + + @Test + public void getItemCount_withApmOffWifiOnNoConnectedWifi_returnThree() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(3); + } + + @Test + public void getItemCount_withApmOffWifiOnHasConnectedWifi_returnTwo() { + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + + assertThat(mInternetAdapter.getItemCount()).isEqualTo(2); + } + + @Test + public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() { + when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE); + mInternetAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() { + when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK); + mInternetAdapter.onBindViewHolder(mViewHolder, 0); + + assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mWifiLockedIcon.getVisibility()).isEqualTo(View.VISIBLE); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java new file mode 100644 index 000000000000..5052affee9de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -0,0 +1,245 @@ +package com.android.systemui.qs.tiles.dialog; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.telephony.TelephonyManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; +import androidx.test.filters.SmallTest; + +import com.android.internal.logging.UiEventLogger; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.wifitrackerlib.WifiEntry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class InternetDialogTest extends SysuiTestCase { + + private static final int SUB_ID = 1; + private static final String MOBILE_NETWORK_TITLE = "Mobile Title"; + private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary"; + private static final String WIFI_TITLE = "Connected Wi-Fi Title"; + private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary"; + + private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class); + + private InternetDialogFactory mInternetDialogFactory = mock(InternetDialogFactory.class); + private InternetAdapter mInternetAdapter = mock(InternetAdapter.class); + private InternetDialogController mInternetDialogController = mock( + InternetDialogController.class); + private InternetDialogController.InternetDialogCallback mCallback = + mock(InternetDialogController.InternetDialogCallback.class); + private MockInternetDialog mInternetDialog; + private WifiReceiver mWifiReceiver = null; + private WifiManager mMockWifiManager = mock(WifiManager.class); + private TelephonyManager mTelephonyManager = mock(TelephonyManager.class); + @Mock + private WifiEntry mWifiEntry = mock(WifiEntry.class); + @Mock + private WifiInfo mWifiInfo; + @Mock + private Handler mHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mInternetDialog = new MockInternetDialog(mContext, mInternetDialogFactory, + mInternetDialogController, true, mUiEventLogger, mHandler); + doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(SUB_ID); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + when(mMockWifiManager.getConnectionInfo()).thenReturn(mWifiInfo); + mInternetDialog.setMobileNetworkTitle(MOBILE_NETWORK_TITLE); + mInternetDialog.setMobileNetworkSummary(MOBILE_NETWORK_SUMMARY); + mInternetDialog.setConnectedWifiTitle(WIFI_TITLE); + mInternetDialog.setConnectedWifiSummary(WIFI_SUMMARY); + mWifiReceiver = new WifiReceiver(); + IntentFilter mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mContext.registerReceiver(mWifiReceiver, mIntentFilter); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mInternetDialogController.getWifiEntryList()).thenReturn(Arrays.asList(mWifiEntry)); + } + + @After + public void tearDown() { + mInternetDialog.dismissDialog(); + } + + @Test + public void updateDialog_withApmOn_internetDialogSubTitleGone() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + mInternetDialog.updateDialog(); + final TextView view = mInternetDialog.mDialogView.requireViewById( + R.id.internet_dialog_subtitle); + + assertThat(view.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withApmOff_internetDialogSubTitleVisible() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); + mInternetDialog.updateDialog(); + final TextView view = mInternetDialog.mDialogView.requireViewById( + R.id.internet_dialog_subtitle); + + assertThat(view.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateDialog_withApmOn_mobileDataLayoutGone() { + when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.mobile_network_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withWifiOnAndHasConnectedWifi_connectedWifiLayoutVisible() { + doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); + when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE); + when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY); + when(mWifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + when(mInternetDialogController.getConnectedWifiEntry()).thenReturn(mWifiEntry); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_connected_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + public void updateDialog_withWifiOnAndNoConnectedWifi_connectedWifiLayoutGone() { + doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); + mInternetDialog.updateDialog(); + final LinearLayout linearLayout = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_connected_layout); + + assertThat(linearLayout.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void updateDialog_withWifiOff_WifiRecycleViewGone() { + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); + mInternetDialog.updateDialog(); + final RecyclerView view = mInternetDialog.mDialogView.requireViewById( + R.id.wifi_list_layout); + + assertThat(view.getVisibility()).isEqualTo(View.GONE); + } + + @Test + public void onClickSeeMoreButton_clickSeeMore_verifyLaunchNetworkSetting() { + final LinearLayout seeAllLayout = mInternetDialog.mDialogView.requireViewById( + R.id.see_all_layout); + seeAllLayout.performClick(); + + verify(mInternetDialogController).launchNetworkSetting(); + } + + private class MockInternetDialog extends InternetDialog { + + private String mMobileNetworkTitle; + private String mMobileNetworkSummary; + private String mConnectedWifiTitle; + private String mConnectedWifiSummary; + + MockInternetDialog(Context context, InternetDialogFactory internetDialogFactory, + InternetDialogController internetDialogController, + boolean aboveStatusBar, UiEventLogger uiEventLogger, @Main Handler handler) { + super(context, internetDialogFactory, internetDialogController, aboveStatusBar, + uiEventLogger, handler); + mAdapter = mInternetAdapter; + mWifiManager = mMockWifiManager; + } + + @Override + String getMobileNetworkTitle() { + return mMobileNetworkTitle; + } + + @Override + String getMobileNetworkSummary() { + return mMobileNetworkSummary; + } + + void setMobileNetworkTitle(String title) { + mMobileNetworkTitle = title; + } + + void setMobileNetworkSummary(String summary) { + mMobileNetworkSummary = summary; + } + + @Override + String getConnectedWifiTitle() { + return mConnectedWifiTitle; + } + + @Override + String getConnectedWifiSummary() { + return mConnectedWifiSummary; + } + + void setConnectedWifiTitle(String title) { + mConnectedWifiTitle = title; + } + + void setConnectedWifiSummary(String summary) { + mConnectedWifiSummary = summary; + } + + @Override + public void onWifiStateReceived(Context context, Intent intent) { + setMobileNetworkTitle(MOBILE_NETWORK_TITLE); + setMobileNetworkSummary(MOBILE_NETWORK_SUMMARY); + } + } + + private class WifiReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { + return; + } + + if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mInternetDialog.updateDialog(); + } + } + } + +} -- cgit v1.2.3 From 51b6659ae15e2509512d6fa0e32fe2edf1a1b6e8 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Fri, 13 Aug 2021 14:29:52 +0800 Subject: [ProviderModel] Cherry-pick from master ag/15207286, ag/15216391 and ag/15205988 Should not show pwd dialog if it is a saved Wi-Fi entry The dialog should be scrollable in landscape mode Fix dialog's UI layout - Move down the dialog to overlay the gesture navigation bar. - Mobile data layout need to align UX figma Bug: 192803404 Test: atest InternetDialogTest Change-Id: Ib5ac6ee909e666b66b41fd0069f6c9528858dd37 Merged-In: Ib5ac6ee909e666b66b41fd0069f6c9528858dd37 --- .../res/layout/internet_connectivity_dialog.xml | 557 +++++++++++---------- .../systemui/qs/tiles/dialog/InternetAdapter.java | 5 +- .../systemui/qs/tiles/dialog/InternetDialog.java | 19 +- .../qs/tiles/dialog/InternetDialogFactory.kt | 1 + .../qs/tiles/dialog/InternetDialogTest.java | 5 +- 5 files changed, 301 insertions(+), 286 deletions(-) diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index fddff0b1eb0f..6163af09fa6b 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -27,15 +27,16 @@ android:layout_width="match_parent" style="@style/Widget.SliceView.Panel" android:gravity="center_vertical|center_horizontal" - android:layout_marginTop="20dp" - android:layout_height="64dp" + android:layout_marginTop="24dp" + android:layout_marginBottom="24dp" + android:layout_height="wrap_content" android:orientation="vertical"> @@ -44,7 +45,8 @@ android:id="@+id/internet_dialog_subtitle" android:gravity="center_vertical|center_horizontal" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="20dp" + android:layout_marginTop="8dp" android:ellipsize="end" android:maxLines="1" android:fontFamily="google-sans" @@ -56,296 +58,309 @@ android:layout_height="1dp" android:background="?android:attr/listDivider"/> - - + android:layout_height="match_parent"> - - - - - + android:layout_height="wrap_content" + android:orientation="vertical"> - - - + android:id="@+id/internet_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> - - - + - + + + - + + + + - + + + - - - + - - - + android:minHeight="1dp" + android:maxHeight="1dp" + style="@*android:style/Widget.Material.ProgressBar.Horizontal"/> - + - + + + - - - + + + - - - - + - - - + - + + + - + + + + - + + + - + - - - + - - - + - + - + + + - -