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