1 /* 2 * Copyright (C) 2008 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 17 package com.android.systemui.statusbar.policy; 18 19 import static android.app.AppOpsManager.OP_COARSE_LOCATION; 20 import static android.app.AppOpsManager.OP_FINE_LOCATION; 21 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; 22 23 import static com.android.settingslib.Utils.updateLocationEnabled; 24 25 import android.app.AppOpsManager; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.PermissionChecker; 31 import android.content.pm.PackageManager; 32 import android.content.pm.UserInfo; 33 import android.database.ContentObserver; 34 import android.location.LocationManager; 35 import android.os.Handler; 36 import android.os.Looper; 37 import android.os.Message; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.provider.DeviceConfig; 41 import android.provider.Settings; 42 43 import androidx.annotation.NonNull; 44 import androidx.annotation.VisibleForTesting; 45 46 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 47 import com.android.internal.logging.UiEvent; 48 import com.android.internal.logging.UiEventLogger; 49 import com.android.systemui.BootCompleteCache; 50 import com.android.systemui.appops.AppOpItem; 51 import com.android.systemui.appops.AppOpsController; 52 import com.android.systemui.broadcast.BroadcastDispatcher; 53 import com.android.systemui.dagger.SysUISingleton; 54 import com.android.systemui.dagger.qualifiers.Background; 55 import com.android.systemui.dagger.qualifiers.Main; 56 import com.android.systemui.settings.UserTracker; 57 import com.android.systemui.util.DeviceConfigProxy; 58 import com.android.systemui.util.settings.SecureSettings; 59 60 import java.util.ArrayList; 61 import java.util.List; 62 63 import javax.inject.Inject; 64 65 /** 66 * A controller to manage changes of location related states and update the views accordingly. 67 */ 68 @SysUISingleton 69 public class LocationControllerImpl extends BroadcastReceiver implements LocationController, 70 AppOpsController.Callback { 71 72 private final Context mContext; 73 private final AppOpsController mAppOpsController; 74 private final DeviceConfigProxy mDeviceConfigProxy; 75 private final BootCompleteCache mBootCompleteCache; 76 private final UserTracker mUserTracker; 77 private final UiEventLogger mUiEventLogger; 78 private final H mHandler; 79 private final Handler mBackgroundHandler; 80 private final PackageManager mPackageManager; 81 private final ContentObserver mContentObserver; 82 private final SecureSettings mSecureSettings; 83 84 private boolean mAreActiveLocationRequests; 85 private boolean mShouldDisplayAllAccesses; 86 private boolean mShowSystemAccessesFlag; 87 private boolean mShowSystemAccessesSetting; 88 89 @Inject LocationControllerImpl(Context context, AppOpsController appOpsController, DeviceConfigProxy deviceConfigProxy, @Main Looper mainLooper, @Background Handler backgroundHandler, BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, UserTracker userTracker, PackageManager packageManager, UiEventLogger uiEventLogger, SecureSettings secureSettings)90 public LocationControllerImpl(Context context, AppOpsController appOpsController, 91 DeviceConfigProxy deviceConfigProxy, 92 @Main Looper mainLooper, @Background Handler backgroundHandler, 93 BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache, 94 UserTracker userTracker, PackageManager packageManager, UiEventLogger uiEventLogger, 95 SecureSettings secureSettings) { 96 mContext = context; 97 mAppOpsController = appOpsController; 98 mDeviceConfigProxy = deviceConfigProxy; 99 mBootCompleteCache = bootCompleteCache; 100 mHandler = new H(mainLooper); 101 mUserTracker = userTracker; 102 mUiEventLogger = uiEventLogger; 103 mSecureSettings = secureSettings; 104 mBackgroundHandler = backgroundHandler; 105 mPackageManager = packageManager; 106 mShouldDisplayAllAccesses = getAllAccessesSetting(); 107 mShowSystemAccessesFlag = getShowSystemFlag(); 108 mShowSystemAccessesSetting = getShowSystemSetting(); 109 mContentObserver = new ContentObserver(mBackgroundHandler) { 110 @Override 111 public void onChange(boolean selfChange) { 112 mShowSystemAccessesSetting = getShowSystemSetting(); 113 } 114 }; 115 116 // Register to listen for changes in Settings.Secure settings. 117 mSecureSettings.registerContentObserverForUser( 118 Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mContentObserver, UserHandle.USER_ALL); 119 120 // Register to listen for changes in DeviceConfig settings. 121 mDeviceConfigProxy.addOnPropertiesChangedListener( 122 DeviceConfig.NAMESPACE_PRIVACY, 123 backgroundHandler::post, 124 properties -> { 125 mShouldDisplayAllAccesses = getAllAccessesSetting(); 126 mShowSystemAccessesFlag = getShowSystemSetting(); 127 updateActiveLocationRequests(); 128 }); 129 130 // Register to listen for changes in location settings. 131 IntentFilter filter = new IntentFilter(); 132 filter.addAction(LocationManager.MODE_CHANGED_ACTION); 133 broadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, UserHandle.ALL); 134 135 // Listen to all accesses and filter the ones interested in based on flags. 136 mAppOpsController.addCallback( 137 new int[]{OP_COARSE_LOCATION, OP_FINE_LOCATION, OP_MONITOR_HIGH_POWER_LOCATION}, 138 this); 139 140 // Examine the current location state and initialize the status view. 141 backgroundHandler.post(this::updateActiveLocationRequests); 142 } 143 144 /** 145 * Add a callback to listen for changes in location settings. 146 */ 147 @Override addCallback(@onNull LocationChangeCallback cb)148 public void addCallback(@NonNull LocationChangeCallback cb) { 149 mHandler.obtainMessage(H.MSG_ADD_CALLBACK, cb).sendToTarget(); 150 mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED); 151 } 152 153 @Override removeCallback(@onNull LocationChangeCallback cb)154 public void removeCallback(@NonNull LocationChangeCallback cb) { 155 mHandler.obtainMessage(H.MSG_REMOVE_CALLBACK, cb).sendToTarget(); 156 } 157 158 /** 159 * Enable or disable location in settings. 160 * 161 * <p>This will attempt to enable/disable every type of location setting 162 * (e.g. high and balanced power). 163 * 164 * <p>If enabling, a user consent dialog will pop up prompting the user to accept. 165 * If the user doesn't accept, network location won't be enabled. 166 * 167 * @return true if attempt to change setting was successful. 168 */ setLocationEnabled(boolean enabled)169 public boolean setLocationEnabled(boolean enabled) { 170 // QuickSettings always runs as the owner, so specifically set the settings 171 // for the current foreground user. 172 int currentUserId = mUserTracker.getUserId(); 173 if (isUserLocationRestricted(currentUserId)) { 174 return false; 175 } 176 // When enabling location, a user consent dialog will pop up, and the 177 // setting won't be fully enabled until the user accepts the agreement. 178 updateLocationEnabled(mContext, enabled, currentUserId, 179 Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS); 180 return true; 181 } 182 183 /** 184 * Returns true if location is enabled in settings. Will return false if 185 * {@link LocationManager} service has not been completely initialized 186 */ isLocationEnabled()187 public boolean isLocationEnabled() { 188 // QuickSettings always runs as the owner, so specifically retrieve the settings 189 // for the current foreground user. 190 LocationManager locationManager = 191 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); 192 return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser( 193 mUserTracker.getUserHandle()); 194 } 195 196 @Override isLocationActive()197 public boolean isLocationActive() { 198 return mAreActiveLocationRequests; 199 } 200 201 /** 202 * Returns true if the current user is restricted from using location. 203 */ isUserLocationRestricted(int userId)204 private boolean isUserLocationRestricted(int userId) { 205 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 206 return um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION, 207 UserHandle.of(userId)); 208 } 209 getAllAccessesSetting()210 private boolean getAllAccessesSetting() { 211 return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 212 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false); 213 } 214 getShowSystemFlag()215 private boolean getShowSystemFlag() { 216 return mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, 217 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SHOW_SYSTEM, false); 218 } 219 getShowSystemSetting()220 private boolean getShowSystemSetting() { 221 return mSecureSettings.getIntForUser(Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0, 222 UserHandle.USER_CURRENT) == 1; 223 } 224 225 /** 226 * Returns true if there currently exist active high power location requests. 227 */ 228 @VisibleForTesting areActiveHighPowerLocationRequests()229 protected boolean areActiveHighPowerLocationRequests() { 230 List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); 231 232 final int numItems = appOpsItems.size(); 233 for (int i = 0; i < numItems; i++) { 234 if (appOpsItems.get(i).getCode() == OP_MONITOR_HIGH_POWER_LOCATION) { 235 return true; 236 } 237 } 238 239 return false; 240 } 241 242 /** 243 * Returns true if there currently exist active location requests. 244 */ 245 @VisibleForTesting areActiveLocationRequests()246 protected void areActiveLocationRequests() { 247 if (!mShouldDisplayAllAccesses) { 248 return; 249 } 250 boolean hadActiveLocationRequests = mAreActiveLocationRequests; 251 boolean shouldDisplay = false; 252 boolean showSystem = mShowSystemAccessesFlag || mShowSystemAccessesSetting; 253 boolean systemAppOp = false; 254 boolean nonSystemAppOp = false; 255 boolean isSystemApp; 256 257 List<AppOpItem> appOpsItems = mAppOpsController.getActiveAppOps(); 258 final List<UserInfo> profiles = mUserTracker.getUserProfiles(); 259 final int numItems = appOpsItems.size(); 260 for (int i = 0; i < numItems; i++) { 261 if (appOpsItems.get(i).getCode() == OP_FINE_LOCATION 262 || appOpsItems.get(i).getCode() == OP_COARSE_LOCATION) { 263 isSystemApp = isSystemApp(profiles, appOpsItems.get(i)); 264 if (isSystemApp) { 265 systemAppOp = true; 266 } else { 267 nonSystemAppOp = true; 268 } 269 270 shouldDisplay = showSystem || shouldDisplay || !isSystemApp; 271 } 272 } 273 274 boolean highPowerOp = areActiveHighPowerLocationRequests(); 275 mAreActiveLocationRequests = shouldDisplay; 276 if (mAreActiveLocationRequests != hadActiveLocationRequests) { 277 mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); 278 } 279 280 // Log each of the types of location access that would cause the location indicator to be 281 // shown, regardless of device's setting state. This is used to understand how often a 282 // user would see the location indicator based on any settings state the device could be in. 283 if (!hadActiveLocationRequests && (highPowerOp || systemAppOp || nonSystemAppOp)) { 284 if (highPowerOp) { 285 mUiEventLogger.log( 286 LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER); 287 } 288 if (systemAppOp) { 289 mUiEventLogger.log(LocationIndicatorEvent.LOCATION_INDICATOR_SYSTEM_APP); 290 } 291 if (nonSystemAppOp) { 292 mUiEventLogger.log(LocationIndicatorEvent.LOCATION_INDICATOR_NON_SYSTEM_APP); 293 } 294 } 295 } 296 isSystemApp(List<UserInfo> profiles, AppOpItem item)297 private boolean isSystemApp(List<UserInfo> profiles, AppOpItem item) { 298 final String permission = AppOpsManager.opToPermission(item.getCode()); 299 UserHandle user = UserHandle.getUserHandleForUid(item.getUid()); 300 301 // Don't show apps belonging to background users except managed users. 302 boolean foundUser = false; 303 final int numProfiles = profiles.size(); 304 for (int i = 0; i < numProfiles; i++) { 305 if (profiles.get(i).getUserHandle().equals(user)) { 306 foundUser = true; 307 } 308 } 309 if (!foundUser) { 310 return true; 311 } 312 313 final int permissionFlags = mPackageManager.getPermissionFlags( 314 permission, item.getPackageName(), user); 315 if (PermissionChecker.checkPermissionForPreflight(mContext, permission, 316 PermissionChecker.PID_UNKNOWN, item.getUid(), item.getPackageName()) 317 == PermissionChecker.PERMISSION_GRANTED) { 318 return (permissionFlags 319 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) 320 == 0; 321 } else { 322 return (permissionFlags 323 & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0; 324 } 325 } 326 327 // Reads the active location requests from either OP_MONITOR_HIGH_POWER_LOCATION, 328 // OP_FINE_LOCATION, or OP_COARSE_LOCATION and updates the status view if necessary. updateActiveLocationRequests()329 private void updateActiveLocationRequests() { 330 if (mShouldDisplayAllAccesses) { 331 mBackgroundHandler.post(this::areActiveLocationRequests); 332 } else { 333 boolean hadActiveLocationRequests = mAreActiveLocationRequests; 334 mAreActiveLocationRequests = areActiveHighPowerLocationRequests(); 335 if (mAreActiveLocationRequests != hadActiveLocationRequests) { 336 mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED); 337 if (mAreActiveLocationRequests) { 338 // Log that the indicator was shown for a high power op. 339 mUiEventLogger.log( 340 LocationIndicatorEvent.LOCATION_INDICATOR_MONITOR_HIGH_POWER); 341 } 342 } 343 } 344 } 345 346 @Override onReceive(Context context, Intent intent)347 public void onReceive(Context context, Intent intent) { 348 if (LocationManager.MODE_CHANGED_ACTION.equals(intent.getAction())) { 349 mHandler.locationSettingsChanged(); 350 } 351 } 352 353 @Override onActiveStateChanged(int code, int uid, String packageName, boolean active)354 public void onActiveStateChanged(int code, int uid, String packageName, boolean active) { 355 updateActiveLocationRequests(); 356 } 357 358 private final class H extends Handler { 359 private static final int MSG_LOCATION_SETTINGS_CHANGED = 1; 360 private static final int MSG_LOCATION_ACTIVE_CHANGED = 2; 361 private static final int MSG_ADD_CALLBACK = 3; 362 private static final int MSG_REMOVE_CALLBACK = 4; 363 364 private final ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = 365 new ArrayList<>(); 366 H(Looper looper)367 H(Looper looper) { 368 super(looper); 369 } 370 371 @Override handleMessage(Message msg)372 public void handleMessage(Message msg) { 373 switch (msg.what) { 374 case MSG_LOCATION_SETTINGS_CHANGED: 375 locationSettingsChanged(); 376 break; 377 case MSG_LOCATION_ACTIVE_CHANGED: 378 locationActiveChanged(); 379 break; 380 case MSG_ADD_CALLBACK: 381 mSettingsChangeCallbacks.add((LocationChangeCallback) msg.obj); 382 break; 383 case MSG_REMOVE_CALLBACK: 384 mSettingsChangeCallbacks.remove((LocationChangeCallback) msg.obj); 385 break; 386 387 } 388 } 389 locationActiveChanged()390 private void locationActiveChanged() { 391 synchronized (mSettingsChangeCallbacks) { 392 final int n = mSettingsChangeCallbacks.size(); 393 for (int i = 0; i < n; i++) { 394 mSettingsChangeCallbacks.get(i) 395 .onLocationActiveChanged(mAreActiveLocationRequests); 396 } 397 } 398 } 399 locationSettingsChanged()400 private void locationSettingsChanged() { 401 boolean isEnabled = isLocationEnabled(); 402 synchronized (mSettingsChangeCallbacks) { 403 final int n = mSettingsChangeCallbacks.size(); 404 for (int i = 0; i < n; i++) { 405 mSettingsChangeCallbacks.get(i).onLocationSettingsChanged(isEnabled); 406 } 407 } 408 } 409 } 410 411 /** 412 * Enum for events which prompt the location indicator to appear. 413 */ 414 enum LocationIndicatorEvent implements UiEventLogger.UiEventEnum { 415 @UiEvent(doc = "Location indicator shown for high power access") 416 LOCATION_INDICATOR_MONITOR_HIGH_POWER(935), 417 @UiEvent(doc = "Location indicator shown for system app access") 418 LOCATION_INDICATOR_SYSTEM_APP(936), 419 @UiEvent(doc = "Location indicator shown for non system app access") 420 LOCATION_INDICATOR_NON_SYSTEM_APP(937); 421 422 private final int mId; LocationIndicatorEvent(int id)423 LocationIndicatorEvent(int id) { 424 mId = id; 425 } getId()426 @Override public int getId() { 427 return mId; 428 } 429 } 430 }