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 }