Implemented new preference setting screen for face based auto-rotation

Test: locally with phone and make -j64 RunSettingsRoboTests
ROBOTEST_FILTER="com.android.settings.display.SmartAutoRotatePreferenceControllerTest"

Bug: 172857585
Change-Id: I8aaeb9d726ff71f64fd241c01fe2a1268fff927e
This commit is contained in:
Abel Tesfaye
2020-11-19 20:37:20 +00:00
parent 5f20dd4ce2
commit a96775c58b
9 changed files with 612 additions and 1 deletions

View File

@@ -954,7 +954,7 @@
<category android:name="com.android.settings.SHORTCUT" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
android:value="com.android.settings.DisplaySettings" />
android:value="@string/rotate_settings_class" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>

View File

@@ -97,6 +97,15 @@
<!-- Description for the button that makes interface elements larger. [CHAR_LIMIT=NONE] -->
<string name="font_size_make_larger_desc">Make larger</string>
<!-- Auto rotate switchbar title. [CHAR_LIMIT=NONE] -->
<string name="auto_rotate_settings_primary_switch_title">Use Auto-Rotate</string>
<!-- Disclaimer for camera based rotate [CHAR_LIMIT=NONE] -->
<string name="smart_rotate_text_headline">
Face based Autorotate uses the front camera to see if and how someone is looking at the screen. It allows
for reading while lying down and images are never stored or sent to Google.&lt;br>&lt;br>
&lt;a href="<xliff:g example="http://www.google.com" id="url">http://support.google.com/mobile?p=telephony_rtt</xliff:g>">Learn more&lt;/a>
</string>
<string name="font_size_preview_text_headline">Sample text</string>
<string name="font_size_preview_text_title"
translation_description="Title text in sample text used to illustrate how the currently selected font size will appear to the user. NOTE: Translate manually. No not adopt any copyrighted material for translation.">
@@ -2704,8 +2713,18 @@
<!-- Display settings --><skip/>
<!-- Sound & display settings screen, section header for settings related to display -->
<string name="display_settings">Display</string>
<!-- Sound & display settings screen, section header for settings related to display -->
<string name="rotate_settings_class" translatable="false">com.android.settings.DisplaySettings</string>
<!-- Sound & display settings screen, accelerometer-based rotation check box label -->
<string name="accelerometer_title">Auto-rotate screen</string>
<!-- Sound & display settings screen, locked rotation check box label [CHAR LIMIT=30] -->
<string name="auto_rotate_option_off">Off</string>
<!-- Sound & display settings screen, accelerometer-based rotation check box label [CHAR LIMIT=30] -->
<string name="auto_rotate_option_on">On</string>
<!-- Sound & display settings screen, face-based rotation check box label [CHAR LIMIT=30] -->
<string name="auto_rotate_option_face_based">On - Face-based</string>
<!-- SmartAutoRotatePreferenceFragment settings screen, face-based rotation switch label [CHAR LIMIT=30] -->
<string name="auto_rotate_switch_face_based">Enable Face Detection</string>
<!-- Display settings screen, Color mode settings title [CHAR LIMIT=30] -->
<string name="color_mode_title">Colors</string>
<!-- Display settings screen, Color mode option for "natural(sRGB) color" [CHAR LIMIT=45] -->

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/accelerometer_title" >
<SwitchPreference
android:key="face_based_rotate"
android:title="@string/auto_rotate_switch_face_based"
settings:controller="com.android.settings.display.SmartAutoRotateController" />
<com.android.settingslib.widget.FooterPreference
android:icon="@drawable/ic_privacy_shield_24dp"
android:selectable="false"
settings:searchable="false" />
</PreferenceScreen>

View File

@@ -83,6 +83,7 @@ import com.android.settings.deviceinfo.aboutphone.MyDeviceInfoFragment;
import com.android.settings.deviceinfo.firmwareversion.FirmwareVersionSettings;
import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
import com.android.settings.display.NightDisplaySettings;
import com.android.settings.display.SmartAutoRotatePreferenceFragment;
import com.android.settings.display.darkmode.DarkModeSettingsFragment;
import com.android.settings.dream.DreamSettings;
import com.android.settings.enterprise.EnterprisePrivacySettings;
@@ -176,6 +177,7 @@ public class SettingsGateway {
SavedAccessPointsWifiSettings2.class.getName(),
AllInOneTetherSettings.class.getName(),
TetherSettings.class.getName(),
SmartAutoRotatePreferenceFragment.class.getName(),
WifiP2pSettings.class.getName(),
WifiTetherSettings.class.getName(),
BackgroundCheckSummary.class.getName(),

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.display;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.widget.Switch;
import com.android.internal.view.RotationPolicy;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
/**
* The switch controller for the location.
*/
public class AutoRotateSwitchBarController implements OnMainSwitchChangeListener,
LifecycleObserver, OnStart, OnStop {
private final SettingsMainSwitchBar mSwitchBar;
private final Context mContext;
private boolean mValidListener;
private final MetricsFeatureProvider mMetricsFeatureProvider;
public AutoRotateSwitchBarController(Context context, SettingsMainSwitchBar switchBar,
Lifecycle lifecycle) {
mSwitchBar = switchBar;
mContext = context;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public void onStart() {
if (!mValidListener) {
mSwitchBar.addOnSwitchChangeListener(this);
mValidListener = true;
}
onChange();
}
@Override
public void onStop() {
if (mValidListener) {
mSwitchBar.removeOnSwitchChangeListener(this);
mValidListener = false;
}
}
/**
* Listens to the state change of the rotation primary switch.
*/
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
setRotationLock(isChecked);
}
protected void onChange() {
final boolean isEnabled = !RotationPolicy.isRotationLocked(mContext);
if (isEnabled != mSwitchBar.isChecked()) {
// set listener to null so that that code below doesn't trigger onCheckedChanged()
if (mValidListener) {
mSwitchBar.removeOnSwitchChangeListener(this);
}
mSwitchBar.setChecked(isEnabled);
if (mValidListener) {
mSwitchBar.addOnSwitchChangeListener(this);
}
}
}
private boolean setRotationLock(boolean isChecked) {
final boolean isLocked = !isChecked;
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ROTATION_LOCK, isLocked);
RotationPolicy.setRotationLock(mContext, isLocked);
return true;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.display;
import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import com.android.internal.view.RotationPolicy;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* SmartAutoRotateController controls whether auto rotation is enabled
*/
public class SmartAutoRotateController extends TogglePreferenceController implements
Preference.OnPreferenceChangeListener {
private final MetricsFeatureProvider mMetricsFeatureProvider;
public SmartAutoRotateController(Context context, String preferenceKey) {
super(context, preferenceKey);
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
}
@Override
public int getAvailabilityStatus() {
return !RotationPolicy.isRotationLocked(mContext)
? AVAILABLE : DISABLED_DEPENDENT_SETTING;
}
@Override
public boolean isChecked() {
return Settings.Secure.getInt(mContext.getContentResolver(),
CAMERA_AUTOROTATE, 0) == 1;
}
@Override
public boolean setChecked(boolean isChecked) {
mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CAMERA_ROTATE_TOGGLE,
isChecked);
Settings.Secure.putInt(mContext.getContentResolver(),
CAMERA_AUTOROTATE,
isChecked ? 1 : 0);
return true;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.display;
import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
import android.content.Context;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import androidx.preference.Preference;
import com.android.internal.view.RotationPolicy;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
/**
* SmartAutoRotatePreferenceController provides auto rotate summary in display settings
*/
public class SmartAutoRotatePreferenceController extends BasePreferenceController {
private static final String TAG = "SmartAutoRotatePreferenceController";
public SmartAutoRotatePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
}
@Override
public int getAvailabilityStatus() {
return RotationPolicy.isRotationLockToggleVisible(mContext)
? AVAILABLE : UNSUPPORTED_ON_DEVICE;
}
protected void update(Preference preference) {
refreshSummary(preference);
}
@Override
public CharSequence getSummary() {
int activeStringId = R.string.auto_rotate_option_off;
if (!RotationPolicy.isRotationLocked(mContext)) {
try {
final int cameraRotate = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
CAMERA_AUTOROTATE,
UserHandle.USER_CURRENT);
activeStringId = cameraRotate == 1 ? R.string.auto_rotate_option_face_based
: R.string.auto_rotate_option_on;
} catch (Settings.SettingNotFoundException e) {
Log.w(TAG, "CAMERA_AUTOROTATE setting not found", e);
}
}
return mContext.getString(activeStringId);
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.display;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Bundle;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.preference.Preference;
import com.android.internal.view.RotationPolicy;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.search.Indexable;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
/**
* Preference fragment used to auto rotation
*/
@SuppressWarnings("WeakerAccess")
@SearchIndexable
public class SmartAutoRotatePreferenceFragment extends DashboardFragment {
private static final String TAG = "SmartAutoRotatePreferenceFragment";
private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
private AutoRotateSwitchBarController mSwitchBarController;
private static final String FACE_SWITCH_PREFERENCE_ID = "face_based_rotate";
private static final String SMART_AUTO_ROTATE_CONTROLLER_KEY = "auto_rotate";
@Override
protected int getPreferenceScreenResId() {
return R.xml.auto_rotate_settings;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
final SettingsActivity activity = (SettingsActivity) getActivity();
final SettingsMainSwitchBar switchBar = activity.getSwitchBar();
switchBar.setTitle(
getContext().getString(R.string.auto_rotate_settings_primary_switch_title));
switchBar.show();
mSwitchBarController = new AutoRotateSwitchBarController(activity, switchBar,
getSettingsLifecycle());
return view;
}
@Override
public void onResume() {
super.onResume();
if (mRotationPolicyListener == null) {
mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() {
@Override
public void onChange() {
mSwitchBarController.onChange();
final boolean isLocked = RotationPolicy.isRotationLocked(getContext());
final Preference preference = findPreference(FACE_SWITCH_PREFERENCE_ID);
if (preference != null) {
preference.setEnabled(!isLocked);
}
}
};
}
RotationPolicy.registerRotationPolicyListener(getPrefContext(),
mRotationPolicyListener);
findPreference(FooterPreference.KEY_FOOTER).setTitle(
Html.fromHtml(getString(R.string.smart_rotate_text_headline),
Html.FROM_HTML_MODE_COMPACT));
}
@Override
public void onPause() {
super.onPause();
if (mRotationPolicyListener != null) {
RotationPolicy.unregisterRotationPolicyListener(getPrefContext(),
mRotationPolicyListener);
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.DISPLAY_AUTO_ROTATE_SETTINGS;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context);
}
private static List<AbstractPreferenceController> buildPreferenceControllers(
Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(
new SmartAutoRotatePreferenceController(context, SMART_AUTO_ROTATE_CONTROLLER_KEY));
return controllers;
}
@Override
protected String getLogTag() {
return TAG;
}
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.auto_rotate_settings) {
@Override
public List<AbstractPreferenceController> createPreferenceControllers(
Context context) {
return buildPreferenceControllers(context);
}
@Override
protected boolean isPageSearchEnabled(Context context) {
return false;
}
};
}

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.display;
import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class SmartAutoRotatePreferenceControllerTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@Mock
private PackageManager mPackageManager;
private ContentResolver mContentResolver;
private SmartAutoRotatePreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
FakeFeatureFactory.setupForTest();
mContentResolver = RuntimeEnvironment.application.getContentResolver();
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContext.getString(R.string.auto_rotate_option_off))
.thenReturn("Off");
when(mContext.getString(R.string.auto_rotate_option_on))
.thenReturn("On");
when(mContext.getString(R.string.auto_rotate_option_face_based))
.thenReturn("On - Face-based");
disableCameraBasedRotation();
mController = new SmartAutoRotatePreferenceController(mContext, "smart_auto_rotate");
}
@Test
public void isAvailableWhenPolicyAllows() {
assertThat(mController.isAvailable()).isFalse();
enableAutoRotationPreference();
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void updatePreference_settingsIsOff_shouldTurnOffToggle() {
disableAutoRotation();
assertThat(mController.getSummary()).isEqualTo("Off");
}
@Test
public void updatePreference_settingsIsOn_shouldTurnOnToggle() {
enableAutoRotation();
assertThat(mController.getSummary()).isEqualTo("On");
}
@Test
public void updatePreference_settingsIsCameraBased_shouldTurnOnToggle() {
enableCameraBasedRotation();
enableAutoRotation();
assertThat(mController.getSummary()).isEqualTo("On - Face-based");
disableAutoRotation();
assertThat(mController.getSummary()).isEqualTo("Off");
}
@Test
public void testGetAvailabilityStatus() {
assertThat(mController.getAvailabilityStatus()).isEqualTo(BasePreferenceController
.UNSUPPORTED_ON_DEVICE);
enableAutoRotationPreference();
assertThat(mController.getAvailabilityStatus()).isEqualTo(BasePreferenceController
.AVAILABLE);
disableAutoRotationPreference();
assertThat(mController.getAvailabilityStatus()).isEqualTo(BasePreferenceController
.UNSUPPORTED_ON_DEVICE);
}
@Test
public void isSliceableCorrectKey_returnsTrue() {
final AutoRotatePreferenceController controller =
new AutoRotatePreferenceController(mContext, "auto_rotate");
assertThat(controller.isSliceable()).isTrue();
}
@Test
public void isSliceableIncorrectKey_returnsFalse() {
final AutoRotatePreferenceController controller =
new AutoRotatePreferenceController(mContext, "bad_key");
assertThat(controller.isSliceable()).isFalse();
}
private void enableAutoRotationPreference() {
when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
when(mContext.getResources().getBoolean(anyInt())).thenReturn(true);
Settings.System.putInt(mContentResolver,
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0);
}
private void disableAutoRotationPreference() {
when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
when(mContext.getResources().getBoolean(anyInt())).thenReturn(true);
Settings.System.putInt(mContentResolver,
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 1);
}
private void enableAutoRotation() {
Settings.System.putIntForUser(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
}
private void disableAutoRotation() {
Settings.System.putIntForUser(mContentResolver,
Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
}
private void enableCameraBasedRotation() {
Settings.Secure.putIntForUser(mContentResolver,
CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
}
private void disableCameraBasedRotation() {
Settings.Secure.putIntForUser(mContentResolver,
CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
}
}