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