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