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