Merge "Teamfood bluetooth quick settings tile dialog." into main
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 213e5cb..7704486 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10335,11 +10335,14 @@
* @return the current credential manager policy if null then this policy has not been
* configured.
*/
+ @UserHandleAware(
+ enabledSinceTargetSdkVersion = UPSIDE_DOWN_CAKE,
+ requiresPermissionIfNotCaller = INTERACT_ACROSS_USERS)
public @Nullable PackagePolicy getCredentialManagerPolicy() {
throwIfParentInstance("getCredentialManagerPolicy");
if (mService != null) {
try {
- return mService.getCredentialManagerPolicy();
+ return mService.getCredentialManagerPolicy(myUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c49b820..58f9d57 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -346,7 +346,7 @@
boolean hasManagedProfileCallerIdAccess(int userId, String packageName);
void setCredentialManagerPolicy(in PackagePolicy policy);
- PackagePolicy getCredentialManagerPolicy();
+ PackagePolicy getCredentialManagerPolicy(int userId);
void setManagedProfileContactsAccessPolicy(in PackagePolicy policy);
PackagePolicy getManagedProfileContactsAccessPolicy();
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index febe6f7..a95e66d 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
flag {
name: "allow_private_profile"
- namespace: "private_profile"
+ namespace: "profile_experiences"
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
}
diff --git a/core/java/com/android/internal/policy/AttributeCache.java b/core/java/com/android/internal/policy/AttributeCache.java
index 1bdad25..970f511 100644
--- a/core/java/com/android/internal/policy/AttributeCache.java
+++ b/core/java/com/android/internal/policy/AttributeCache.java
@@ -16,12 +16,18 @@
package com.android.internal.policy;
+import android.annotation.RequiresPermission;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.net.Uri;
+import android.os.Handler;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.LruCache;
@@ -46,6 +52,8 @@
@GuardedBy("this")
private final Configuration mConfiguration = new Configuration();
+ private PackageMonitor mPackageMonitor;
+
public final static class Package {
public final Context context;
private final SparseArray<ArrayMap<int[], Entry>> mMap = new SparseArray<>();
@@ -77,6 +85,34 @@
}
}
+ /**
+ * Start monitor package change, so the resources can be loaded correctly.
+ */
+ void monitorPackageRemove(Handler handler) {
+ if (mPackageMonitor == null) {
+ mPackageMonitor = new PackageMonitor(mContext, handler);
+ }
+ }
+
+ static class PackageMonitor extends BroadcastReceiver {
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ PackageMonitor(Context context, Handler handler) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addDataScheme(IntentFilter.SCHEME_PACKAGE);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter,
+ null /* broadcastPermission */, handler);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Uri packageUri = intent.getData();
+ if (packageUri != null) {
+ final String packageName = packageUri.getEncodedSchemeSpecificPart();
+ AttributeCache.instance().removePackage(packageName);
+ }
+ }
+ }
+
public static AttributeCache instance() {
return sInstance;
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 8f4df80..40a437f 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -48,6 +48,7 @@
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
+import android.os.Handler;
import android.os.SystemProperties;
import android.util.Slog;
import android.view.InflateException;
@@ -1399,4 +1400,14 @@
// Approximation of WCAG 2.0 relative luminance.
return ((r * 8) + (g * 22) + (b * 2)) >> 5;
}
+
+ /**
+ * For non-system server process, it must call this method to initialize the AttributeCache and
+ * start monitor package change, so the resources can be loaded correctly.
+ */
+ public static void initAttributeCache(Context context, Handler handler) {
+ AttributeCache.init(context);
+ AttributeCache.instance().monitorPackageRemove(handler);
+ }
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 13c0ac4..b71c48e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -76,6 +76,10 @@
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
+ // While set true, Keyguard has created a remote animation runner to handle the open app
+ // transition.
+ private boolean mIsLaunchingActivityOverLockscreen;
+
private final class StartedTransition {
final TransitionInfo mInfo;
final SurfaceControl.Transaction mFinishT;
@@ -120,7 +124,7 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
- if (!handles(info)) {
+ if (!handles(info) || mIsLaunchingActivityOverLockscreen) {
return false;
}
@@ -313,5 +317,11 @@
mUnoccludeTransition = unoccludeTransition;
});
}
+
+ @Override
+ public void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {
+ mMainExecutor.execute(() ->
+ mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
index b4b327f..33c299f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java
@@ -38,4 +38,9 @@
@NonNull IRemoteTransition occludeTransition,
@NonNull IRemoteTransition occludeByDreamTransition,
@NonNull IRemoteTransition unoccludeTransition) {}
+
+ /**
+ * Notify whether keyguard has created a remote animation runner for next app launch.
+ */
+ default void setLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 00f6a1c..83dc7fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -111,6 +111,14 @@
WindowContainerTransaction mFinishWCT = null;
/**
+ * Whether the transition has request for remote transition while mLeftoversHandler
+ * isn't remote transition handler.
+ * If true and the mLeftoversHandler can handle the transition, need to notify remote
+ * transition handler to consume the transition.
+ */
+ boolean mHasRequestToRemote;
+
+ /**
* Mixed transitions are made up of multiple "parts". This keeps track of how many
* parts are currently animating.
*/
@@ -200,6 +208,10 @@
MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE, transition);
mixed.mLeftoversHandler = handler.first;
mActiveTransitions.add(mixed);
+ if (mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mixed.mHasRequestToRemote = true;
+ mPlayer.getRemoteTransitionHandler().handleRequest(transition, request);
+ }
return handler.second;
} else if (mSplitHandler.isSplitScreenVisible()
&& isOpeningType(request.getType())
@@ -316,12 +328,22 @@
// the time of handleRequest, but we need more information than is available at that time.
if (KeyguardTransitionHandler.handles(info)) {
if (mixed != null && mixed.mType != MixedTransition.TYPE_KEYGUARD) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
- "Converting mixed transition into a keyguard transition");
- onTransitionConsumed(transition, false, null);
+ final MixedTransition keyguardMixed =
+ new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
+ mActiveTransitions.add(keyguardMixed);
+ final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
+ startTransaction, finishTransaction, finishCallback);
+ if (hasAnimateKeyguard) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "Converting mixed transition into a keyguard transition");
+ // Consume the original mixed transition
+ onTransitionConsumed(transition, false, null);
+ return true;
+ } else {
+ // Keyguard handler cannot handle it, process through original mixed
+ mActiveTransitions.remove(keyguardMixed);
+ }
}
- mixed = new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
- mActiveTransitions.add(mixed);
}
if (mixed == null) return false;
@@ -332,8 +354,17 @@
} else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
return false;
} else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
- return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction,
- finishTransaction, finishCallback);
+ final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
+ startTransaction, finishTransaction, finishCallback);
+ // Consume the transition on remote handler if the leftover handler already handle this
+ // transition. And if it cannot, the transition will be handled by remote handler, so
+ // don't consume here.
+ // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
+ if (handledToPip && mixed.mHasRequestToRemote
+ && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
+ }
+ return handledToPip;
} else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -799,5 +830,8 @@
} else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
mUnfoldHandler.onTransitionConsumed(transition, aborted, finishT);
}
+ if (mixed.mHasRequestToRemote) {
+ mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, aborted, finishT);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7df658e..de03f58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -99,7 +99,6 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
@@ -182,7 +181,7 @@
/* broadcastPermission = */ null,
mMainHandler);
- AttributeCache.init(mContext);
+ TransitionAnimation.initAttributeCache(mContext, mMainHandler);
}
private void updateEnterpriseThumbnailDrawable() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
index 77f14f1..adf92d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/BaseAppCompat.kt
@@ -50,7 +50,7 @@
}
@Before
- fun before() {
+ fun setUp() {
Assume.assumeTrue(tapl.isTablet && letterboxRule.isIgnoreOrientationRequest)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
new file mode 100644
index 0000000..ba2b3e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/RotateImmersiveAppInFullscreenTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.appcompat
+
+import android.os.Build
+import android.tools.common.datatypes.Rect
+import android.platform.test.annotations.Postsubmit
+import android.system.helpers.CommandsHelper
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.common.flicker.assertions.FlickerTest
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import android.tools.device.helpers.FIND_TIMEOUT
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.LetterboxAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Test rotating an immersive app in fullscreen.
+ *
+ * To run this test: `atest WMShellFlickerTestsOther:RotateImmersiveAppInFullscreenTest`
+ *
+ * Actions:
+ * ```
+ * Rotate the device by 90 degrees to trigger a rotation through sensors
+ * Verify that the button exists
+ * ```
+ *
+ * Notes:
+ * ```
+ * Some default assertions that are inherited from
+ * the `BaseTest` are ignored due to the nature of the immersive apps.
+ *
+ * This test only works with Cuttlefish devices.
+ * ```
+ */
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+class RotateImmersiveAppInFullscreenTest(flicker: LegacyFlickerTest) : BaseAppCompat(flicker) {
+
+ private val immersiveApp = LetterboxAppHelper(instrumentation,
+ launcherName = ActivityOptions.PortraitImmersiveActivity.LABEL,
+ component =
+ ActivityOptions.PortraitImmersiveActivity.COMPONENT.toFlickerComponent())
+
+ private val cmdHelper: CommandsHelper = CommandsHelper.getInstance(instrumentation)
+ private val execAdb: (String) -> String = { cmd -> cmdHelper.executeShellCommand(cmd) }
+
+ protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+
+ private val isCuttlefishDevice: Boolean = Build.MODEL.contains("Cuttlefish")
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ setStartRotation()
+ immersiveApp.launchViaIntent(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Display not found")
+ }
+ transitions {
+ if (isCuttlefishDevice) {
+ // Simulates a device rotation through sensors because the rotation button
+ // only appears in a rotation event through sensors
+ execAdb("/vendor/bin/cuttlefish_sensor_injection rotate 0")
+ // verify rotation button existence
+ val rotationButtonSelector = By.res(LAUNCHER_PACKAGE, "rotate_suggestion")
+ uiDevice.wait(Until.hasObject(rotationButtonSelector), FIND_TIMEOUT)
+ uiDevice.findObject(rotationButtonSelector)
+ ?: error("rotation button not found")
+ }
+ }
+ teardown {
+ immersiveApp.exit(wmHelper)
+ }
+ }
+
+ @Before
+ fun setUpForImmersiveAppTests() {
+ Assume.assumeTrue(isCuttlefishDevice)
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun taskBarWindowIsAlwaysVisible() {
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun navBarWindowIsAlwaysVisible() {
+ }
+
+ /** {@inheritDoc} */
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarWindowIsAlwaysVisible() {
+ }
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ }
+
+ @Test
+ @Ignore("Not applicable to this CUJ. App is in immersive mode.")
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ }
+
+ /** Test that app is fullscreen by checking status bar and task bar visibility. */
+ @Postsubmit
+ @Test
+ fun appWindowFullScreen() {
+ flicker.assertWmEnd {
+ this.isAppWindowInvisible(ComponentNameMatcher.STATUS_BAR)
+ .isAppWindowInvisible(ComponentNameMatcher.TASK_BAR)
+ .visibleRegion(immersiveApp).coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Test that app is in the original rotation we have set up. */
+ @Postsubmit
+ @Test
+ fun appInOriginalRotation() {
+ flicker.assertWmEnd {
+ this.hasRotation(Rotation.ROTATION_90)
+ }
+ }
+
+ companion object {
+ private var startDisplayBounds = Rect.EMPTY
+ const val LAUNCHER_PACKAGE = "com.google.android.apps.nexuslauncher"
+
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return LegacyFlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(Rotation.ROTATION_90),
+ // TODO(b/292403378): 3 button mode not added as rotation button is hidden in taskbar
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 8c81733..d1067a9 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -105,6 +105,13 @@
screen. -->
<item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>
+ <!-- Proportion of the screen height to use to set the maximum height of the bouncer to when
+ the device is in the DEVICE_POSTURE_HALF_OPENED posture.
+
+ This value is only used when motion layout bouncer is used - when flag
+ landscape.enable_lockscreen (b/293252410) is on -->
+ <item name="motion_layout_half_fold_bouncer_height_ratio" type="dimen" format="float">0.55</item>
+
<!-- The actual amount of translation that is applied to the security when it animates from one
side of the screen to the other in one-handed or user switcher mode. Note that it will
always translate from the side of the screen to the other (it will "jump" closer to the
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
index 6112411..751d6d8 100644
--- a/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
@@ -10,9 +10,34 @@
motion:duration="0"
motion:autoTransition="none"/>
+ <Transition
+ motion:constraintSetStart="@id/single_constraints"
+ motion:constraintSetEnd="@+id/half_folded_single_constraints"
+ motion:duration="@integer/material_motion_duration_short_1"
+ motion:autoTransition="none"/>
+
<!-- No changes to default layout -->
<ConstraintSet android:id="@+id/single_constraints"/>
+ <ConstraintSet android:id="@+id/half_folded_single_constraints">
+
+ <Constraint
+ android:id="@+id/pattern_top_guideline"
+ androidprv:layout_constraintGuide_percent=
+ "@dimen/motion_layout_half_fold_bouncer_height_ratio"/>
+
+ <Constraint
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="0dp"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/flow1"/>
+
+ </ConstraintSet>
+
<ConstraintSet android:id="@+id/split_constraints">
<Constraint
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
index 2a1270c..cc498f4 100644
--- a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
@@ -26,11 +26,35 @@
motion:constraintSetStart="@id/single_constraints"
motion:constraintSetEnd="@+id/split_constraints"
motion:duration="0"
- motion:autoTransition="none"/>
+ motion:autoTransition="none" />
+
+ <Transition
+ motion:constraintSetStart="@id/single_constraints"
+ motion:constraintSetEnd="@+id/half_folded_single_constraints"
+ motion:duration="@integer/material_motion_duration_short_1" />
<!-- No changes to default layout -->
<ConstraintSet android:id="@+id/single_constraints"/>
+ <ConstraintSet android:id="@+id/half_folded_single_constraints">
+
+ <Constraint
+ android:id="@+id/pin_pad_top_guideline"
+ androidprv:layout_constraintGuide_percent=
+ "@dimen/motion_layout_half_fold_bouncer_height_ratio"/>
+
+ <Constraint
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="0dp"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/flow1"/>
+
+ </ConstraintSet>
+
<ConstraintSet android:id="@+id/split_constraints">
<Constraint
@@ -68,4 +92,5 @@
android:layout_marginTop="@dimen/keyguard_eca_top_margin" />
</ConstraintSet>
+
</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index 4265c4b..6dd44fb 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -14,55 +14,81 @@
~ limitations under the License.
-->
+<!-- TODO(b/298124674) remove this root -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/bluetooth_device_container"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/bluetooth_device_list_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="4dp">
- <LinearLayout
- android:id="@+id/bluetooth_device"
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/bluetooth_device_row"
style="@style/BluetoothTileDialog.Device"
android:layout_height="@dimen/bluetooth_dialog_device_height"
android:paddingEnd="24dp"
android:paddingStart="20dp"
android:baselineAligned="false">
- <FrameLayout
+ <ImageView
+ android:id="@+id/bluetooth_device_icon"
+ android:contentDescription="@string/accessibility_bluetooth_device_icon"
android:layout_width="24dp"
android:layout_height="24dp"
- android:layout_gravity="center_vertical|start"
- android:clickable="false">
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_gravity="center_vertical" />
- <ImageView
- android:id="@+id/bluetooth_device_icon"
- android:contentDescription="@string/accessibility_bluetooth_device_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center" />
- </FrameLayout>
-
- <LinearLayout
+ <View
+ android:id="@+id/bluetooth_device"
android:layout_width="0dp"
- android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="@+id/bluetooth_device_name"
+ app:layout_constraintBottom_toBottomOf="@+id/bluetooth_device_summary"
+ app:layout_constraintStart_toStartOf="@+id/bluetooth_device_name"
+ app:layout_constraintEnd_toEndOf="@+id/bluetooth_device_name" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:id="@+id/bluetooth_device_name"
+ style="@style/BluetoothTileDialog.DeviceName"
android:paddingStart="20dp"
- android:paddingEnd="24dp"
- android:layout_marginEnd="30dp"
- android:layout_weight="1"
- android:clickable="false"
- android:gravity="start|center_vertical"
- android:orientation="vertical">
+ android:paddingTop="10dp"
+ app:layout_constraintWidth_percent="0.7"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
+ app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+ app:layout_constraintBottom_toTopOf="@+id/bluetooth_device_summary"
+ android:gravity="center_vertical"
+ android:textSize="14sp" />
- <TextView
- android:id="@+id/bluetooth_device_name"
- style="@style/BluetoothTileDialog.DeviceName"
- android:textSize="14sp" />
+ <TextView
+ android:layout_width="0dp"
+ android:id="@+id/bluetooth_device_summary"
+ style="@style/BluetoothTileDialog.DeviceSummary"
+ android:paddingStart="20dp"
+ android:paddingBottom="10dp"
+ app:layout_constraintWidth_percent="0.7"
+ app:layout_constraintTop_toBottomOf="@+id/bluetooth_device_name"
+ app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
+ app:layout_constraintEnd_toStartOf="@+id/gear_icon"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:gravity="center_vertical" />
- <TextView
- android:id="@+id/bluetooth_device_summary"
- style="@style/BluetoothTileDialog.DeviceSummary" />
- </LinearLayout>
- </LinearLayout>
-
+ <ImageView
+ android:id="@+id/gear_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:contentDescription="@string/accessibility_bluetooth_device_settings_gear"
+ android:layout_width="0dp"
+ android:layout_height="24dp"
+ app:layout_constraintStart_toEndOf="@+id/bluetooth_device_name"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintWidth_percent="0.3"
+ android:gravity="center_vertical"
+ android:paddingStart="10dp" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 9d14d0f..16aeb95 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -14,155 +14,175 @@
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
- android:layout_width="@dimen/large_dialog_width"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ style="@style/Widget.SliceView.Panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
- <LinearLayout
- style="@style/Widget.SliceView.Panel"
- android:layout_width="match_parent"
+ <TextView
+ android:id="@+id/bluetooth_tile_dialog_title"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/bluetooth_dialog_layout_margin"
- android:layout_marginTop="24dp"
+ android:paddingTop="24dp"
+ android:ellipsize="end"
android:gravity="center_vertical|center_horizontal"
- android:orientation="vertical">
+ android:text="@string/quick_settings_bluetooth_label"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ android:textSize="24sp"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/bluetooth_tile_dialog_subtitle"
+ app:layout_constraintTop_toTopOf="parent" />
- <TextView
- android:id="@+id/bluetooth_tile_dialog_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:gravity="center_vertical|center_horizontal"
- android:text="@string/quick_settings_bluetooth_label"
- android:textAppearance="@style/TextAppearance.Dialog.Title"
- android:textSize="24sp" />
+ <TextView
+ android:id="@+id/bluetooth_tile_dialog_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:layout_marginBottom="@dimen/bluetooth_dialog_layout_margin"
+ android:ellipsize="end"
+ android:gravity="center_vertical|center_horizontal"
+ android:maxLines="1"
+ android:text="@string/quick_settings_bluetooth_tile_subtitle"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/bluetooth_toggle_title"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
- <TextView
- android:id="@+id/bluetooth_tile_dialog_subtitle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:ellipsize="end"
- android:gravity="center_vertical|center_horizontal"
- android:maxLines="1"
- android:text="@string/quick_settings_bluetooth_tile_subtitle"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message" />
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/turn_on_bluetooth_layout"
+ <TextView
+ android:id="@+id/bluetooth_toggle_title"
style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
+ android:layout_height="64dp"
+ android:gravity="center_vertical"
+ android:layout_marginTop="4dp"
+ android:text="@string/turn_on_bluetooth"
+ android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textSize="16sp"
+ app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
+
+ <Switch
+ android:id="@+id/bluetooth_toggle"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
+ android:layout_height="48dp"
+ android:gravity="center|center_vertical"
+ android:paddingEnd="24dp"
+ android:layout_marginTop="10dp"
+ android:contentDescription="@string/turn_on_bluetooth"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:theme="@style/MainSwitch.Settingslib"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:track="@drawable/settingslib_track_selector"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/bluetooth_toggle_title"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle" />
+
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/pair_new_device_layout_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="ic_add,pair_new_device_text" />
+
+ <ImageView
+ android:id="@+id/ic_add"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="36dp"
+ android:gravity="center_vertical"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_add"
+ app:layout_constraintBottom_toTopOf="@id/device_list"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/pair_new_device_text"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
+ android:tint="?android:attr/textColorPrimary" />
+
+ <TextView
+ android:id="@+id/pair_new_device_text"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
android:layout_height="@dimen/bluetooth_dialog_device_height"
- android:gravity="center"
- android:clickable="false"
- android:focusable="false"
- android:baselineAligned="false">
+ android:gravity="center_vertical"
+ android:layout_marginStart="0dp"
+ android:paddingStart="20dp"
+ android:text="@string/pair_new_bluetooth_devices"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ app:layout_constraintBottom_toTopOf="@id/device_list"
+ app:layout_constraintStart_toEndOf="@+id/ic_add"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_toggle_title"
+ app:layout_constraintEnd_toEndOf="parent" />
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="start|center_vertical"
- android:orientation="vertical"
- android:clickable="false">
- <TextView
- android:id="@+id/bluetooth_toggle_title"
- android:text="@string/turn_on_bluetooth"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="start|center_vertical"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
- android:textSize="16sp"/>
- </LinearLayout>
-
- <FrameLayout
- android:layout_width="@dimen/settingslib_switch_track_width"
- android:layout_height="48dp"
- android:layout_marginTop="10dp"
- android:layout_marginBottom="10dp">
- <Switch
- android:id="@+id/bluetooth_toggle"
- android:contentDescription="@string/turn_on_bluetooth"
- android:switchMinWidth="@dimen/settingslib_switch_track_width"
- android:layout_gravity="center"
- android:layout_width="@dimen/settingslib_switch_track_width"
- android:layout_height="match_parent"
- android:track="@drawable/settingslib_track_selector"
- android:thumb="@drawable/settingslib_thumb_selector"
- android:theme="@style/MainSwitch.Settingslib"/>
- </FrameLayout>
-
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/body_layout"
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:nestedScrollingEnabled="false"
+ android:overScrollMode="never"
+ android:scrollbars="vertical"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/pair_new_device_text"
+ app:layout_constraintBottom_toTopOf="@+id/see_all_text" />
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/device_list"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:nestedScrollingEnabled="false"
- android:overScrollMode="never"
- android:scrollbars="vertical" />
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/see_all_layout_group"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="ic_arrow,see_all_text" />
- <LinearLayout
- android:id="@+id/see_all_layout"
- style="@style/BluetoothTileDialog.Device"
- android:layout_height="64dp"
- android:paddingStart="20dp"
- android:visibility="gone">
+ <ImageView
+ android:id="@+id/ic_arrow"
+ android:layout_marginStart="36dp"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:importantForAccessibility="no"
+ android:gravity="center_vertical"
+ android:src="@drawable/ic_arrow_forward"
+ app:layout_constraintBottom_toTopOf="@+id/done_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/see_all_text"
+ app:layout_constraintTop_toBottomOf="@id/device_list" />
- <FrameLayout
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_gravity="center_vertical|start"
- android:clickable="false">
+ <TextView
+ android:id="@+id/see_all_text"
+ style="@style/BluetoothTileDialog.Device"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/bluetooth_dialog_device_height"
+ android:gravity="center_vertical"
+ android:layout_marginStart="0dp"
+ android:paddingStart="20dp"
+ android:text="@string/see_all_bluetooth_devices"
+ android:textSize="14sp"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
+ app:layout_constraintBottom_toTopOf="@+id/done_button"
+ app:layout_constraintStart_toEndOf="@+id/ic_arrow"
+ app:layout_constraintTop_toBottomOf="@id/device_list"
+ app:layout_constraintEnd_toEndOf="parent" />
- <ImageView
- android:id="@+id/arrow_forward"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:src="@drawable/ic_arrow_forward"
- android:importantForAccessibility="no" />
- </FrameLayout>
-
- <FrameLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
- android:clickable="false"
- android:orientation="vertical">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="start|center_vertical"
- android:text="@string/see_all_bluetooth_devices"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
- android:textSize="14sp" />
- </FrameLayout>
- </LinearLayout>
-
- <Button
- android:id="@+id/done_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginStart="@dimen/dialog_side_padding"
- android:layout_marginEnd="@dimen/dialog_side_padding"
- android:layout_marginBottom="@dimen/dialog_bottom_padding"
- android:text="@string/inline_done_button"
- android:layout_gravity="end|center_vertical"
- style="@style/Widget.Dialog.Button"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"/>
- </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+ <Button
+ android:id="@+id/done_button"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/inline_done_button"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/see_all_text" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5f3ddda..bbc363b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -462,7 +462,9 @@
<!-- Content description of the bluetooth device icon. [CHAR LIMIT=NONE] -->
<string name="accessibility_bluetooth_device_icon">Bluetooth device icon</string>
- <!-- Content description of the bluetooth icon when connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+
+ <!-- Content description of the bluetooth device settings gear icon. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_bluetooth_device_settings_gear">Bluetooth device settings gear</string>
<!-- Content description of the battery when battery state is unknown for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_unknown">Battery percentage unknown.</string>
@@ -625,7 +627,9 @@
<!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
<!-- QuickSettings: Bluetooth dialog subtitle [CHAR LIMIT=NONE]-->
- <string name="quick_settings_bluetooth_tile_subtitle">Tap to connect or disconnect</string>
+ <string name="quick_settings_bluetooth_tile_subtitle">Tap a device to connect</string>
+ <!-- QuickSettings: Bluetooth dialog pair new devices [CHAR LIMIT=NONE]-->
+ <string name="pair_new_bluetooth_devices">Pair new device</string>
<!-- QuickSettings: Bluetooth dialog see all devices [CHAR LIMIT=NONE]-->
<string name="see_all_bluetooth_devices">See all</string>
<!-- QuickSettings: Bluetooth dialog turn on Bluetooth [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 3bd6ab0..084cb88 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1280,7 +1280,6 @@
</style>
<style name="BluetoothTileDialog.DeviceSummary">
- <item name="android:layout_marginEnd">7dp</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">2</item>
<item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 1741e30..622b67f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
@@ -52,6 +53,8 @@
private final DisappearAnimationUtils mDisappearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
@Nullable private MotionLayout mContainerMotionLayout;
+ // TODO (b/293252410) - usage of mContainerConstraintLayout should be removed
+ // when the flag is enabled/removed
@Nullable private ConstraintLayout mContainerConstraintLayout;
private int mDisappearYTranslation;
private View[][] mViews;
@@ -59,7 +62,7 @@
private int mYTransOffset;
private View mBouncerMessageArea;
private boolean mAlreadyUsingSplitBouncer = false;
- private boolean mIsLockScreenLandscapeEnabled = false;
+ private boolean mIsSmallLockScreenLandscapeEnabled = false;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public static final long ANIMATION_DURATION = 650;
@@ -87,12 +90,12 @@
/** Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
* enabled, instead of constraint layout (old bouncer implementation) */
public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
- mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
+ mIsSmallLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
findContainerLayout();
}
private void findContainerLayout() {
- if (mIsLockScreenLandscapeEnabled) {
+ if (mIsSmallLockScreenLandscapeEnabled) {
mContainerMotionLayout = findViewById(R.id.pin_container);
} else {
mContainerConstraintLayout = findViewById(R.id.pin_container);
@@ -109,7 +112,7 @@
if (mLastDevicePosture == posture) return;
mLastDevicePosture = posture;
- if (mIsLockScreenLandscapeEnabled) {
+ if (mIsSmallLockScreenLandscapeEnabled) {
boolean useSplitBouncerAfterFold =
mLastDevicePosture == DEVICE_POSTURE_CLOSED
&& getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
@@ -166,21 +169,45 @@
}
}
+ if (mIsSmallLockScreenLandscapeEnabled) {
+ updateHalfFoldedConstraints();
+ } else {
+ updateHalfFoldedGuideline();
+ }
+ }
+
+ private void updateHalfFoldedConstraints() {
+ // Update the constraints based on the device posture...
+ if (mAlreadyUsingSplitBouncer) return;
+
+ boolean shouldCollapsePin =
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
+ && mContext.getResources().getConfiguration().orientation
+ == ORIENTATION_PORTRAIT;
+
+ int expectedMotionLayoutState = shouldCollapsePin
+ ? R.id.half_folded_single_constraints
+ : R.id.single_constraints;
+
+ transitionToMotionLayoutState(expectedMotionLayoutState);
+ }
+
+ // TODO (b/293252410) - this method can be removed when the flag is enabled/removed
+ private void updateHalfFoldedGuideline() {
// Update the guideline based on the device posture...
float halfOpenPercentage =
mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
- if (mIsLockScreenLandscapeEnabled) {
- ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints);
- cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
- mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
- cs.applyTo(mContainerMotionLayout);
- } else {
- ConstraintSet cs = new ConstraintSet();
- cs.clone(mContainerConstraintLayout);
- cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
- mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
- cs.applyTo(mContainerConstraintLayout);
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainerConstraintLayout);
+ cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainerConstraintLayout);
+ }
+
+ private void transitionToMotionLayoutState(int state) {
+ if (mContainerMotionLayout.getCurrentState() != state) {
+ mContainerMotionLayout.transitionToState(state);
}
}
@@ -189,12 +216,24 @@
* Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. */
@Override
protected void updateConstraints(boolean useSplitBouncer) {
+ if (!mIsSmallLockScreenLandscapeEnabled) return;
+
mAlreadyUsingSplitBouncer = useSplitBouncer;
+
if (useSplitBouncer) {
mContainerMotionLayout.jumpToState(R.id.split_constraints);
mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
} else {
- mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ boolean useHalfFoldedConstraints =
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
+ && mContext.getResources().getConfiguration().orientation
+ == ORIENTATION_PORTRAIT;
+
+ if (useHalfFoldedConstraints) {
+ mContainerMotionLayout.jumpToState(R.id.half_folded_single_constraints);
+ } else {
+ mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ }
mContainerMotionLayout.setMaxWidth(getResources()
.getDimensionPixelSize(R.dimen.keyguard_security_width));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 802e222..5c206e9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
@@ -81,9 +82,11 @@
BouncerKeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
@Nullable private MotionLayout mContainerMotionLayout;
+ // TODO (b/293252410) - usage of mContainerConstraintLayout should be removed
+ // when the flag is enabled/removed
@Nullable private ConstraintLayout mContainerConstraintLayout;
private boolean mAlreadyUsingSplitBouncer = false;
- private boolean mIsLockScreenLandscapeEnabled = false;
+ private boolean mIsSmallLockScreenLandscapeEnabled = false;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public KeyguardPatternView(Context context) {
@@ -111,12 +114,12 @@
* enabled, instead of constraint layout (old bouncer implementation)
*/
public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
- mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
+ mIsSmallLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
findContainerLayout();
}
private void findContainerLayout() {
- if (mIsLockScreenLandscapeEnabled) {
+ if (mIsSmallLockScreenLandscapeEnabled) {
mContainerMotionLayout = findViewById(R.id.pattern_container);
} else {
mContainerConstraintLayout = findViewById(R.id.pattern_container);
@@ -132,7 +135,7 @@
if (mLastDevicePosture == posture) return;
mLastDevicePosture = posture;
- if (mIsLockScreenLandscapeEnabled) {
+ if (mIsSmallLockScreenLandscapeEnabled) {
boolean useSplitBouncerAfterFold =
mLastDevicePosture == DEVICE_POSTURE_CLOSED
&& getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
@@ -147,21 +150,45 @@
}
private void updateMargins() {
+ if (mIsSmallLockScreenLandscapeEnabled) {
+ updateHalfFoldedConstraints();
+ } else {
+ updateHalfFoldedGuideline();
+ }
+ }
+
+ private void updateHalfFoldedConstraints() {
+ // Update the constraints based on the device posture...
+ if (mAlreadyUsingSplitBouncer) return;
+
+ boolean shouldCollapsePattern =
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
+ && mContext.getResources().getConfiguration().orientation
+ == ORIENTATION_PORTRAIT;
+
+ int expectedMotionLayoutState = shouldCollapsePattern
+ ? R.id.half_folded_single_constraints
+ : R.id.single_constraints;
+
+ transitionToMotionLayoutState(expectedMotionLayoutState);
+ }
+
+ // TODO (b/293252410) - this method can be removed when the flag is enabled/removed
+ private void updateHalfFoldedGuideline() {
// Update the guideline based on the device posture...
float halfOpenPercentage =
mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
- if (mIsLockScreenLandscapeEnabled) {
- ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints);
- cs.setGuidelinePercent(R.id.pattern_top_guideline,
- mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
- cs.applyTo(mContainerMotionLayout);
- } else {
- ConstraintSet cs = new ConstraintSet();
- cs.clone(mContainerConstraintLayout);
- cs.setGuidelinePercent(R.id.pattern_top_guideline,
- mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
- cs.applyTo(mContainerConstraintLayout);
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainerConstraintLayout);
+ cs.setGuidelinePercent(R.id.pattern_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainerConstraintLayout);
+ }
+
+ private void transitionToMotionLayoutState(int state) {
+ if (mContainerMotionLayout.getCurrentState() != state) {
+ mContainerMotionLayout.transitionToState(state);
}
}
@@ -172,12 +199,24 @@
*/
@Override
protected void updateConstraints(boolean useSplitBouncer) {
+ if (!mIsSmallLockScreenLandscapeEnabled) return;
+
mAlreadyUsingSplitBouncer = useSplitBouncer;
+
if (useSplitBouncer) {
mContainerMotionLayout.jumpToState(R.id.split_constraints);
mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
} else {
- mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ boolean useHalfFoldedConstraints =
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
+ && mContext.getResources().getConfiguration().orientation
+ == ORIENTATION_PORTRAIT;
+
+ if (useHalfFoldedConstraints) {
+ mContainerMotionLayout.jumpToState(R.id.half_folded_single_constraints);
+ } else {
+ mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ }
mContainerMotionLayout.setMaxWidth(getResources()
.getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size));
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 9c4d224..ec2999f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -139,12 +139,12 @@
onViewInflatedListener.onViewInflated(childController);
// Single bouncer constrains are default
- if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)
- &&
- getResources().getBoolean(R.bool.update_bouncer_constraints)) {
+ if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
boolean useSplitBouncer =
- getResources().getConfiguration().orientation
+ getResources().getBoolean(R.bool.update_bouncer_constraints)
+ && getResources().getConfiguration().orientation
== ORIENTATION_LANDSCAPE;
+
updateConstraints(useSplitBouncer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index efd25d5..b506a36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3798,6 +3798,13 @@
}
/**
+ * Notify whether keyguard has created a remote animation runner for next app launch.
+ */
+ public void launchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {
+ mKeyguardTransitions.setLaunchingActivityOverLockscreen(isLaunchingActivityOverLockscreen);
+ }
+
+ /**
* Implementation of RemoteAnimationRunner that creates a new
* {@link ActivityLaunchAnimator.Runner} whenever onAnimationStart is called, delegating the
* remote animation methods to that runner.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 7a436a7..6815a73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -28,6 +28,7 @@
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -42,11 +43,12 @@
constructor(
private val bluetoothToggleInitialValue: Boolean,
private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+ private val uiEventLogger: UiEventLogger,
context: Context,
) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {
- private val mutableBluetoothStateSwitchedFlow: MutableStateFlow<Boolean?> =
- MutableStateFlow(null)
+ private val mutableBluetoothStateSwitchedFlow: MutableStateFlow<Boolean> =
+ MutableStateFlow(bluetoothToggleInitialValue)
internal val bluetoothStateSwitchedFlow
get() = mutableBluetoothStateSwitchedFlow.asStateFlow()
@@ -55,31 +57,48 @@
internal val deviceItemClickedFlow
get() = mutableClickedFlow.asSharedFlow()
- private val deviceItemAdapter: Adapter = Adapter()
+ private val deviceItemAdapter: Adapter = Adapter(bluetoothTileDialogCallback)
private lateinit var toggleView: Switch
private lateinit var doneButton: View
- private lateinit var seeAllView: View
+ private lateinit var seeAllViewGroup: View
+ private lateinit var pairNewDeviceViewGroup: View
+ private lateinit var seeAllText: View
+ private lateinit var pairNewDeviceText: View
private lateinit var deviceListView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
setContentView(LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null))
toggleView = requireViewById(R.id.bluetooth_toggle)
doneButton = requireViewById(R.id.done_button)
- seeAllView = requireViewById(R.id.see_all_layout)
+ seeAllViewGroup = requireViewById(R.id.see_all_layout_group)
+ pairNewDeviceViewGroup = requireViewById(R.id.pair_new_device_layout_group)
+ seeAllText = requireViewById(R.id.see_all_text)
+ pairNewDeviceText = requireViewById(R.id.pair_new_device_text)
deviceListView = requireViewById<RecyclerView>(R.id.device_list)
setupToggle()
setupRecyclerView()
doneButton.setOnClickListener { dismiss() }
+ seeAllText.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
+ pairNewDeviceText.setOnClickListener {
+ bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
+ }
}
- internal fun onDeviceItemUpdated(deviceItem: List<DeviceItem>, showSeeAll: Boolean) {
- seeAllView.visibility = if (showSeeAll) VISIBLE else GONE
+ // TODO(b/298124674): use DiffUtil or AsyncListDiffer to avoid updating the whole list
+ internal fun onDeviceItemUpdated(
+ deviceItem: List<DeviceItem>,
+ showSeeAll: Boolean,
+ showPairNewDevice: Boolean
+ ) {
+ seeAllViewGroup.visibility = if (showSeeAll) VISIBLE else GONE
+ pairNewDeviceViewGroup.visibility = if (showPairNewDevice) VISIBLE else GONE
deviceItemAdapter.refreshDeviceItemList(deviceItem)
}
@@ -95,6 +114,7 @@
toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { _, isChecked ->
mutableBluetoothStateSwitchedFlow.value = isChecked
+ uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
}
}
@@ -105,11 +125,8 @@
}
}
- internal inner class Adapter : RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
-
- init {
- setHasStableIds(true)
- }
+ internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
+ RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
private val deviceItem: MutableList<DeviceItem> = mutableListOf()
@@ -122,11 +139,9 @@
override fun getItemCount() = deviceItem.size
- override fun getItemId(position: Int) = position.toLong()
-
override fun onBindViewHolder(holder: DeviceItemViewHolder, position: Int) {
val item = getItem(position)
- holder.bind(item, position)
+ holder.bind(item, position, onClickCallback)
}
internal fun getItem(position: Int) = deviceItem[position]
@@ -143,17 +158,26 @@
}
internal inner class DeviceItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
- private val container = view.requireViewById<View>(R.id.bluetooth_device)
+ private val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+ private val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
private val nameView = view.requireViewById<TextView>(R.id.bluetooth_device_name)
private val summaryView = view.requireViewById<TextView>(R.id.bluetooth_device_summary)
private val iconView = view.requireViewById<ImageView>(R.id.bluetooth_device_icon)
+ private val gearView = view.requireViewById<View>(R.id.gear_icon)
- internal fun bind(item: DeviceItem, position: Int) {
+ internal fun bind(
+ item: DeviceItem,
+ position: Int,
+ deviceItemOnClickCallback: BluetoothTileDialogCallback
+ ) {
container.apply {
isEnabled = item.isEnabled
alpha = item.alpha
background = item.background
- setOnClickListener { mutableClickedFlow.tryEmit(Pair(item, position)) }
+ }
+ deviceView.setOnClickListener {
+ mutableClickedFlow.tryEmit(Pair(item, position))
+ uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_CLICKED)
}
nameView.text = item.deviceName
summaryView.text = item.connectionSummary
@@ -163,6 +187,9 @@
contentDescription = it.second
}
}
+ gearView.setOnClickListener {
+ deviceItemOnClickCallback.onDeviceItemGearClicked(item, it)
+ }
}
}
}
@@ -171,5 +198,10 @@
const val ENABLED_ALPHA = 1.0f
const val DISABLED_ALPHA = 0.3f
const val MAX_DEVICE_ITEM_ENTRY = 3
+ const val ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
+ const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
+ "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
+ const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
new file mode 100644
index 0000000..2865ad7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogUiEvent.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog.bluetooth
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** UI Events for the bluetooth tile dialog. */
+enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The bluetooth tile dialog is shown") BLUETOOTH_TILE_DIALOG_SHOWN(1493),
+ @UiEvent(doc = "The master toggle is clicked") BLUETOOTH_TOGGLE_CLICKED(1494),
+ @UiEvent(doc = "Pair new device is clicked") PAIR_NEW_DEVICE_CLICKED(1495),
+ @UiEvent(doc = "See all is clicked") SEE_ALL_CLICKED(1496),
+ @UiEvent(doc = "Gear icon clicked") DEVICE_GEAR_CLICKED(1497),
+ @UiEvent(doc = "Device clicked") DEVICE_CLICKED(1498),
+ @UiEvent(doc = "Connected device clicked to active") CONNECTED_DEVICE_SET_ACTIVE(1499),
+ @UiEvent(doc = "Saved clicked to connect") SAVED_DEVICE_CONNECT(1500);
+
+ override fun getId() = metricId
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 63f0531..012484f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -17,12 +17,19 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
import android.content.Context
+import android.content.Intent
+import android.os.Bundle
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PAIR_NEW_DEVICE
+import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
@@ -42,6 +49,8 @@
private val deviceItemInteractor: DeviceItemInteractor,
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+ private val uiEventLogger: UiEventLogger,
@Application private val coroutineScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
) : BluetoothTileDialogCallback {
@@ -93,14 +102,14 @@
.onEach {
dialog!!.onDeviceItemUpdated(
it.take(MAX_DEVICE_ITEM_ENTRY),
- showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY
+ showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+ showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
)
}
.launchIn(this)
dialog!!
.bluetoothStateSwitchedFlow
- .filterNotNull()
.onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
.launchIn(this)
@@ -119,19 +128,57 @@
return BluetoothTileDialog(
bluetoothStateInteractor.isBluetoothEnabled,
this@BluetoothTileDialogViewModel,
+ uiEventLogger,
context
)
.apply { SystemUIDialog.registerDismissListener(this) { dismissDialog() } }
}
+ override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.DEVICE_GEAR_CLICKED)
+ val intent =
+ Intent(ACTION_BLUETOOTH_DEVICE_DETAILS).apply {
+ putExtra(
+ ":settings:show_fragment_args",
+ Bundle().apply {
+ putString("device_address", deviceItem.cachedBluetoothDevice.address)
+ }
+ )
+ }
+ startSettingsActivity(intent, view)
+ }
+
+ override fun onSeeAllClicked(view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.SEE_ALL_CLICKED)
+ startSettingsActivity(Intent(ACTION_PREVIOUSLY_CONNECTED_DEVICE), view)
+ }
+
+ override fun onPairNewDeviceClicked(view: View) {
+ uiEventLogger.log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+ startSettingsActivity(Intent(ACTION_PAIR_NEW_DEVICE), view)
+ }
+
private fun dismissDialog() {
job?.cancel()
job = null
dialog?.dismiss()
dialog = null
}
+
+ private fun startSettingsActivity(intent: Intent, view: View) {
+ dialog?.run {
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ activityStarter.postStartActivityDismissingKeyguard(
+ intent,
+ 0,
+ dialogLaunchAnimator.createActivityLaunchController(view)
+ )
+ }
+ }
}
internal interface BluetoothTileDialogCallback {
- // TODO(b/298124674): Add click events for gear, see all and pair new device.
+ fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
+ fun onSeeAllClicked(view: View)
+ fun onPairNewDeviceClicked(view: View)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index fd57fd4..a16a9f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -52,16 +52,16 @@
cachedBluetoothDevice = cachedDevice,
deviceName = cachedDevice.name,
connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
+ ?: context.getString(connected),
iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
+ Pair(p.first, p.second)
+ },
background = context.getDrawable(backgroundOn),
isEnabled = !cachedDevice.isBusy,
alpha =
- if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
- else BluetoothTileDialog.ENABLED_ALPHA,
+ if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
+ else BluetoothTileDialog.ENABLED_ALPHA,
)
}
}
@@ -80,16 +80,15 @@
cachedBluetoothDevice = cachedDevice,
deviceName = cachedDevice.name,
connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(connected),
+ ?: context.getString(connected),
iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
- background = context.getDrawable(backgroundOn),
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
+ Pair(p.first, p.second)
+ },
isEnabled = !cachedDevice.isBusy,
alpha =
- if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
- else BluetoothTileDialog.ENABLED_ALPHA,
+ if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
+ else BluetoothTileDialog.ENABLED_ALPHA,
)
}
}
@@ -108,15 +107,15 @@
cachedBluetoothDevice = cachedDevice,
deviceName = cachedDevice.name,
connectionSummary = cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
- ?: context.getString(saved),
+ ?: context.getString(saved),
iconWithDescription =
- BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
- Pair(p.first, p.second)
- },
+ BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let { p ->
+ Pair(p.first, p.second)
+ },
isEnabled = !cachedDevice.isBusy,
alpha =
- if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
- else BluetoothTileDialog.ENABLED_ALPHA,
+ if (cachedDevice.isBusy) BluetoothTileDialog.DISABLED_ALPHA
+ else BluetoothTileDialog.ENABLED_ALPHA,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index 6ffb614..fcd0ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -20,6 +20,7 @@
import android.bluetooth.BluetoothDevice
import android.content.Context
import android.media.AudioManager
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -49,6 +50,7 @@
private val audioManager: AudioManager,
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(),
private val localBluetoothManager: LocalBluetoothManager?,
+ private val uiEventLogger: UiEventLogger,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
@@ -147,12 +149,14 @@
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
if (!BluetoothUtils.isActiveMediaDevice(deviceItem.cachedBluetoothDevice)) {
deviceItem.cachedBluetoothDevice.setActive()
+ uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
isClicked = true
}
}
DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> {}
DeviceItemType.SAVED_BLUETOOTH_DEVICE -> {
deviceItem.cachedBluetoothDevice.connect()
+ uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
isClicked = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3a88504..6f69ea81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3429,6 +3429,7 @@
@Override
public void setIsLaunchingActivityOverLockscreen(boolean isLaunchingActivityOverLockscreen) {
mIsLaunchingActivityOverLockscreen = isLaunchingActivityOverLockscreen;
+ mKeyguardViewMediator.launchingActivityOverLockscreen(mIsLaunchingActivityOverLockscreen);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
index e1d177d..89fa55b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
@@ -26,6 +26,7 @@
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.DISABLED_ALPHA
@@ -59,13 +60,16 @@
@Mock private lateinit var drawable: Drawable
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
private lateinit var icon: Pair<Drawable, String>
private lateinit var bluetoothTileDialog: BluetoothTileDialog
private lateinit var deviceItem: DeviceItem
@Before
fun setUp() {
- bluetoothTileDialog = BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, mContext)
+ bluetoothTileDialog =
+ BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
DeviceItem(
@@ -83,7 +87,7 @@
fun testShowDialog_createRecyclerViewWithAdapter() {
bluetoothTileDialog.show()
- val recyclerView = bluetoothTileDialog.findViewById<RecyclerView>(R.id.device_list)
+ val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
assertThat(bluetoothTileDialog.isShowing).isTrue()
assertThat(recyclerView).isNotNull()
@@ -94,11 +98,16 @@
@Test
fun testShowDialog_displayBluetoothDevice() {
- bluetoothTileDialog = BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, mContext)
+ bluetoothTileDialog =
+ BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
bluetoothTileDialog.show()
- bluetoothTileDialog.onDeviceItemUpdated(listOf(deviceItem), false)
+ bluetoothTileDialog.onDeviceItemUpdated(
+ listOf(deviceItem),
+ showSeeAll = false,
+ showPairNewDevice = false
+ )
- val recyclerView = bluetoothTileDialog.findViewById<RecyclerView>(R.id.device_list)
+ val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
assertThat(adapter.itemCount).isEqualTo(1)
assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
@@ -114,16 +123,17 @@
val view =
LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
val viewHolder =
- BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, mContext)
- .Adapter()
+ BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+ .Adapter(bluetoothTileDialogCallback)
.DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, 0)
- val container = view.findViewById<View>(R.id.bluetooth_device)
+ viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback)
+ val container = view.requireViewById<View>(R.id.bluetooth_device)
+ val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
assertThat(container).isNotNull()
- assertThat(container!!.isEnabled).isTrue()
+ assertThat(container.isEnabled).isTrue()
assertThat(container.alpha).isEqualTo(ENABLED_ALPHA)
- assertThat(container.hasOnClickListeners()).isTrue()
+ assertThat(deviceView.hasOnClickListeners()).isTrue()
}
@Test
@@ -134,30 +144,40 @@
val view =
LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
val viewHolder =
- BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, mContext)
- .Adapter()
+ BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
+ .Adapter(bluetoothTileDialogCallback)
.DeviceItemViewHolder(view)
- viewHolder.bind(deviceItem, 0)
- val container = view.findViewById<View>(R.id.bluetooth_device)
+ viewHolder.bind(deviceItem, 0, bluetoothTileDialogCallback)
+ val container = view.requireViewById<View>(R.id.bluetooth_device_row)
+ val deviceView = view.requireViewById<View>(R.id.bluetooth_device)
assertThat(container).isNotNull()
- assertThat(container!!.isEnabled).isFalse()
+ assertThat(container.isEnabled).isFalse()
assertThat(container.alpha).isEqualTo(DISABLED_ALPHA)
- assertThat(container.hasOnClickListeners()).isTrue()
+ assertThat(deviceView.hasOnClickListeners()).isTrue()
}
@Test
- fun testOnDeviceUpdated_hideSeeAll() {
- bluetoothTileDialog = BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, mContext)
+ fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
+ bluetoothTileDialog =
+ BluetoothTileDialog(ENABLED, bluetoothTileDialogCallback, uiEventLogger, mContext)
bluetoothTileDialog.show()
- bluetoothTileDialog.onDeviceItemUpdated(listOf(deviceItem), false)
+ bluetoothTileDialog.onDeviceItemUpdated(
+ listOf(deviceItem),
+ showSeeAll = false,
+ showPairNewDevice = true
+ )
- val seeAllLayout = bluetoothTileDialog.findViewById<View>(R.id.see_all_layout)
- val recyclerView = bluetoothTileDialog.findViewById<RecyclerView>(R.id.device_list)
+ val seeAllLayout = bluetoothTileDialog.requireViewById<View>(R.id.see_all_layout_group)
+ val pairNewLayout =
+ bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_layout_group)
+ val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
val adapter = recyclerView?.adapter as BluetoothTileDialog.Adapter
assertThat(seeAllLayout).isNotNull()
- assertThat(seeAllLayout!!.visibility).isEqualTo(GONE)
+ assertThat(seeAllLayout.visibility).isEqualTo(GONE)
+ assertThat(pairNewLayout).isNotNull()
+ assertThat(pairNewLayout.visibility).isEqualTo(VISIBLE)
assertThat(adapter.itemCount).isEqualTo(1)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index 975f1e2..7157cce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -18,10 +18,14 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.view.View
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
@@ -38,6 +42,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
@@ -61,8 +66,16 @@
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
+ @Mock private lateinit var activityStarter: ActivityStarter
+
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
+
+ @Mock private lateinit var deviceItem: DeviceItem
+
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
@@ -77,6 +90,8 @@
deviceItemInteractor,
bluetoothStateInteractor,
dialogLaunchAnimator,
+ activityStarter,
+ uiEventLogger,
testScope.backgroundScope,
dispatcher,
)
@@ -96,6 +111,7 @@
assertThat(bluetoothTileDialogViewModel.dialog).isNotNull()
verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), any())
assertThat(bluetoothTileDialogViewModel.dialog?.isShowing).isTrue()
+ verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
}
}
@@ -140,4 +156,16 @@
verify(bluetoothStateInteractor).updateBluetoothStateFlow
}
}
+
+ @Test
+ fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
+ `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ bluetoothTileDialogViewModel.showDialog(context, null)
+
+ val clickedView = View(context)
+ bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+
+ verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+ verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index df9914a..07a95ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -23,6 +23,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
@@ -67,6 +68,8 @@
@Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+
private lateinit var interactor: DeviceItemInteractor
private lateinit var dispatcher: CoroutineDispatcher
@@ -83,6 +86,7 @@
audioManager,
adapter,
localBluetoothManager,
+ uiEventLogger,
testScope.backgroundScope,
dispatcher
)
diff --git a/services/Android.bp b/services/Android.bp
index 9264172..f237095 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -34,17 +34,18 @@
},
}
-// Opt-in config for optimizing and shrinking the services target using R8.
-// Enabled via `export SYSTEM_OPTIMIZE_JAVA=true`, or explicitly in Make via the
+// Config to control optimizing and shrinking the services target using R8.
+// Set via `export SYSTEM_OPTIMIZE_JAVA=true|false`, or explicitly in Make via the
// `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA` variable.
-// TODO(b/196084106): Enable optimizations by default after stabilizing and
-// building out retrace infrastructure.
soong_config_module_type {
name: "system_optimized_java_defaults",
module_type: "java_defaults",
config_namespace: "ANDROID",
bool_variables: ["SYSTEM_OPTIMIZE_JAVA"],
- properties: ["optimize"],
+ properties: [
+ "optimize",
+ "dxflags",
+ ],
}
system_optimized_java_defaults {
@@ -75,6 +76,9 @@
// permission subpackage to prune unused jarjar'ed Kotlin dependencies.
proguard_flags_files: ["proguard_permission.flags"],
},
+ // Explicitly configure R8 to preserve debug info, as this path should
+ // really only allow stripping of permission-specific code and deps.
+ dxflags: ["--debug"],
},
},
},
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index f328b22..31e5cb8 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -53,7 +53,7 @@
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
@@ -131,7 +131,7 @@
* @see DisplayManager#VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public static final long MAKE_VIRTUAL_DISPLAY_FLAGS_CONSISTENT_WITH_DISPLAY_MANAGER =
294837146L;
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index cd867f6..2e5f2dc 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -408,12 +408,6 @@
}
}
- void stop() {
- if (mEglContext != null && mEglDisplay != null) {
- EGL14.eglDestroyContext(mEglDisplay, mEglContext);
- }
- }
-
/**
* Draws an animation frame showing the color fade activated at the
* specified level.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bc81491..83f4df9 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -3577,8 +3577,7 @@
DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
int displayId, int displayState) {
- return new DisplayPowerState(blanker, colorFade, displayId, displayState,
- new Handler(/*async=*/ true));
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState);
}
DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 90a8490..b0d293a 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -324,6 +324,8 @@
// Must only be accessed on the handler thread.
private DisplayPowerState mPowerState;
+
+
// The currently active screen on unblocker. This field is non-null whenever
// we are waiting for a callback to release it and unblock the screen.
private ScreenOnUnblocker mPendingScreenOnUnblocker;
@@ -2914,8 +2916,7 @@
DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
int displayId, int displayState) {
- return new DisplayPowerState(blanker, colorFade, displayId, displayState,
- new Handler(/*async=*/ true));
+ return new DisplayPowerState(blanker, colorFade, displayId, displayState);
}
DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 85c6a6d..2c257a1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -74,9 +74,8 @@
private volatile boolean mStopped;
DisplayPowerState(
- DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState,
- Handler handler) {
- mHandler = handler;
+ DisplayBlanker blanker, ColorFade colorFade, int displayId, int displayState) {
+ mHandler = new Handler(true /*async*/);
mChoreographer = Choreographer.getInstance();
mBlanker = blanker;
mColorFade = colorFade;
@@ -318,7 +317,6 @@
mStopped = true;
mPhotonicModulator.interrupt();
dismissColorFade();
- stopColorFade();
mCleanListener = null;
mHandler.removeCallbacksAndMessages(null);
}
@@ -378,11 +376,6 @@
}
}
- // Clears up color fade resources.
- private void stopColorFade() {
- if (mColorFade != null) mColorFade.stop();
- }
-
private final Runnable mScreenUpdateRunnable = new Runnable() {
@Override
public void run() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index 7045e65..c01bc20 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -868,6 +868,28 @@
}
@ServiceThreadOnly
+ void removeUnusedLocalDevices(ArrayList<HdmiCecLocalDevice> allocatedDevices) {
+ ArrayList<Integer> deviceTypesToRemove = new ArrayList<>();
+ for (int i = 0; i < mLocalDevices.size(); i++) {
+ int deviceType = mLocalDevices.keyAt(i);
+ boolean shouldRemoveLocalDevice = allocatedDevices.stream().noneMatch(
+ localDevice -> localDevice.getDeviceInfo() != null
+ && localDevice.getDeviceInfo().getDeviceType() == deviceType);
+ if (shouldRemoveLocalDevice) {
+ deviceTypesToRemove.add(deviceType);
+ }
+ }
+ for (Integer deviceType : deviceTypesToRemove) {
+ mLocalDevices.remove(deviceType);
+ }
+ }
+
+ @ServiceThreadOnly
+ void removeLocalDeviceWithType(int deviceType) {
+ mLocalDevices.remove(deviceType);
+ }
+
+ @ServiceThreadOnly
public void clearDeviceList() {
assertRunOnServiceThread();
for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 232bc47..429db5e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1313,9 +1313,6 @@
localDevice.init();
localDevices.add(localDevice);
}
- // It's now safe to flush existing local devices from mCecController since they were
- // already moved to 'localDevices'.
- clearCecLocalDevices();
mHdmiCecNetwork.clearDeviceList();
allocateLogicalAddress(localDevices, initiatedBy);
}
@@ -1344,6 +1341,7 @@
if (logicalAddress == Constants.ADDR_UNREGISTERED) {
Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType
+ "]");
+ mHdmiCecNetwork.removeLocalDeviceWithType(deviceType);
} else {
// Set POWER_STATUS_ON to all local devices because they share
// lifetime
@@ -1352,6 +1350,8 @@
deviceType,
HdmiControlManager.POWER_STATUS_ON, getCecVersion());
localDevice.setDeviceInfo(deviceInfo);
+ // If a local device of the same type already exists, it will be
+ // replaced.
mHdmiCecNetwork.addLocalDevice(deviceType, localDevice);
mHdmiCecNetwork.addCecDevice(localDevice.getDeviceInfo());
mCecController.addLogicalAddress(logicalAddress);
@@ -1367,6 +1367,10 @@
// since we reallocate the logical address only.
onInitializeCecComplete(initiatedBy);
}
+ // We remove local devices here, instead of before the start of
+ // address allocation, to prevent multiple local devices of the
+ // same type from existing simultaneously.
+ mHdmiCecNetwork.removeUnusedLocalDevices(allocatedDevices);
mAddressAllocated = true;
notifyAddressAllocated(allocatedDevices, initiatedBy);
// Reinvoke the saved display status callback once the local
@@ -1386,9 +1390,19 @@
}
}
+ /**
+ * Notifies local devices that address allocation finished.
+ * @param devices - list of local devices allocated.
+ * @param initiatedBy - reason for the address allocation.
+ */
+ @VisibleForTesting
@ServiceThreadOnly
- private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
+ public void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
assertRunOnServiceThread();
+ if (devices == null || devices.isEmpty()) {
+ Slog.w(TAG, "No local device to notify.");
+ return;
+ }
List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer();
for (HdmiCecLocalDevice device : devices) {
int address = device.getDeviceInfo().getLogicalAddress();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d824534..a0c7870 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2887,7 +2887,7 @@
final boolean animate;
if (mStartingData != null) {
if (mStartingData.mWaitForSyncTransactionCommit
- || mTransitionController.inCollectingTransition(startingWindow)) {
+ || mTransitionController.isCollecting(this)) {
mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
return;
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 2d281c4..07ffa69e 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -108,4 +108,13 @@
boolean hasImeSurface() {
return false;
}
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " waitForSyncTransactionCommit=" + mWaitForSyncTransactionCommit
+ + " removeAfterTransaction= " + mRemoveAfterTransaction
+ + "}";
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 84d1a45..f604932 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16017,16 +16017,20 @@
}
@Override
- public PackagePolicy getCredentialManagerPolicy() {
+ public PackagePolicy getCredentialManagerPolicy(int userId) {
if (!mHasFeature) {
return null;
}
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
canWriteCredentialManagerPolicy(caller) || canQueryAdminPolicy(caller));
+ if (userId != caller.getUserId()) {
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS));
+ }
synchronized (getLockObject()) {
- ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(userId);
return (admin != null) ? admin.mCredentialManagerPolicy : null;
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 9174899..a56b59a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -1255,21 +1255,6 @@
}
@Test
- public void testPowerStateStopsOnDpcStop() {
- // Set up
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1);
-
- // Stop dpc
- mHolder.dpc.stop();
- advanceTime(1);
-
- // Ensure dps has stopped
- verify(mHolder.displayPowerState, times(1)).stop();
- }
-
- @Test
public void testRampRateForHdrContent_HdrClamperOff() {
float hdrBrightness = 0.8f;
float clampedBrightness = 0.6f;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 412b65f..0572117 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1191,21 +1191,6 @@
}
@Test
- public void testPowerStateStopsOnDpcStop() {
- // Set up
- DisplayPowerRequest dpr = new DisplayPowerRequest();
- mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
- advanceTime(1);
-
- // Stop dpc
- mHolder.dpc.stop();
- advanceTime(1);
-
- // Ensure dps has stopped
- verify(mHolder.displayPowerState, times(1)).stop();
- }
-
- @Test
public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
deleted file mode 100644
index 167a412..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerStateTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.Mockito.times;
-
-import android.os.Handler;
-import android.os.test.TestLooper;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-
-@SmallTest
-public class DisplayPowerStateTest {
- private static final int DISPLAY_ID = 123;
-
- private DisplayPowerState mDisplayPowerState;
- private TestLooper mTestLooper;
- @Mock
- private DisplayBlanker mDisplayBlankerMock;
- @Mock
- private ColorFade mColorFadeMock;
-
- @Rule
- public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Before
- public void setUp() {
- mTestLooper = new TestLooper();
- mDisplayPowerState = new DisplayPowerState(
- mDisplayBlankerMock, mColorFadeMock, DISPLAY_ID, Display.STATE_ON,
- new Handler(mTestLooper.getLooper()));
- }
-
- @Test
- public void testColorFadeStopsOnDpsStop() {
- mDisplayPowerState.stop();
- verify(mColorFadeMock, times(1)).stop();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 0d172fdb..708ee35 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -21,8 +21,15 @@
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
+import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_3;
import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_HOTPLUG;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_SCREEN_ON;
+import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_SOUNDBAR_MODE;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -51,6 +58,7 @@
import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
import android.hardware.hdmi.IHdmiControlStatusChangeListener;
import android.hardware.hdmi.IHdmiVendorCommandListener;
+import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
@@ -71,6 +79,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
/**
* Tests for {@link HdmiControlService} class.
@@ -137,7 +146,7 @@
mLocalDevices.add(mAudioSystemDeviceSpy);
mLocalDevices.add(mPlaybackDeviceSpy);
- mHdmiPortInfo = new HdmiPortInfo[4];
+ mHdmiPortInfo = new HdmiPortInfo[5];
mHdmiPortInfo[0] =
new HdmiPortInfo.Builder(1, HdmiPortInfo.PORT_INPUT, 0x2100)
.setCecSupported(true)
@@ -166,6 +175,13 @@
.setArcSupported(false)
.setEarcSupported(false)
.build();
+ mHdmiPortInfo[4] =
+ new HdmiPortInfo.Builder(4, HdmiPortInfo.PORT_OUTPUT, 0x3000)
+ .setCecSupported(true)
+ .setMhlSupported(false)
+ .setArcSupported(false)
+ .setEarcSupported(false)
+ .build();
mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlServiceSpy.initService();
mWakeLockSpy = spy(new FakePowerManagerWrapper.FakeWakeLockWrapper());
@@ -1396,6 +1412,207 @@
}
@Test
+ public void triggerMultipleAddressAllocations_uniqueLocalDevicePerDeviceType() {
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ // Wake up process that will trigger the address allocation to start.
+ mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // Hotplug In will trigger the address allocation to start.
+ mHdmiControlServiceSpy.onHotplug(4, true);
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_HOTPLUG));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The first allocation finished. The second allocation is still in progress.
+ HdmiCecLocalDevicePlayback firstAllocatedPlayback = mHdmiControlServiceSpy.playback();
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The second allocation finished.
+ HdmiCecLocalDevicePlayback secondAllocatedPlayback = mHdmiControlServiceSpy.playback();
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_HOTPLUG));
+ // Local devices have the same identity.
+ assertTrue(firstAllocatedPlayback == secondAllocatedPlayback);
+ }
+
+ @Test
+ public void triggerMultipleAddressAllocations_keepLastAllocatedAddress() {
+ // First logical address for playback is free.
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
+ mTestLooper.dispatchAll();
+
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ // Wake up process that will trigger the address allocation to start.
+ mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+
+ // First logical address for playback is busy.
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SCREEN_ON));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+ mTestLooper.dispatchAll();
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The first allocation finished. The second allocation is still in progress.
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The second allocation finished. Second logical address for playback is used.
+ HdmiCecLocalDevicePlayback allocatedPlayback = mHdmiControlServiceSpy.playback();
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SCREEN_ON));
+ assertThat(allocatedPlayback.getDeviceInfo().getLogicalAddress())
+ .isEqualTo(ADDR_PLAYBACK_2);
+ }
+
+ @Test
+ public void triggerMultipleAddressAllocations_toggleSoundbarMode_addThenRemoveAudioSystem() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ // Enabling Dynamic soundbar mode will trigger address allocation.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // Disabling Dynamic soundbar mode will trigger another address allocation.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+ mTestLooper.dispatchAll();
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The first allocation finished. The second allocation is still in progress.
+ // The audio system is present in the network.
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The second allocation finished. The audio system is not present in the network.
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+ }
+
+ @Test
+ public void triggerMultipleAddressAllocations_toggleSoundbarMode_removeThenAddAudioSystem() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
+ // Enable the setting and check if the audio system local device is found in the network.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+
+ long allocationDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setLogicalAddressAllocationDelay(allocationDelay);
+ mTestLooper.dispatchAll();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ // Disabling Dynamic soundbar mode will trigger address allocation.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+ mTestLooper.dispatchAll();
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // Enabling Dynamic soundbar mode will trigger another address allocation.
+ mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+ HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+ mTestLooper.dispatchAll();
+ verify(mHdmiControlServiceSpy, times(1))
+ .allocateLogicalAddress(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The first allocation finished. The second allocation is still in progress.
+ // The audio system is not present in the network.
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+ Mockito.clearInvocations(mHdmiControlServiceSpy);
+
+ mTestLooper.moveTimeForward(allocationDelay / 2);
+ mTestLooper.dispatchAll();
+ // The second allocation finished. The audio system is present in the network.
+ verify(mHdmiControlServiceSpy, times(1))
+ .notifyAddressAllocated(any(), eq(INITIATED_BY_SOUNDBAR_MODE));
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNotNull();
+ }
+
+ @Test
+ public void failedAddressAllocation_noLocalDevice() {
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
+ mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_3, SendMessageResult.SUCCESS);
+ mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.clearCecLocalDevices();
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlServiceSpy.playback()).isNull();
+ assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
+ }
+
+ @Test
public void earcIdle_blocksArcConnection() {
mHdmiControlServiceSpy.clearEarcLocalDevice();
HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
index 030b292..b3c9c96 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonLandscape.kt
@@ -31,7 +31,8 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonLandscape :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
- @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ // TODO: Missing CUJ (b/300078127)
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
index 770da143..29c11a4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButton3ButtonPortrait.kt
@@ -31,7 +31,8 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButton3ButtonPortrait :
CloseAppBackButton(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
- @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ // TODO: Missing CUJ (b/300078127)
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
index 4b2206d7..1bb4a25 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavLandscape.kt
@@ -31,7 +31,8 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavLandscape :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
- @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ // TODO: Missing CUJ (b/300078127)
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
index 54b471e..17cb6e1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/close/flicker/CloseAppBackButtonGesturalNavPortrait.kt
@@ -31,7 +31,8 @@
@RunWith(FlickerServiceJUnit4ClassRunner::class)
class CloseAppBackButtonGesturalNavPortrait :
CloseAppBackButton(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
- @ExpectedScenarios(["APP_CLOSE_TO_HOME"])
+ // TODO: Missing CUJ (b/300078127)
+ @ExpectedScenarios(["ENTIRE_TRACE"])
@Test
override fun closeAppBackButtonTest() = super.closeAppBackButtonTest()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
index 93130c4..a1708fe 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsBack.kt
@@ -19,6 +19,7 @@
import android.app.Instrumentation
import android.tools.common.NavBar
import android.tools.common.Rotation
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -46,7 +47,9 @@
tapl.setExpectedRotation(rotation.value)
tapl.setIgnoreTaskbarVisibility(true)
testApp1.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
testApp2.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
}
@Test
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index f867c98..9198ae1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -102,6 +102,20 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".PortraitImmersiveActivity"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitImmersiveActivity"
+ android:immersive="true"
+ android:resizeableActivity="true"
+ android:screenOrientation="portrait"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:configChanges="screenSize"
+ android:label="PortraitImmersiveActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
<activity android:name=".LaunchTransparentActivity"
android:resizeableActivity="false"
android:screenOrientation="portrait"
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 7c5e9a3..8b334c0 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -73,6 +73,12 @@
FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity");
}
+ public static class PortraitImmersiveActivity {
+ public static final String LABEL = "PortraitImmersiveActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".PortraitImmersiveActivity");
+ }
+
public static class TransparentActivity {
public static final String LABEL = "TransparentActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitImmersiveActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitImmersiveActivity.java
new file mode 100644
index 0000000..0a7f81c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PortraitImmersiveActivity.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+public class PortraitImmersiveActivity extends GameActivity {
+}
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
index 235bd47..fd888ec 100644
--- a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_der.der
Binary files differ
diff --git a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
index 413e3c0..66f7bfd 100644
--- a/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
+++ b/tests/NetworkSecurityConfigTest/res/raw/ca_certs_pem.pem
@@ -1,35 +1,31 @@
-----BEGIN CERTIFICATE-----
-MIIDfTCCAuagAwIBAgIDErvmMA0GCSqGSIb3DQEBBQUAME4xCzAJBgNVBAYTAlVT
-MRAwDgYDVQQKEwdFcXVpZmF4MS0wKwYDVQQLEyRFcXVpZmF4IFNlY3VyZSBDZXJ0
-aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDIwNTIxMDQwMDAwWhcNMTgwODIxMDQwMDAw
-WjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UE
-AxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
-CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9m
-OSm9BXiLnTjoBbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIu
-T8rxh0PBFpVXLVDviS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6c
-JmTM386DGXHKTubU1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmR
-Cw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5asz
-PeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo4HwMIHtMB8GA1UdIwQYMBaAFEjm
-aPkr0rKV10fYIyAQTzOYkJ/UMB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrM
-TjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjA6BgNVHR8EMzAxMC+g
-LaArhilodHRwOi8vY3JsLmdlb3RydXN0LmNvbS9jcmxzL3NlY3VyZWNhLmNybDBO
-BgNVHSAERzBFMEMGBFUdIAAwOzA5BggrBgEFBQcCARYtaHR0cHM6Ly93d3cuZ2Vv
-dHJ1c3QuY29tL3Jlc291cmNlcy9yZXBvc2l0b3J5MA0GCSqGSIb3DQEBBQUAA4GB
-AHbhEm5OSxYShjAGsoEIz/AIx8dxfmbuwu3UOx//8PDITtZDOLC5MH0Y0FWDomrL
-NhGc6Ehmo21/uBPUR/6LWlxz/K7ZGzIZOKuXNBSqltLroxwUCEm2u+WR74M26x1W
-b8ravHNjkOR/ez4iyz0H7V84dJzjA1BOoa+Y7mHyhD8S
------END CERTIFICATE-----
------BEGIN CERTIFICATE-----
-MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
-A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
-cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
-MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
-BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
-YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
-ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
-BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
-I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
-CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
-2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
-2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
+MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
+MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
+CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx
+OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT
+GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63
+ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS
+iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k
+KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ
+DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk
+j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5
+cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW
+CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499
+iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei
+Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap
+sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b
+9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf
+BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw
+JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH
+MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al
+oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy
+MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF
+AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9
+NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9
+WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw
+9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy
++qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
+d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
-----END CERTIFICATE-----
diff --git a/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
index 5d23d36e..99106ad 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/domain_whitespace.xml
@@ -5,7 +5,7 @@
</domain>
<domain> developer.android.com </domain>
<pin-set>
- <pin digest="SHA-256"> 7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y= </pin>
+ <pin digest="SHA-256"> zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w= </pin>
</pin-set>
</domain-config>
</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
index d45fd77..232f88f 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/nested_domains.xml
@@ -9,7 +9,7 @@
<domain-config>
<domain>developer.android.com</domain>
<pin-set>
- <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ <pin digest="SHA-256">zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=</pin>
</pin-set>
</domain-config>
</domain-config>
diff --git a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
index 1773d280..7cc81b0 100644
--- a/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
+++ b/tests/NetworkSecurityConfigTest/res/xml/pins1.xml
@@ -3,7 +3,7 @@
<domain-config>
<domain>android.com</domain>
<pin-set>
- <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
+ <pin digest="SHA-256">zCTnfLwLKbS9S2sbp+uFz4KZOocFvXxkV06Ce9O5M2w=</pin>
</pin-set>
</domain-config>
</network-security-config>
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
index 047be16..0494f17 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/NetworkSecurityConfigTests.java
@@ -22,23 +22,17 @@
import android.test.ActivityUnitTestCase;
import android.util.ArraySet;
import android.util.Pair;
+
+import com.android.org.conscrypt.TrustedCertificateStore;
+
import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.URL;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.TrustManager;
-import com.android.org.conscrypt.TrustedCertificateStore;
+import javax.net.ssl.SSLContext;
public class NetworkSecurityConfigTests extends ActivityUnitTestCase<Activity> {
@@ -46,9 +40,9 @@
super(Activity.class);
}
- // SHA-256 of the G2 intermediate CA for android.com (as of 10/2015).
- private static final byte[] G2_SPKI_SHA256
- = hexToBytes("ec722969cb64200ab6638f68ac538e40abab5b19a6485661042a1061c4612776");
+ // SHA-256 of the GTS intermediate CA (CN = GTS CA 1C3) for android.com (as of 09/2023).
+ private static final byte[] GTS_INTERMEDIATE_SPKI_SHA256 =
+ hexToBytes("cc24e77cbc0b29b4bd4b6b1ba7eb85cf82993a8705bd7c64574e827bd3b9336c");
private static final byte[] TEST_CA_BYTES
= hexToBytes(
@@ -161,7 +155,7 @@
public void testGoodPin() throws Exception {
ArraySet<Pin> pins = new ArraySet<Pin>();
- pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
+ pins.add(new Pin("SHA-256", GTS_INTERMEDIATE_SPKI_SHA256));
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
@@ -247,7 +241,7 @@
public void testWithUrlConnection() throws Exception {
ArraySet<Pin> pins = new ArraySet<Pin>();
- pins.add(new Pin("SHA-256", G2_SPKI_SHA256));
+ pins.add(new Pin("SHA-256", GTS_INTERMEDIATE_SPKI_SHA256));
NetworkSecurityConfig domain = new NetworkSecurityConfig.Builder()
.setPinSet(new PinSet(pins, Long.MAX_VALUE))
.addCertificatesEntryRef(
@@ -304,7 +298,7 @@
} finally {
// Delete the user added CA. We don't know the alias so just delete them all.
for (String alias : store.aliases()) {
- if (store.isUser(alias)) {
+ if (TrustedCertificateStore.isUser(alias)) {
try {
store.deleteCertificateEntry(alias);
} catch (Exception ignored) {
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
index 9dec21b..39b5cb4c 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/TestUtils.java
@@ -16,19 +16,20 @@
package android.security.net.config;
+import static org.junit.Assert.fail;
+
import android.content.pm.ApplicationInfo;
import android.os.Build;
-import java.net.Socket;
+
import java.net.URL;
+
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.TrustManager;
+import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;
-import junit.framework.Assert;
-
-public final class TestUtils extends Assert {
+public final class TestUtils {
private TestUtils() {
}
@@ -36,8 +37,8 @@
public static void assertConnectionFails(SSLContext context, String host, int port)
throws Exception {
try {
- Socket s = context.getSocketFactory().createSocket(host, port);
- s.getInputStream();
+ SSLSocket s = (SSLSocket) context.getSocketFactory().createSocket(host, port);
+ s.startHandshake();
fail("Expected connection to " + host + ":" + port + " to fail.");
} catch (SSLHandshakeException expected) {
}
@@ -45,7 +46,8 @@
public static void assertConnectionSucceeds(SSLContext context, String host, int port)
throws Exception {
- Socket s = context.getSocketFactory().createSocket(host, port);
+ SSLSocket s = (SSLSocket) context.getSocketFactory().createSocket(host, port);
+ s.startHandshake();
s.getInputStream();
}
diff --git a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
index 4b7a014..81e05c1 100644
--- a/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
+++ b/tests/NetworkSecurityConfigTest/src/android/security/net/config/XmlConfigTests.java
@@ -16,26 +16,18 @@
package android.security.net.config;
-import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
-import android.util.ArraySet;
-import android.util.Pair;
+
import java.io.IOException;
import java.net.InetAddress;
-import java.net.Socket;
-import java.net.URL;
import java.security.KeyStore;
import java.security.Provider;
-import java.security.Security;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.Set;
-import javax.net.ssl.HttpsURLConnection;
+
import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
@@ -52,7 +44,7 @@
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
@@ -72,7 +64,7 @@
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertTrue(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
@@ -91,14 +83,14 @@
NetworkSecurityConfig config = appConfig.getConfigForHostname("");
assertNotNull(config);
// Check defaults.
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertTrue(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
assertTrue(pinSet.pins.isEmpty());
// Check android.com.
config = appConfig.getConfigForHostname("android.com");
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
pinSet = config.getPins();
@@ -188,7 +180,7 @@
ApplicationConfig appConfig = new ApplicationConfig(source);
assertTrue(appConfig.hasPerDomainConfigs());
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
assertFalse(config.getTrustAnchors().isEmpty());
PinSet pinSet = config.getPins();
@@ -250,9 +242,9 @@
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
- assertEquals(2, config.getTrustAnchors().size());
+ assertEquals(1, config.getTrustAnchors().size());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);
@@ -267,9 +259,9 @@
ApplicationConfig appConfig = new ApplicationConfig(source);
// Check android.com.
NetworkSecurityConfig config = appConfig.getConfigForHostname("android.com");
- assertTrue(config.isCleartextTrafficPermitted());
+ assertFalse(config.isCleartextTrafficPermitted());
assertFalse(config.isHstsEnforced());
- assertEquals(2, config.getTrustAnchors().size());
+ assertEquals(1, config.getTrustAnchors().size());
// Try connections.
SSLContext context = TestUtils.getSSLContext(source);
TestUtils.assertConnectionSucceeds(context, "android.com", 443);