1 /*
2  * Copyright (C) 2014 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.AppOpsManager;
22 import android.content.Context;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.IInterface;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Objects;
32 
33 /**
34  * A helper class that handles operations in remote listeners.
35  *
36  * @param <TRequest> the type of request.
37  * @param <TListener> the type of GNSS data listener.
38  */
39 public abstract class RemoteListenerHelper<TRequest, TListener extends IInterface> {
40 
41     protected static final int RESULT_SUCCESS = 0;
42     protected static final int RESULT_NOT_AVAILABLE = 1;
43     protected static final int RESULT_NOT_SUPPORTED = 2;
44     protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
45     protected static final int RESULT_INTERNAL_ERROR = 4;
46     protected static final int RESULT_UNKNOWN = 5;
47     protected static final int RESULT_NOT_ALLOWED = 6;
48 
49     protected final Handler mHandler;
50     private final String mTag;
51 
52     protected final Map<IBinder, IdentifiedListener> mListenerMap = new HashMap<>();
53 
54     protected final Context mContext;
55     protected final AppOpsManager mAppOps;
56 
57     private volatile boolean mIsRegistered;  // must access only on handler thread, or read-only
58 
59     private boolean mHasIsSupported;
60     private boolean mIsSupported;
61 
62     private int mLastReportedResult = RESULT_UNKNOWN;
63 
RemoteListenerHelper(Context context, Handler handler, String name)64     protected RemoteListenerHelper(Context context, Handler handler, String name) {
65         Objects.requireNonNull(name);
66         mHandler = handler;
67         mTag = name;
68         mContext = context;
69         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
70     }
71 
72     // read-only access for a dump() thread assured via volatile
isRegistered()73     public boolean isRegistered() {
74         return mIsRegistered;
75     }
76 
77     /**
78      * Adds GNSS data listener {@code listener} with caller identify {@code callerIdentify}.
79      */
addListener(@ullable TRequest request, @NonNull TListener listener, CallerIdentity callerIdentity)80     public void addListener(@Nullable TRequest request, @NonNull TListener listener,
81             CallerIdentity callerIdentity) {
82         Objects.requireNonNull(listener, "Attempted to register a 'null' listener.");
83         IBinder binder = listener.asBinder();
84         synchronized (mListenerMap) {
85             if (mListenerMap.containsKey(binder)) {
86                 // listener already added
87                 return;
88             }
89 
90             IdentifiedListener identifiedListener = new IdentifiedListener(request, listener,
91                     callerIdentity);
92             mListenerMap.put(binder, identifiedListener);
93 
94             // update statuses we already know about, starting from the ones that will never change
95             int result;
96             if (!isAvailableInPlatform()) {
97                 result = RESULT_NOT_AVAILABLE;
98             } else if (mHasIsSupported && !mIsSupported) {
99                 result = RESULT_NOT_SUPPORTED;
100             } else if (!isGpsEnabled()) {
101                 // only attempt to register if GPS is enabled, otherwise we will register once GPS
102                 // becomes available
103                 result = RESULT_GPS_LOCATION_DISABLED;
104             } else if (mHasIsSupported && mIsSupported) {
105                 tryRegister();
106                 // initially presume success, possible internal error could follow asynchornously
107                 result = RESULT_SUCCESS;
108             } else {
109                 // at this point if the supported flag is not set, the notification will be sent
110                 // asynchronously in the future
111                 return;
112             }
113             post(identifiedListener, getHandlerOperation(result));
114         }
115     }
116 
117     /**
118      * Remove GNSS data listener {@code listener}.
119      */
removeListener(@onNull TListener listener)120     public void removeListener(@NonNull TListener listener) {
121         Objects.requireNonNull(listener, "Attempted to remove a 'null' listener.");
122         synchronized (mListenerMap) {
123             mListenerMap.remove(listener.asBinder());
124             if (mListenerMap.isEmpty()) {
125                 tryUnregister();
126             }
127         }
128     }
129 
isAvailableInPlatform()130     protected abstract boolean isAvailableInPlatform();
isGpsEnabled()131     protected abstract boolean isGpsEnabled();
132     // must access only on handler thread
registerWithService()133     protected abstract int registerWithService();
unregisterFromService()134     protected abstract void unregisterFromService(); // must access only on handler thread
getHandlerOperation(int result)135     protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
136 
137     protected interface ListenerOperation<TListener extends IInterface> {
execute(TListener listener, CallerIdentity callerIdentity)138         void execute(TListener listener, CallerIdentity callerIdentity) throws RemoteException;
139     }
140 
foreach(ListenerOperation<TListener> operation)141     protected void foreach(ListenerOperation<TListener> operation) {
142         synchronized (mListenerMap) {
143             foreachUnsafe(operation);
144         }
145     }
146 
setSupported(boolean value)147     protected void setSupported(boolean value) {
148         synchronized (mListenerMap) {
149             mHasIsSupported = true;
150             mIsSupported = value;
151         }
152     }
153 
tryUpdateRegistrationWithService()154     protected void tryUpdateRegistrationWithService() {
155         synchronized (mListenerMap) {
156             if (!isGpsEnabled()) {
157                 tryUnregister();
158                 return;
159             }
160             if (mListenerMap.isEmpty()) {
161                 return;
162             }
163             tryRegister();
164         }
165     }
166 
updateResult()167     protected void updateResult() {
168         synchronized (mListenerMap) {
169             int newResult = calculateCurrentResultUnsafe();
170             if (mLastReportedResult == newResult) {
171                 return;
172             }
173             foreachUnsafe(getHandlerOperation(newResult));
174             mLastReportedResult = newResult;
175         }
176     }
177 
hasPermission(Context context, CallerIdentity callerIdentity)178     protected boolean hasPermission(Context context, CallerIdentity callerIdentity) {
179         if (LocationPermissionUtil.doesCallerReportToAppOps(context, callerIdentity)) {
180             // The caller is identified as a location provider that will report location
181             // access to AppOps. Skip noteOp but do checkOp to check for location permission.
182             return mAppOps.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.uid,
183                     callerIdentity.packageName) == AppOpsManager.MODE_ALLOWED;
184         }
185 
186         return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.uid,
187                 callerIdentity.packageName, callerIdentity.featureId, null)
188                 == AppOpsManager.MODE_ALLOWED;
189     }
190 
logPermissionDisabledEventNotReported(String tag, String packageName, String event)191     protected void logPermissionDisabledEventNotReported(String tag, String packageName,
192             String event) {
193         if (Log.isLoggable(tag, Log.DEBUG)) {
194             Log.d(tag, "Location permission disabled. Skipping " + event + " reporting for app: "
195                     + packageName);
196         }
197     }
198 
foreachUnsafe(ListenerOperation<TListener> operation)199     private void foreachUnsafe(ListenerOperation<TListener> operation) {
200         for (IdentifiedListener identifiedListener : mListenerMap.values()) {
201             post(identifiedListener, operation);
202         }
203     }
204 
post(IdentifiedListener identifiedListener, ListenerOperation<TListener> operation)205     private void post(IdentifiedListener identifiedListener,
206             ListenerOperation<TListener> operation) {
207         if (operation != null) {
208             mHandler.post(new HandlerRunnable(identifiedListener, operation));
209         }
210     }
211 
tryRegister()212     private void tryRegister() {
213         mHandler.post(new Runnable() {
214             int registrationState = RESULT_INTERNAL_ERROR;
215             @Override
216             public void run() {
217                 if (!mIsRegistered) {
218                     registrationState = registerWithService();
219                     mIsRegistered = registrationState == RESULT_SUCCESS;
220                 }
221                 if (!mIsRegistered) {
222                     // post back a failure
223                     mHandler.post(() -> {
224                         synchronized (mListenerMap) {
225                             foreachUnsafe(getHandlerOperation(registrationState));
226                         }
227                     });
228                 }
229             }
230         });
231     }
232 
tryUnregister()233     private void tryUnregister() {
234         mHandler.post(() -> {
235                     if (!mIsRegistered) {
236                         return;
237                     }
238                     unregisterFromService();
239                     mIsRegistered = false;
240                 }
241         );
242     }
243 
calculateCurrentResultUnsafe()244     private int calculateCurrentResultUnsafe() {
245         // update statuses we already know about, starting from the ones that will never change
246         if (!isAvailableInPlatform()) {
247             return RESULT_NOT_AVAILABLE;
248         }
249         if (!mHasIsSupported || mListenerMap.isEmpty()) {
250             // we'll update once we have a supported status available
251             return RESULT_UNKNOWN;
252         }
253         if (!mIsSupported) {
254             return RESULT_NOT_SUPPORTED;
255         }
256         if (!isGpsEnabled()) {
257             return RESULT_GPS_LOCATION_DISABLED;
258         }
259         return RESULT_SUCCESS;
260     }
261 
262     protected class IdentifiedListener {
263         @Nullable private final TRequest mRequest;
264         private final TListener mListener;
265         private final CallerIdentity mCallerIdentity;
266 
IdentifiedListener(@ullable TRequest request, @NonNull TListener listener, CallerIdentity callerIdentity)267         private IdentifiedListener(@Nullable TRequest request, @NonNull TListener listener,
268                 CallerIdentity callerIdentity) {
269             mListener = listener;
270             mRequest = request;
271             mCallerIdentity = callerIdentity;
272         }
273 
274         @Nullable
getRequest()275         public TRequest getRequest() {
276             return mRequest;
277         }
278     }
279 
280     private class HandlerRunnable implements Runnable {
281         private final IdentifiedListener mIdentifiedListener;
282         private final ListenerOperation<TListener> mOperation;
283 
HandlerRunnable(IdentifiedListener identifiedListener, ListenerOperation<TListener> operation)284         private HandlerRunnable(IdentifiedListener identifiedListener,
285                 ListenerOperation<TListener> operation) {
286             mIdentifiedListener = identifiedListener;
287             mOperation = operation;
288         }
289 
290         @Override
run()291         public void run() {
292             try {
293                 mOperation.execute(mIdentifiedListener.mListener,
294                         mIdentifiedListener.mCallerIdentity);
295             } catch (RemoteException e) {
296                 Log.v(mTag, "Error in monitored listener.", e);
297             }
298         }
299     }
300 }
301