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 package com.android.settings.display;
17 
18 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
19 import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
20 
21 import static androidx.lifecycle.Lifecycle.Event.ON_START;
22 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
23 
24 import android.Manifest;
25 import android.app.settings.SettingsEnums;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.hardware.SensorPrivacyManager;
33 import android.os.PowerManager;
34 import android.provider.Settings;
35 import android.service.rotationresolver.RotationResolverService;
36 import android.text.TextUtils;
37 
38 import androidx.lifecycle.LifecycleObserver;
39 import androidx.lifecycle.OnLifecycleEvent;
40 import androidx.preference.Preference;
41 import androidx.preference.PreferenceScreen;
42 
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.internal.view.RotationPolicy;
45 import com.android.settings.R;
46 import com.android.settings.core.TogglePreferenceController;
47 import com.android.settings.overlay.FeatureFactory;
48 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
49 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
50 
51 /**
52  * SmartAutoRotateController controls whether auto rotation is enabled
53  */
54 public class SmartAutoRotateController extends TogglePreferenceController implements
55         Preference.OnPreferenceChangeListener, LifecycleObserver {
56 
57     protected Preference mPreference;
58 
59     private final MetricsFeatureProvider mMetricsFeatureProvider;
60     private final SensorPrivacyManager mPrivacyManager;
61     private final PowerManager mPowerManager;
62     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
63         @Override
64         public void onReceive(Context context, Intent intent) {
65             updateState(mPreference);
66         }
67     };
68 
69     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener =
70             new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
71                 @Override
72                 public void onSensorPrivacyChanged(int sensor, boolean enabled) {
73                     updateState(mPreference);
74                 }
75             };
76 
77     private final DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager;
78     private final DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
79             mDeviceStateRotationLockSettingsListener = () -> updateState(mPreference);
80     private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
81 
SmartAutoRotateController(Context context, String preferenceKey)82     public SmartAutoRotateController(Context context, String preferenceKey) {
83         super(context, preferenceKey);
84         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
85         mPrivacyManager = SensorPrivacyManager.getInstance(context);
86         mPowerManager = context.getSystemService(PowerManager.class);
87         mDeviceStateAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(
88                 context);
89     }
90 
91     @Override
getAvailabilityStatus()92     public int getAvailabilityStatus() {
93         if (!isRotationResolverServiceAvailable(mContext)) {
94             return UNSUPPORTED_ON_DEVICE;
95         }
96         return !isRotationLocked() && hasSufficientPermission(mContext)
97                 && !isCameraLocked() && !isPowerSaveMode() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
98     }
99 
isRotationLocked()100     protected boolean isRotationLocked() {
101         if (DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)) {
102             return mDeviceStateAutoRotateSettingsManager.isRotationLockedForAllStates();
103         }
104         return RotationPolicy.isRotationLocked(mContext);
105     }
106 
107     @Override
updateState(Preference preference)108     public void updateState(Preference preference) {
109         super.updateState(preference);
110         if (preference != null) {
111             preference.setEnabled(getAvailabilityStatus() == AVAILABLE);
112         }
113     }
114 
115     /**
116      * Need this because all controller tests use RoboElectric. No easy way to mock this service,
117      * so we mock the call we need
118      */
119     @VisibleForTesting
isCameraLocked()120     boolean isCameraLocked() {
121         return mPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA);
122     }
123 
124     @VisibleForTesting
isPowerSaveMode()125     boolean isPowerSaveMode() {
126         return mPowerManager.isPowerSaveMode();
127     }
128 
129     @OnLifecycleEvent(ON_START)
onStart()130     public void onStart() {
131         mContext.registerReceiver(mReceiver,
132                 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
133         if (mRotationPolicyListener == null) {
134             mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() {
135                 @Override
136                 public void onChange() {
137                     updateState(mPreference);
138                 }
139             };
140         }
141         RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener);
142         mDeviceStateAutoRotateSettingsManager.registerListener(
143                 mDeviceStateRotationLockSettingsListener);
144         mPrivacyManager.addSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
145     }
146 
147     @OnLifecycleEvent(ON_STOP)
onStop()148     public void onStop() {
149         mContext.unregisterReceiver(mReceiver);
150         if (mRotationPolicyListener != null) {
151             RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
152             mRotationPolicyListener = null;
153         }
154         mDeviceStateAutoRotateSettingsManager.unregisterListener(
155                 mDeviceStateRotationLockSettingsListener);
156         mPrivacyManager.removeSensorPrivacyListener(CAMERA, mPrivacyChangedListener);
157     }
158 
159     @Override
isChecked()160     public boolean isChecked() {
161         return !isRotationLocked() && hasSufficientPermission(mContext)
162                 && !isCameraLocked() && !isPowerSaveMode() && Settings.Secure.getInt(
163                 mContext.getContentResolver(),
164                 CAMERA_AUTOROTATE, 0) == 1;
165     }
166 
167     @Override
displayPreference(PreferenceScreen screen)168     public void displayPreference(PreferenceScreen screen) {
169         super.displayPreference(screen);
170         mPreference = screen.findPreference(getPreferenceKey());
171     }
172 
173     @Override
setChecked(boolean isChecked)174     public boolean setChecked(boolean isChecked) {
175         mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CAMERA_ROTATE_TOGGLE,
176                 isChecked);
177         Settings.Secure.putInt(mContext.getContentResolver(),
178                 CAMERA_AUTOROTATE,
179                 isChecked ? 1 : 0);
180         return true;
181     }
182 
183     @Override
getSliceHighlightMenuRes()184     public int getSliceHighlightMenuRes() {
185         return R.string.menu_key_display;
186     }
187 
188     /**
189      * Returns true if there is a {@link RotationResolverService} available
190      */
isRotationResolverServiceAvailable(Context context)191     public static boolean isRotationResolverServiceAvailable(Context context) {
192         if (!context.getResources().getBoolean(
193                 R.bool.config_auto_rotate_face_detection_available)) {
194             return false;
195         }
196         final PackageManager packageManager = context.getPackageManager();
197         final String resolvePackage = packageManager.getRotationResolverPackageName();
198         if (TextUtils.isEmpty(resolvePackage)) {
199             return false;
200         }
201         final Intent intent = new Intent(RotationResolverService.SERVICE_INTERFACE).setPackage(
202                 resolvePackage);
203         final ResolveInfo resolveInfo = packageManager.resolveService(intent,
204                 PackageManager.MATCH_SYSTEM_ONLY);
205         return resolveInfo != null && resolveInfo.serviceInfo != null;
206     }
207 
hasSufficientPermission(Context context)208     static boolean hasSufficientPermission(Context context) {
209         final PackageManager packageManager = context.getPackageManager();
210         final String rotationPackage = packageManager.getRotationResolverPackageName();
211         return rotationPackage != null && packageManager.checkPermission(
212                 Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
213     }
214 }
215