Home | History | Annotate | Download | only in policy
      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 com.android.settingslib.Utils.updateLocationEnabled;
     20 
     21 import android.app.ActivityManager;
     22 import android.app.AppOpsManager;
     23 import android.app.StatusBarManager;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.location.LocationManager;
     29 import android.os.Handler;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 import android.os.Process;
     33 import android.os.UserHandle;
     34 import android.os.UserManager;
     35 import android.provider.Settings;
     36 import android.support.annotation.VisibleForTesting;
     37 import com.android.systemui.util.Utils;
     38 import java.util.ArrayList;
     39 import java.util.List;
     40 
     41 /**
     42  * A controller to manage changes of location related states and update the views accordingly.
     43  */
     44 public class LocationControllerImpl extends BroadcastReceiver implements LocationController {
     45 
     46     private static final int[] mHighPowerRequestAppOpArray
     47         = new int[] {AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION};
     48 
     49     private Context mContext;
     50 
     51     private AppOpsManager mAppOpsManager;
     52     private StatusBarManager mStatusBarManager;
     53 
     54     private boolean mAreActiveLocationRequests;
     55 
     56     private ArrayList<LocationChangeCallback> mSettingsChangeCallbacks =
     57             new ArrayList<LocationChangeCallback>();
     58     private final H mHandler = new H();
     59 
     60     public LocationControllerImpl(Context context, Looper bgLooper) {
     61         mContext = context;
     62 
     63         // Register to listen for changes in location settings.
     64         IntentFilter filter = new IntentFilter();
     65         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
     66         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
     67         context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, new Handler(bgLooper));
     68 
     69         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     70         mStatusBarManager
     71                 = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
     72 
     73         // Examine the current location state and initialize the status view.
     74         updateActiveLocationRequests();
     75     }
     76 
     77     /**
     78      * Add a callback to listen for changes in location settings.
     79      */
     80     public void addCallback(LocationChangeCallback cb) {
     81         mSettingsChangeCallbacks.add(cb);
     82         mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
     83     }
     84 
     85     public void removeCallback(LocationChangeCallback cb) {
     86         mSettingsChangeCallbacks.remove(cb);
     87     }
     88 
     89     /**
     90      * Enable or disable location in settings.
     91      *
     92      * <p>This will attempt to enable/disable every type of location setting
     93      * (e.g. high and balanced power).
     94      *
     95      * <p>If enabling, a user consent dialog will pop up prompting the user to accept.
     96      * If the user doesn't accept, network location won't be enabled.
     97      *
     98      * @return true if attempt to change setting was successful.
     99      */
    100     public boolean setLocationEnabled(boolean enabled) {
    101         // QuickSettings always runs as the owner, so specifically set the settings
    102         // for the current foreground user.
    103         int currentUserId = ActivityManager.getCurrentUser();
    104         if (isUserLocationRestricted(currentUserId)) {
    105             return false;
    106         }
    107         // When enabling location, a user consent dialog will pop up, and the
    108         // setting won't be fully enabled until the user accepts the agreement.
    109         updateLocationEnabled(mContext, enabled, currentUserId,
    110                 Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
    111         return true;
    112     }
    113 
    114     /**
    115      * Returns true if location is enabled in settings.
    116      */
    117     public boolean isLocationEnabled() {
    118         // QuickSettings always runs as the owner, so specifically retrieve the settings
    119         // for the current foreground user.
    120         LocationManager locationManager =
    121                 (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
    122         return locationManager.isLocationEnabledForUser(
    123                 UserHandle.of(ActivityManager.getCurrentUser()));
    124     }
    125 
    126     @Override
    127     public boolean isLocationActive() {
    128         return mAreActiveLocationRequests;
    129     }
    130 
    131     /**
    132      * Returns true if the current user is restricted from using location.
    133      */
    134     private boolean isUserLocationRestricted(int userId) {
    135         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    136         return um.hasUserRestriction(UserManager.DISALLOW_SHARE_LOCATION,
    137                 UserHandle.of(userId));
    138     }
    139 
    140     /**
    141      * Returns true if there currently exist active high power location requests.
    142      */
    143     @VisibleForTesting
    144     protected boolean areActiveHighPowerLocationRequests() {
    145         List<AppOpsManager.PackageOps> packages
    146             = mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray);
    147         // AppOpsManager can return null when there is no requested data.
    148         if (packages != null) {
    149             final int numPackages = packages.size();
    150             for (int packageInd = 0; packageInd < numPackages; packageInd++) {
    151                 AppOpsManager.PackageOps packageOp = packages.get(packageInd);
    152                 List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
    153                 if (opEntries != null) {
    154                     final int numOps = opEntries.size();
    155                     for (int opInd = 0; opInd < numOps; opInd++) {
    156                         AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
    157                         // AppOpsManager should only return OP_MONITOR_HIGH_POWER_LOCATION because
    158                         // of the mHighPowerRequestAppOpArray filter, but checking defensively.
    159                         if (opEntry.getOp() == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION) {
    160                             if (opEntry.isRunning()) {
    161                                 return true;
    162                             }
    163                         }
    164                     }
    165                 }
    166             }
    167         }
    168 
    169         return false;
    170     }
    171 
    172     // Reads the active location requests and updates the status view if necessary.
    173     private void updateActiveLocationRequests() {
    174         boolean hadActiveLocationRequests = mAreActiveLocationRequests;
    175         mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
    176         if (mAreActiveLocationRequests != hadActiveLocationRequests) {
    177             mHandler.sendEmptyMessage(H.MSG_LOCATION_ACTIVE_CHANGED);
    178         }
    179     }
    180 
    181     @Override
    182     public void onReceive(Context context, Intent intent) {
    183         final String action = intent.getAction();
    184         if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
    185             updateActiveLocationRequests();
    186         } else if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
    187             mHandler.sendEmptyMessage(H.MSG_LOCATION_SETTINGS_CHANGED);
    188         }
    189     }
    190 
    191     private final class H extends Handler {
    192         private static final int MSG_LOCATION_SETTINGS_CHANGED = 1;
    193         private static final int MSG_LOCATION_ACTIVE_CHANGED = 2;
    194 
    195         @Override
    196         public void handleMessage(Message msg) {
    197             switch (msg.what) {
    198                 case MSG_LOCATION_SETTINGS_CHANGED:
    199                     locationSettingsChanged();
    200                     break;
    201                 case MSG_LOCATION_ACTIVE_CHANGED:
    202                     locationActiveChanged();
    203                     break;
    204             }
    205         }
    206 
    207         private void locationActiveChanged() {
    208             Utils.safeForeach(mSettingsChangeCallbacks,
    209                     cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests));
    210         }
    211 
    212         private void locationSettingsChanged() {
    213             boolean isEnabled = isLocationEnabled();
    214             Utils.safeForeach(mSettingsChangeCallbacks,
    215                     cb -> cb.onLocationSettingsChanged(isEnabled));
    216         }
    217     }
    218 }
    219