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