1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.display;
18 
19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
20 import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
21 
22 import static androidx.lifecycle.Lifecycle.Event.ON_START;
23 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
24 
25 import static com.android.settings.display.SmartAutoRotateController.hasSufficientPermission;
26 import static com.android.settings.display.SmartAutoRotateController.isRotationResolverServiceAvailable;
27 
28 import android.app.settings.SettingsEnums;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.hardware.SensorPrivacyManager;
34 import android.os.PowerManager;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 
39 import androidx.lifecycle.LifecycleObserver;
40 import androidx.lifecycle.OnLifecycleEvent;
41 import androidx.preference.Preference;
42 import androidx.preference.PreferenceScreen;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.internal.view.RotationPolicy;
46 import com.android.settings.R;
47 import com.android.settings.core.TogglePreferenceController;
48 import com.android.settings.overlay.FeatureFactory;
49 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
50 
51 /**
52  * SmartAutoRotatePreferenceController provides auto rotate summary in display settings
53  */
54 public class SmartAutoRotatePreferenceController extends TogglePreferenceController
55         implements LifecycleObserver {
56 
57     private final MetricsFeatureProvider mMetricsFeatureProvider;
58     private final SensorPrivacyManager mPrivacyManager;
59     private final PowerManager mPowerManager;
60     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
61         @Override
62         public void onReceive(Context context, Intent intent) {
63             refreshSummary(mPreference);
64         }
65     };
66 
67     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener =
68             new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
69         @Override
70         public void onSensorPrivacyChanged(int sensor, boolean enabled) {
71             refreshSummary(mPreference);
72         }
73     };
74 
75     private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
76     private Preference mPreference;
77 
SmartAutoRotatePreferenceController(Context context, String preferenceKey)78     public SmartAutoRotatePreferenceController(Context context, String preferenceKey) {
79         super(context, preferenceKey);
80         mPrivacyManager = SensorPrivacyManager.getInstance(context);
81         mPowerManager = context.getSystemService(PowerManager.class);
82         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
83     }
84 
85     @Override
getAvailabilityStatus()86     public int getAvailabilityStatus() {
87         return RotationPolicy.isRotationLockToggleVisible(mContext)
88                 && !DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)
89                 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
90     }
91 
92     @Override
isSliceable()93     public boolean isSliceable() {
94         return TextUtils.equals(getPreferenceKey(), "auto_rotate");
95     }
96 
97     @Override
isPublicSlice()98     public boolean isPublicSlice() {
99         return true;
100     }
101 
102     @Override
getSliceHighlightMenuRes()103     public int getSliceHighlightMenuRes() {
104         return R.string.menu_key_display;
105     }
106 
107     @Override
displayPreference(PreferenceScreen screen)108     public void displayPreference(PreferenceScreen screen) {
109         super.displayPreference(screen);
110         mPreference = screen.findPreference(getPreferenceKey());
111     }
112 
113     @Override
updateState(Preference preference)114     public void updateState(Preference preference) {
115         super.updateState(preference);
116         refreshSummary(mPreference);
117     }
118 
119     @OnLifecycleEvent(ON_START)
onStart()120     public void onStart() {
121         mContext.registerReceiver(mReceiver,
122                 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
123         if (mRotationPolicyListener == null) {
124             mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() {
125                 @Override
126                 public void onChange() {
127                     if (mPreference != null) {
128                         updateState(mPreference);
129                     }
130                 }
131             };
132         }
133         RotationPolicy.registerRotationPolicyListener(mContext,
134                 mRotationPolicyListener);
135         mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
136     }
137 
138     @OnLifecycleEvent(ON_STOP)
onStop()139     public void onStop() {
140         mContext.unregisterReceiver(mReceiver);
141         if (mRotationPolicyListener != null) {
142             RotationPolicy.unregisterRotationPolicyListener(mContext,
143                     mRotationPolicyListener);
144         }
145         mPrivacyManager.removeSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
146     }
147 
148     /**
149      * Need this because all controller tests use Roboelectric. No easy way to mock this service,
150      * so we mock the call we need
151      */
152     @VisibleForTesting
isCameraLocked()153     boolean isCameraLocked() {
154         return mPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA);
155     }
156 
157     @VisibleForTesting
isPowerSaveMode()158     boolean isPowerSaveMode() {
159         return mPowerManager.isPowerSaveMode();
160     }
161 
162     @Override
isChecked()163     public boolean isChecked() {
164         return !RotationPolicy.isRotationLocked(mContext);
165     }
166 
167     @Override
setChecked(boolean isChecked)168     public boolean setChecked(boolean isChecked) {
169         final boolean isLocked = !isChecked;
170         mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_ROTATION_LOCK,
171                 isLocked);
172         RotationPolicy.setRotationLock(mContext, isLocked,
173                 /* caller= */ "SmartAutoRotatePreferenceController#setChecked");
174         return true;
175     }
176 
177     @Override
getSummary()178     public CharSequence getSummary() {
179         int activeStringId = R.string.auto_rotate_option_off;
180         if (!RotationPolicy.isRotationLocked(mContext)) {
181             final int cameraRotate = Settings.Secure.getIntForUser(
182                     mContext.getContentResolver(),
183                     CAMERA_AUTOROTATE,
184                     0, UserHandle.USER_CURRENT);
185             activeStringId = cameraRotate == 1 && isRotationResolverServiceAvailable(mContext)
186                     && hasSufficientPermission(mContext)
187                     && !isCameraLocked()
188                     && !isPowerSaveMode()
189                     ? R.string.auto_rotate_option_face_based
190                     : R.string.auto_rotate_option_on;
191         }
192         return mContext.getString(activeStringId);
193     }
194 }