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