Merge "Adds RearDisplay education dialog to SysUI" into tm-qpr-dev am: 34b99a23d3 am: 7c406753e6

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20482626

Change-Id: I8a53ccb442379a6b6c5344e12a20d9e8f7e99467
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Kenneth Ford
2022-11-24 16:47:43 +00:00
committed by Automerger Merge Worker
11 changed files with 479 additions and 0 deletions

View File

@@ -126,6 +126,9 @@
<uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
<!-- DeviceStateManager -->
<uses-permission android:name="android.permission.CONTROL_DEVICE_STATE" />
<!-- DreamManager -->
<uses-permission android:name="android.permission.READ_DREAM_STATE" />
<uses-permission android:name="android.permission.WRITE_DREAM_STATE" />

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center" >
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardElevation="0dp"
app:cardCornerRadius="28dp"
app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/rear_display_folded_animation"
android:layout_width="@dimen/rear_display_animation_width"
android:layout_height="@dimen/rear_display_animation_height"
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY"
app:lottie_rawRes="@raw/rear_display_folded"
app:lottie_autoPlay="true"
app:lottie_repeatMode="reverse"/>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_fold_bottom_sheet_title"
android:textAppearance="@style/TextAppearance.Dialog.Title"
android:lineSpacingExtra="2sp"
android:paddingTop="@dimen/rear_display_title_top_padding"
android:paddingBottom="@dimen/rear_display_title_bottom_padding"
android:gravity="center_horizontal|center_vertical"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_bottom_sheet_description"
android:textAppearance="@style/TextAppearance.Dialog.Body"
android:lineSpacingExtra="2sp"
android:translationY="-1.24sp"
android:gravity="center_horizontal|top"
/>
</LinearLayout>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center" >
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardElevation="0dp"
app:cardCornerRadius="28dp"
app:cardBackgroundColor="@color/rear_display_overlay_animation_background_color">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/rear_display_folded_animation"
android:layout_width="@dimen/rear_display_animation_width"
android:layout_height="@dimen/rear_display_animation_height"
android:layout_gravity="center"
android:contentDescription="@null"
android:scaleType="fitXY"
app:lottie_rawRes="@raw/rear_display_turnaround"
app:lottie_autoPlay="true"
app:lottie_repeatMode="reverse"/>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_unfold_bottom_sheet_title"
android:textAppearance="@style/TextAppearance.Dialog.Title"
android:lineSpacingExtra="2sp"
android:paddingTop="@dimen/rear_display_title_top_padding"
android:paddingBottom="@dimen/rear_display_title_bottom_padding"
android:gravity="center_horizontal|center_vertical"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_bottom_sheet_description"
android:textAppearance="@style/TextAppearance.Dialog.Body"
android:lineSpacingExtra="2sp"
android:translationY="-1.24sp"
android:gravity="center_horizontal|top"
/>
<TextView
android:id="@+id/rear_display_warning_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_bottom_sheet_warning"
android:textAppearance="@style/TextAppearance.Dialog.Body"
android:lineSpacingExtra="2sp"
android:gravity="center_horizontal|top"
/>
</LinearLayout>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -257,4 +257,8 @@
<color name="dream_overlay_clock_ambient_text_shadow_color">#4D000000</color>
<color name="dream_overlay_status_bar_key_text_shadow_color">#66000000</color>
<color name="dream_overlay_status_bar_ambient_text_shadow_color">#59000000</color>
<!-- Rear Display Education -->
<color name="rear_display_overlay_animation_background_color">#1E1B17</color>
<color name="rear_display_overlay_dialog_background_color">#1E1B17</color>
</resources>

View File

@@ -1623,4 +1623,10 @@
<dimen name="config_rounded_mask_size">0px</dimen>
<dimen name="config_rounded_mask_size_top">0px</dimen>
<dimen name="config_rounded_mask_size_bottom">0px</dimen>
<!-- Rear Display Education dimens -->
<dimen name="rear_display_animation_width">273dp</dimen>
<dimen name="rear_display_animation_height">200dp</dimen>
<dimen name="rear_display_title_top_padding">24dp</dimen>
<dimen name="rear_display_title_bottom_padding">16dp</dimen>
</resources>

View File

@@ -2847,4 +2847,17 @@
shortcut button on the lock screen. [CHAR LIMIT=NONE].
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
<!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_cancel">Cancel</string>
<!-- Text for the user to confirm they flipped the device around. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_confirm">Flip now</string>
<!-- Text for education page title to guide user to unfold phone. [CHAR_LIMIT=50] -->
<string name="rear_display_fold_bottom_sheet_title">Unfold phone for a better selfie</string>
<!-- Text for education page title to guide user to flip to the front display. [CHAR_LIMIT=50] -->
<string name="rear_display_unfold_bottom_sheet_title">Flip to front display for a better selfie?</string>
<!-- Text for education page description to suggest user to use rear selfie capture. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_description">Use the rear-facing camera for a wider photo with higher resolution.</string>
<!-- Text for education page description to warn user that the display will turn off if the button is clicked. [CHAR_LIMIT=NONE] -->
<string name="rear_display_bottom_sheet_warning"><b>&#x2731; This screen will turn off</b></string>
</resources>

View File

@@ -37,6 +37,7 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver
import com.android.systemui.media.taptotransfer.sender.MediaTttSenderCoordinator
import com.android.systemui.power.PowerUI
import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
@@ -250,4 +251,11 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(ChipbarCoordinator::class)
abstract fun bindChipbarController(sysui: ChipbarCoordinator): CoreStartable
/** Inject into RearDisplayDialogController) */
@Binds
@IntoMap
@ClassKey(RearDisplayDialogController::class)
abstract fun bindRearDisplayDialogController(sysui: RearDisplayDialogController): CoreStartable
}

View File

@@ -0,0 +1,208 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.reardisplay;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerGlobal;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* Provides an educational dialog to the user alerting them to what
* they may need to do to enter rear display mode. This may be to open the
* device if it is currently folded, or to confirm that they would like
* the content to move to the screen on their device that is aligned with
* the rear camera. This includes a device animation to provide more context
* to the user.
*
* We are suppressing lint for the VisibleForTests check because the use of
* DeviceStateManagerGlobal as in this file should not be encouraged for other use-cases.
* The lint check will notify any other use-cases that they are possibly doing something
* incorrectly.
*/
@SuppressLint("VisibleForTests") // TODO(b/260264542) Migrate away from DeviceStateManagerGlobal
@SysUISingleton
public class RearDisplayDialogController implements CoreStartable, CommandQueue.Callbacks {
private int[] mFoldedStates;
private boolean mStartedFolded;
private boolean mServiceNotified = false;
private int mAnimationRepeatCount = LottieDrawable.INFINITE;
private DeviceStateManagerGlobal mDeviceStateManagerGlobal;
private DeviceStateManager.DeviceStateCallback mDeviceStateManagerCallback =
new DeviceStateManagerCallback();
private final Context mContext;
private final CommandQueue mCommandQueue;
private final Executor mExecutor;
@VisibleForTesting
SystemUIDialog mRearDisplayEducationDialog;
@Inject
public RearDisplayDialogController(Context context, CommandQueue commandQueue,
@Main Executor executor) {
mContext = context;
mCommandQueue = commandQueue;
mExecutor = executor;
}
@Override
public void start() {
mCommandQueue.addCallback(this);
}
@Override
public void showRearDisplayDialog(int currentBaseState) {
initializeValues(currentBaseState);
createAndShowDialog();
}
private void createAndShowDialog() {
mServiceNotified = false;
Context dialogContext = mRearDisplayEducationDialog.getContext();
View dialogView;
if (mStartedFolded) {
dialogView = View.inflate(dialogContext,
R.layout.activity_rear_display_education, null);
} else {
dialogView = View.inflate(dialogContext,
R.layout.activity_rear_display_education_opened, null);
}
LottieAnimationView animationView = dialogView.findViewById(
R.id.rear_display_folded_animation);
animationView.setRepeatCount(mAnimationRepeatCount);
mRearDisplayEducationDialog.setView(dialogView);
configureDialogButtons();
mRearDisplayEducationDialog.show();
}
/**
* Configures the buttons on the dialog depending on the starting device posture
*/
private void configureDialogButtons() {
// If we are open, we need to provide a confirm option
if (!mStartedFolded) {
mRearDisplayEducationDialog.setPositiveButton(
R.string.rear_display_bottom_sheet_confirm,
(dialog, which) -> closeOverlayAndNotifyService(false), true);
}
mRearDisplayEducationDialog.setNegativeButton(R.string.rear_display_bottom_sheet_cancel,
(dialog, which) -> closeOverlayAndNotifyService(true), true);
mRearDisplayEducationDialog.setOnDismissListener(dialog -> {
// Dialog is being dismissed before we've notified the system server
if (!mServiceNotified) {
closeOverlayAndNotifyService(true);
}
});
}
/**
* Initializes properties and values we need when getting ready to show the dialog.
*
* Ensures we're not using old values from when the dialog may have been shown previously.
*/
private void initializeValues(int startingBaseState) {
mRearDisplayEducationDialog = new SystemUIDialog(mContext);
if (mFoldedStates == null) {
mFoldedStates = mContext.getResources().getIntArray(
com.android.internal.R.array.config_foldedDeviceStates);
}
mStartedFolded = isFoldedState(startingBaseState);
mDeviceStateManagerGlobal = DeviceStateManagerGlobal.getInstance();
mDeviceStateManagerGlobal.registerDeviceStateCallback(mDeviceStateManagerCallback,
mExecutor);
}
private boolean isFoldedState(int state) {
for (int i = 0; i < mFoldedStates.length; i++) {
if (mFoldedStates[i] == state) return true;
}
return false;
}
/**
* Closes the educational overlay, and notifies the system service if rear display mode
* should be cancelled or enabled.
*/
private void closeOverlayAndNotifyService(boolean shouldCancelRequest) {
mServiceNotified = true;
mDeviceStateManagerGlobal.unregisterDeviceStateCallback(mDeviceStateManagerCallback);
mDeviceStateManagerGlobal.onStateRequestOverlayDismissed(shouldCancelRequest);
}
/**
* TestAPI to allow us to set the folded states array, instead of reading from resources.
*/
@TestApi
void setFoldedStates(int[] foldedStates) {
mFoldedStates = foldedStates;
}
@TestApi
void setDeviceStateManagerCallback(
DeviceStateManager.DeviceStateCallback deviceStateManagerCallback) {
mDeviceStateManagerCallback = deviceStateManagerCallback;
}
@TestApi
void setAnimationRepeatCount(int repeatCount) {
mAnimationRepeatCount = repeatCount;
}
private class DeviceStateManagerCallback implements DeviceStateManager.DeviceStateCallback {
@Override
public void onBaseStateChanged(int state) {
if (mStartedFolded && !isFoldedState(state)) {
// We've opened the device, we can close the overlay
mRearDisplayEducationDialog.dismiss();
closeOverlayAndNotifyService(false);
} else if (!mStartedFolded && isFoldedState(state)) {
// We've closed the device, finish activity
mRearDisplayEducationDialog.dismiss();
closeOverlayAndNotifyService(true);
}
}
// We only care about physical device changes in this scenario
@Override
public void onStateChanged(int state) {}
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.reardisplay;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import android.hardware.devicestate.DeviceStateManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class RearDisplayDialogControllerTest extends SysuiTestCase {
@Mock
private CommandQueue mCommandQueue;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private static final int CLOSED_BASE_STATE = 0;
private static final int OPEN_BASE_STATE = 1;
@Test
public void testClosedDialogIsShown() {
RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
mCommandQueue, mFakeExecutor);
controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
controller.setFoldedStates(new int[]{0});
controller.setAnimationRepeatCount(0);
controller.showRearDisplayDialog(CLOSED_BASE_STATE);
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_warning_text_view);
assertNull(deviceOpenedWarningTextView);
}
@Test
public void testOpenDialogIsShown() {
RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
mCommandQueue, mFakeExecutor);
controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
controller.setFoldedStates(new int[]{0});
controller.setAnimationRepeatCount(0);
controller.showRearDisplayDialog(OPEN_BASE_STATE);
assertTrue(controller.mRearDisplayEducationDialog.isShowing());
View deviceOpenedWarningTextView = controller.mRearDisplayEducationDialog.findViewById(
R.id.rear_display_warning_text_view);
assertNotNull(deviceOpenedWarningTextView);
}
/**
* Empty device state manager callbacks, so we can verify that the correct
* dialogs are being created regardless of device state of the test device.
*/
private static class TestDeviceStateManagerCallback implements
DeviceStateManager.DeviceStateCallback {
@Override
public void onStateChanged(int state) { }
}
}