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