• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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