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 android.app.ActivityManager;
     20 import android.app.AppOpsManager;
     21 import android.app.StatusBarManager;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.database.ContentObserver;
     28 import android.location.LocationManager;
     29 import android.os.Handler;
     30 import android.os.UserHandle;
     31 import android.os.UserManager;
     32 import android.provider.Settings;
     33 
     34 import com.android.systemui.R;
     35 
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 
     39 /**
     40  * A controller to manage changes of location related states and update the views accordingly.
     41  */
     42 public class LocationController extends BroadcastReceiver {
     43     // The name of the placeholder corresponding to the location request status icon.
     44     // This string corresponds to config_statusBarIcons in core/res/res/values/config.xml.
     45     public static final String LOCATION_STATUS_ICON_PLACEHOLDER = "location";
     46     public static final int LOCATION_STATUS_ICON_ID
     47         = R.drawable.stat_sys_device_access_location_found;
     48 
     49     private static final int[] mHighPowerRequestAppOpArray
     50         = new int[] {AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION};
     51 
     52     private Context mContext;
     53 
     54     private AppOpsManager mAppOpsManager;
     55     private StatusBarManager mStatusBarManager;
     56 
     57     private boolean mAreActiveLocationRequests;
     58 
     59     private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
     60             new ArrayList<LocationSettingsChangeCallback>();
     61 
     62     /**
     63      * A callback for change in location settings (the user has enabled/disabled location).
     64      */
     65     public interface LocationSettingsChangeCallback {
     66         /**
     67          * Called whenever location settings change.
     68          *
     69          * @param locationEnabled A value of true indicates that at least one type of location
     70          *                        is enabled in settings.
     71          */
     72         public void onLocationSettingsChanged(boolean locationEnabled);
     73     }
     74 
     75     public LocationController(Context context) {
     76         mContext = context;
     77 
     78         IntentFilter filter = new IntentFilter();
     79         filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
     80         context.registerReceiver(this, filter);
     81 
     82         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
     83         mStatusBarManager
     84                 = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
     85 
     86         // Register to listen for changes in location settings.
     87         IntentFilter intentFilter = new IntentFilter();
     88         intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
     89         context.registerReceiverAsUser(new BroadcastReceiver() {
     90             @Override
     91             public void onReceive(Context context, Intent intent) {
     92                 String action = intent.getAction();
     93                 if (LocationManager.MODE_CHANGED_ACTION.equals(action)) {
     94                     locationSettingsChanged();
     95                 }
     96             }
     97         }, UserHandle.ALL, intentFilter, null, new Handler());
     98 
     99         // Examine the current location state and initialize the status view.
    100         updateActiveLocationRequests();
    101         refreshViews();
    102     }
    103 
    104     /**
    105      * Add a callback to listen for changes in location settings.
    106      */
    107     public void addSettingsChangedCallback(LocationSettingsChangeCallback cb) {
    108         mSettingsChangeCallbacks.add(cb);
    109     }
    110 
    111     /**
    112      * Enable or disable location in settings.
    113      *
    114      * <p>This will attempt to enable/disable every type of location setting
    115      * (e.g. high and balanced power).
    116      *
    117      * <p>If enabling, a user consent dialog will pop up prompting the user to accept.
    118      * If the user doesn't accept, network location won't be enabled.
    119      *
    120      * @return true if attempt to change setting was successful.
    121      */
    122     public boolean setLocationEnabled(boolean enabled) {
    123         int currentUserId = ActivityManager.getCurrentUser();
    124         if (isUserLocationRestricted(currentUserId)) {
    125             return false;
    126         }
    127         final ContentResolver cr = mContext.getContentResolver();
    128         // When enabling location, a user consent dialog will pop up, and the
    129         // setting won't be fully enabled until the user accepts the agreement.
    130         int mode = enabled
    131                 ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
    132         // QuickSettings always runs as the owner, so specifically set the settings
    133         // for the current foreground user.
    134         return Settings.Secure
    135                 .putIntForUser(cr, Settings.Secure.LOCATION_MODE, mode, currentUserId);
    136     }
    137 
    138     /**
    139      * Returns true if location isn't disabled in settings.
    140      */
    141     public boolean isLocationEnabled() {
    142         ContentResolver resolver = mContext.getContentResolver();
    143         // QuickSettings always runs as the owner, so specifically retrieve the settings
    144         // for the current foreground user.
    145         int mode = Settings.Secure.getIntForUser(resolver, Settings.Secure.LOCATION_MODE,
    146                 Settings.Secure.LOCATION_MODE_OFF, ActivityManager.getCurrentUser());
    147         return mode != Settings.Secure.LOCATION_MODE_OFF;
    148     }
    149 
    150     /**
    151      * Returns true if the current user is restricted from using location.
    152      */
    153     private boolean isUserLocationRestricted(int userId) {
    154         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    155         return um.hasUserRestriction(
    156                 UserManager.DISALLOW_SHARE_LOCATION,
    157                 new UserHandle(userId));
    158     }
    159 
    160     /**
    161      * Returns true if there currently exist active high power location requests.
    162      */
    163     private boolean areActiveHighPowerLocationRequests() {
    164         List<AppOpsManager.PackageOps> packages
    165             = mAppOpsManager.getPackagesForOps(mHighPowerRequestAppOpArray);
    166         // AppOpsManager can return null when there is no requested data.
    167         if (packages != null) {
    168             final int numPackages = packages.size();
    169             for (int packageInd = 0; packageInd < numPackages; packageInd++) {
    170                 AppOpsManager.PackageOps packageOp = packages.get(packageInd);
    171                 List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
    172                 if (opEntries != null) {
    173                     final int numOps = opEntries.size();
    174                     for (int opInd = 0; opInd < numOps; opInd++) {
    175                         AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
    176                         // AppOpsManager should only return OP_MONITOR_HIGH_POWER_LOCATION because
    177                         // of the mHighPowerRequestAppOpArray filter, but checking defensively.
    178                         if (opEntry.getOp() == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION) {
    179                             if (opEntry.isRunning()) {
    180                                 return true;
    181                             }
    182                         }
    183                     }
    184                 }
    185             }
    186         }
    187 
    188         return false;
    189     }
    190 
    191     // Updates the status view based on the current state of location requests.
    192     private void refreshViews() {
    193         if (mAreActiveLocationRequests) {
    194             mStatusBarManager.setIcon(LOCATION_STATUS_ICON_PLACEHOLDER, LOCATION_STATUS_ICON_ID, 0,
    195                     mContext.getString(R.string.accessibility_location_active));
    196         } else {
    197             mStatusBarManager.removeIcon(LOCATION_STATUS_ICON_PLACEHOLDER);
    198         }
    199     }
    200 
    201     // Reads the active location requests and updates the status view if necessary.
    202     private void updateActiveLocationRequests() {
    203         boolean hadActiveLocationRequests = mAreActiveLocationRequests;
    204         mAreActiveLocationRequests = areActiveHighPowerLocationRequests();
    205         if (mAreActiveLocationRequests != hadActiveLocationRequests) {
    206             refreshViews();
    207         }
    208     }
    209 
    210     private void locationSettingsChanged() {
    211         boolean isEnabled = isLocationEnabled();
    212         for (LocationSettingsChangeCallback cb : mSettingsChangeCallbacks) {
    213             cb.onLocationSettingsChanged(isEnabled);
    214         }
    215     }
    216 
    217     @Override
    218     public void onReceive(Context context, Intent intent) {
    219         final String action = intent.getAction();
    220         if (LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)) {
    221             updateActiveLocationRequests();
    222         }
    223     }
    224 }
    225