1 /* 2 * Copyright (C) 2020 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.server.location; 18 19 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; 20 import static android.app.AppOpsManager.OP_MONITOR_LOCATION; 21 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 22 23 import static com.android.server.location.CallerIdentity.PERMISSION_NONE; 24 import static com.android.server.location.LocationManagerService.D; 25 import static com.android.server.location.LocationManagerService.TAG; 26 27 import android.annotation.Nullable; 28 import android.app.AppOpsManager; 29 import android.content.Context; 30 import android.os.Binder; 31 import android.util.Log; 32 33 import com.android.internal.annotations.GuardedBy; 34 import com.android.internal.util.Preconditions; 35 import com.android.internal.util.function.pooled.PooledLambda; 36 import com.android.server.FgThread; 37 38 import java.util.Objects; 39 import java.util.concurrent.CopyOnWriteArrayList; 40 41 /** 42 * Provides helpers and listeners for appops. 43 */ 44 public class AppOpsHelper { 45 46 /** 47 * Listener for current user changes. 48 */ 49 public interface LocationAppOpListener { 50 51 /** 52 * Called when something has changed about a location appop for the given package. 53 */ onAppOpsChanged(String packageName)54 void onAppOpsChanged(String packageName); 55 } 56 57 private final Context mContext; 58 private final CopyOnWriteArrayList<LocationAppOpListener> mListeners; 59 60 @GuardedBy("this") 61 @Nullable 62 private AppOpsManager mAppOps; 63 AppOpsHelper(Context context)64 public AppOpsHelper(Context context) { 65 mContext = context; 66 mListeners = new CopyOnWriteArrayList<>(); 67 } 68 69 /** Called when system is ready. */ onSystemReady()70 public synchronized void onSystemReady() { 71 if (mAppOps != null) { 72 return; 73 } 74 75 mAppOps = Objects.requireNonNull(mContext.getSystemService(AppOpsManager.class)); 76 mAppOps.startWatchingMode( 77 AppOpsManager.OP_COARSE_LOCATION, 78 null, 79 AppOpsManager.WATCH_FOREGROUND_CHANGES, 80 new AppOpsManager.OnOpChangedInternalListener() { 81 public void onOpChanged(int op, String packageName) { 82 // invoked on ui thread, move to fg thread so ui thread isn't blocked 83 FgThread.getHandler().sendMessage( 84 PooledLambda.obtainMessage(AppOpsHelper::onAppOpChanged, 85 AppOpsHelper.this, packageName)); 86 } 87 }); 88 } 89 onAppOpChanged(String packageName)90 private void onAppOpChanged(String packageName) { 91 if (D) { 92 Log.v(TAG, "location appop changed for " + packageName); 93 } 94 95 for (LocationAppOpListener listener : mListeners) { 96 listener.onAppOpsChanged(packageName); 97 } 98 } 99 100 /** 101 * Adds a listener for app ops events. Callbacks occur on an unspecified thread. 102 */ addListener(LocationAppOpListener listener)103 public void addListener(LocationAppOpListener listener) { 104 mListeners.add(listener); 105 } 106 107 /** 108 * Removes a listener for app ops events. 109 */ removeListener(LocationAppOpListener listener)110 public void removeListener(LocationAppOpListener listener) { 111 mListeners.remove(listener); 112 } 113 114 /** 115 * Checks if the given identity may have locations delivered without noting that a location is 116 * being delivered. This is a looser guarantee than {@link #noteLocationAccess(CallerIdentity)}, 117 * and this function does not validate package arguments and so should not be used with 118 * unvalidated arguments or before actually delivering locations. 119 * 120 * @see AppOpsManager#checkOpNoThrow(int, int, String) 121 */ checkLocationAccess(CallerIdentity callerIdentity)122 public boolean checkLocationAccess(CallerIdentity callerIdentity) { 123 synchronized (this) { 124 Preconditions.checkState(mAppOps != null); 125 } 126 127 if (callerIdentity.permissionLevel == PERMISSION_NONE) { 128 return false; 129 } 130 131 long identity = Binder.clearCallingIdentity(); 132 try { 133 if (mContext.checkPermission( 134 CallerIdentity.asPermission(callerIdentity.permissionLevel), callerIdentity.pid, 135 callerIdentity.uid) != PERMISSION_GRANTED) { 136 return false; 137 } 138 139 return mAppOps.checkOpNoThrow( 140 CallerIdentity.asAppOp(callerIdentity.permissionLevel), 141 callerIdentity.uid, 142 callerIdentity.packageName) == AppOpsManager.MODE_ALLOWED; 143 } finally { 144 Binder.restoreCallingIdentity(identity); 145 } 146 } 147 148 /** 149 * Notes location access to the given identity, ie, location delivery. This method should be 150 * called right before a location is delivered, and if it returns false, the location should not 151 * be delivered. 152 */ noteLocationAccess(CallerIdentity callerIdentity)153 public boolean noteLocationAccess(CallerIdentity callerIdentity) { 154 if (callerIdentity.permissionLevel == PERMISSION_NONE) { 155 return false; 156 } 157 158 long identity = Binder.clearCallingIdentity(); 159 try { 160 if (mContext.checkPermission( 161 CallerIdentity.asPermission(callerIdentity.permissionLevel), callerIdentity.pid, 162 callerIdentity.uid) != PERMISSION_GRANTED) { 163 return false; 164 } 165 } finally { 166 Binder.restoreCallingIdentity(identity); 167 } 168 169 return noteOpNoThrow(CallerIdentity.asAppOp(callerIdentity.permissionLevel), 170 callerIdentity); 171 } 172 173 /** 174 * Notifies app ops that the given identity is using location at normal/low power levels. If 175 * this function returns false, do not later call 176 * {@link #stopLocationMonitoring(CallerIdentity)}. 177 */ startLocationMonitoring(CallerIdentity identity)178 public boolean startLocationMonitoring(CallerIdentity identity) { 179 return startLocationMonitoring(OP_MONITOR_LOCATION, identity); 180 } 181 182 /** 183 * Notifies app ops that the given identity is no longer using location at normal/low power 184 * levels. 185 */ stopLocationMonitoring(CallerIdentity identity)186 public void stopLocationMonitoring(CallerIdentity identity) { 187 stopLocationMonitoring(OP_MONITOR_LOCATION, identity); 188 } 189 190 /** 191 * Notifies app ops that the given identity is using location at high levels. If this function 192 * returns false, do not later call {@link #stopLocationMonitoring(CallerIdentity)}. 193 */ startHighPowerLocationMonitoring(CallerIdentity identity)194 public boolean startHighPowerLocationMonitoring(CallerIdentity identity) { 195 return startLocationMonitoring(OP_MONITOR_HIGH_POWER_LOCATION, identity); 196 } 197 198 /** 199 * Notifies app ops that the given identity is no longer using location at high power levels. 200 */ stopHighPowerLocationMonitoring(CallerIdentity identity)201 public void stopHighPowerLocationMonitoring(CallerIdentity identity) { 202 stopLocationMonitoring(OP_MONITOR_HIGH_POWER_LOCATION, identity); 203 } 204 205 /** 206 * Notes access to any mock location APIs. If this call returns false, access to the APIs should 207 * silently fail. 208 */ noteMockLocationAccess(CallerIdentity callerIdentity)209 public boolean noteMockLocationAccess(CallerIdentity callerIdentity) { 210 synchronized (this) { 211 Preconditions.checkState(mAppOps != null); 212 } 213 214 long identity = Binder.clearCallingIdentity(); 215 try { 216 // note that this is not the no throw version of noteOp, this call may throw exceptions 217 return mAppOps.noteOp( 218 AppOpsManager.OP_MOCK_LOCATION, 219 callerIdentity.uid, 220 callerIdentity.packageName, 221 callerIdentity.featureId, 222 callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; 223 } finally { 224 Binder.restoreCallingIdentity(identity); 225 } 226 } 227 startLocationMonitoring(int appOp, CallerIdentity callerIdentity)228 private boolean startLocationMonitoring(int appOp, CallerIdentity callerIdentity) { 229 synchronized (this) { 230 Preconditions.checkState(mAppOps != null); 231 } 232 233 long identity = Binder.clearCallingIdentity(); 234 try { 235 return mAppOps.startOpNoThrow( 236 appOp, 237 callerIdentity.uid, 238 callerIdentity.packageName, 239 false, 240 callerIdentity.featureId, 241 callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; 242 } finally { 243 Binder.restoreCallingIdentity(identity); 244 } 245 } 246 stopLocationMonitoring(int appOp, CallerIdentity callerIdentity)247 private void stopLocationMonitoring(int appOp, CallerIdentity callerIdentity) { 248 synchronized (this) { 249 Preconditions.checkState(mAppOps != null); 250 } 251 252 long identity = Binder.clearCallingIdentity(); 253 try { 254 mAppOps.finishOp( 255 appOp, 256 callerIdentity.uid, 257 callerIdentity.packageName, 258 callerIdentity.featureId); 259 } finally { 260 Binder.restoreCallingIdentity(identity); 261 } 262 } 263 noteOpNoThrow(int appOp, CallerIdentity callerIdentity)264 private boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { 265 synchronized (this) { 266 Preconditions.checkState(mAppOps != null); 267 } 268 269 long identity = Binder.clearCallingIdentity(); 270 try { 271 return mAppOps.noteOpNoThrow( 272 appOp, 273 callerIdentity.uid, 274 callerIdentity.packageName, 275 callerIdentity.featureId, 276 callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED; 277 } finally { 278 Binder.restoreCallingIdentity(identity); 279 } 280 } 281 } 282