• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     private final DeviceStateRotationLockSettingsManager mDeviceStateAutoRotateSettingsManager;
69     private final DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
70             mDeviceStateRotationLockSettingsListener = () -> updateState(mPreference);
71     private RotationPolicy.RotationPolicyListener mRotationPolicyListener;
72 
SmartAutoRotateController(Context context, String preferenceKey)73     public SmartAutoRotateController(Context context, String preferenceKey) {
74         super(context, preferenceKey);
75         mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
76         mPrivacyManager = SensorPrivacyManager.getInstance(context);
77         mPrivacyManager
78                 .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> updateState(mPreference));
79         mPowerManager = context.getSystemService(PowerManager.class);
80         mDeviceStateAutoRotateSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(
81                 context);
82     }
83 
84     @Override
getAvailabilityStatus()85     public int getAvailabilityStatus() {
86         if (!isRotationResolverServiceAvailable(mContext)) {
87             return UNSUPPORTED_ON_DEVICE;
88         }
89         return !isRotationLocked() && hasSufficientPermission(mContext)
90                 && !isCameraLocked() && !isPowerSaveMode() ? AVAILABLE : DISABLED_DEPENDENT_SETTING;
91     }
92 
isRotationLocked()93     protected boolean isRotationLocked() {
94         if (DeviceStateAutoRotationHelper.isDeviceStateRotationEnabled(mContext)) {
95             return mDeviceStateAutoRotateSettingsManager.isRotationLockedForAllStates();
96         }
97         return RotationPolicy.isRotationLocked(mContext);
98     }
99 
100     @Override
updateState(Preference preference)101     public void updateState(Preference preference) {
102         super.updateState(preference);
103         if (preference != null) {
104             preference.setEnabled(getAvailabilityStatus() == AVAILABLE);
105         }
106     }
107 
108     /**
109      * Need this because all controller tests use RoboElectric. No easy way to mock this service,
110      * so we mock the call we need
111      */
112     @VisibleForTesting
isCameraLocked()113     boolean isCameraLocked() {
114         return mPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA);
115     }
116 
117     @VisibleForTesting
isPowerSaveMode()118     boolean isPowerSaveMode() {
119         return mPowerManager.isPowerSaveMode();
120     }
121 
122     @OnLifecycleEvent(ON_START)
onStart()123     public void onStart() {
124         mContext.registerReceiver(mReceiver,
125                 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED));
126         if (mRotationPolicyListener == null) {
127             mRotationPolicyListener = new RotationPolicy.RotationPolicyListener() {
128                 @Override
129                 public void onChange() {
130                     updateState(mPreference);
131                 }
132             };
133         }
134         RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener);
135         mDeviceStateAutoRotateSettingsManager.registerListener(
136                 mDeviceStateRotationLockSettingsListener);
137     }
138 
139     @OnLifecycleEvent(ON_STOP)
onStop()140     public void onStop() {
141         mContext.unregisterReceiver(mReceiver);
142         if (mRotationPolicyListener != null) {
143             RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener);
144             mRotationPolicyListener = null;
145         }
146         mDeviceStateAutoRotateSettingsManager.unregisterListener(
147                 mDeviceStateRotationLockSettingsListener);
148     }
149 
150     @Override
isChecked()151     public boolean isChecked() {
152         return !isRotationLocked() && hasSufficientPermission(mContext)
153                 && !isCameraLocked() && !isPowerSaveMode() && Settings.Secure.getInt(
154                 mContext.getContentResolver(),
155                 CAMERA_AUTOROTATE, 0) == 1;
156     }
157 
158     @Override
displayPreference(PreferenceScreen screen)159     public void displayPreference(PreferenceScreen screen) {
160         super.displayPreference(screen);
161         mPreference = screen.findPreference(getPreferenceKey());
162     }
163 
164     @Override
setChecked(boolean isChecked)165     public boolean setChecked(boolean isChecked) {
166         mMetricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CAMERA_ROTATE_TOGGLE,
167                 isChecked);
168         Settings.Secure.putInt(mContext.getContentResolver(),
169                 CAMERA_AUTOROTATE,
170                 isChecked ? 1 : 0);
171         return true;
172     }
173 
174     @Override
getSliceHighlightMenuRes()175     public int getSliceHighlightMenuRes() {
176         return R.string.menu_key_display;
177     }
178 
179     /**
180      * Returns true if there is a {@link RotationResolverService} available
181      */
isRotationResolverServiceAvailable(Context context)182     public static boolean isRotationResolverServiceAvailable(Context context) {
183         if (!context.getResources().getBoolean(
184                 R.bool.config_auto_rotate_face_detection_available)) {
185             return false;
186         }
187         final PackageManager packageManager = context.getPackageManager();
188         final String resolvePackage = packageManager.getRotationResolverPackageName();
189         if (TextUtils.isEmpty(resolvePackage)) {
190             return false;
191         }
192         final Intent intent = new Intent(RotationResolverService.SERVICE_INTERFACE).setPackage(
193                 resolvePackage);
194         final ResolveInfo resolveInfo = packageManager.resolveService(intent,
195                 PackageManager.MATCH_SYSTEM_ONLY);
196         return resolveInfo != null && resolveInfo.serviceInfo != null;
197     }
198 
hasSufficientPermission(Context context)199     static boolean hasSufficientPermission(Context context) {
200         final PackageManager packageManager = context.getPackageManager();
201         final String rotationPackage = packageManager.getRotationResolverPackageName();
202         return rotationPackage != null && packageManager.checkPermission(
203                 Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
204     }
205 }
206