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