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