/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.location; import com.android.internal.util.Preconditions; import android.annotation.NonNull; import android.os.Handler; import android.os.IBinder; import android.os.IInterface; import android.os.RemoteException; import android.util.Log; import java.util.HashMap; import java.util.Map; /** * A helper class, that handles operations in remote listeners, and tracks for remote process death. */ abstract class RemoteListenerHelper<TListener extends IInterface> { protected static final int RESULT_SUCCESS = 0; protected static final int RESULT_NOT_AVAILABLE = 1; protected static final int RESULT_NOT_SUPPORTED = 2; protected static final int RESULT_GPS_LOCATION_DISABLED = 3; protected static final int RESULT_INTERNAL_ERROR = 4; protected static final int RESULT_UNKNOWN = 5; private final Handler mHandler; private final String mTag; private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>(); private boolean mIsRegistered; private boolean mHasIsSupported; private boolean mIsSupported; private int mLastReportedResult = RESULT_UNKNOWN; protected RemoteListenerHelper(Handler handler, String name) { Preconditions.checkNotNull(name); mHandler = handler; mTag = name; } public boolean addListener(@NonNull TListener listener) { Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener."); IBinder binder = listener.asBinder(); LinkedListener deathListener = new LinkedListener(listener); synchronized (mListenerMap) { if (mListenerMap.containsKey(binder)) { // listener already added return true; } try { binder.linkToDeath(deathListener, 0 /* flags */); } catch (RemoteException e) { // if the remote process registering the listener is already death, just swallow the // exception and return Log.v(mTag, "Remote listener already died.", e); return false; } mListenerMap.put(binder, deathListener); // update statuses we already know about, starting from the ones that will never change int result; if (!isAvailableInPlatform()) { result = RESULT_NOT_AVAILABLE; } else if (mHasIsSupported && !mIsSupported) { result = RESULT_NOT_SUPPORTED; } else if (!isGpsEnabled()) { result = RESULT_GPS_LOCATION_DISABLED; } else if (!tryRegister()) { // only attempt to register if GPS is enabled, otherwise we will register once GPS // becomes available result = RESULT_INTERNAL_ERROR; } else if (mHasIsSupported && mIsSupported) { result = RESULT_SUCCESS; } else { // at this point if the supported flag is not set, the notification will be sent // asynchronously in the future return true; } post(listener, getHandlerOperation(result)); } return true; } public void removeListener(@NonNull TListener listener) { Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener."); IBinder binder = listener.asBinder(); LinkedListener linkedListener; synchronized (mListenerMap) { linkedListener = mListenerMap.remove(binder); if (mListenerMap.isEmpty()) { tryUnregister(); } } if (linkedListener != null) { binder.unlinkToDeath(linkedListener, 0 /* flags */); } } protected abstract boolean isAvailableInPlatform(); protected abstract boolean isGpsEnabled(); protected abstract boolean registerWithService(); protected abstract void unregisterFromService(); protected abstract ListenerOperation<TListener> getHandlerOperation(int result); protected interface ListenerOperation<TListener extends IInterface> { void execute(TListener listener) throws RemoteException; } protected void foreach(ListenerOperation<TListener> operation) { synchronized (mListenerMap) { foreachUnsafe(operation); } } protected void setSupported(boolean value) { synchronized (mListenerMap) { mHasIsSupported = true; mIsSupported = value; } } protected boolean tryUpdateRegistrationWithService() { synchronized (mListenerMap) { if (!isGpsEnabled()) { tryUnregister(); return true; } if (mListenerMap.isEmpty()) { return true; } if (tryRegister()) { // registration was successful, there is no need to update the state return true; } ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR); foreachUnsafe(operation); return false; } } protected void updateResult() { synchronized (mListenerMap) { int newResult = calculateCurrentResultUnsafe(); if (mLastReportedResult == newResult) { return; } foreachUnsafe(getHandlerOperation(newResult)); mLastReportedResult = newResult; } } private void foreachUnsafe(ListenerOperation<TListener> operation) { for (LinkedListener linkedListener : mListenerMap.values()) { post(linkedListener.getUnderlyingListener(), operation); } } private void post(TListener listener, ListenerOperation<TListener> operation) { if (operation != null) { mHandler.post(new HandlerRunnable(listener, operation)); } } private boolean tryRegister() { if (!mIsRegistered) { mIsRegistered = registerWithService(); } return mIsRegistered; } private void tryUnregister() { if (!mIsRegistered) { return; } unregisterFromService(); mIsRegistered = false; } private int calculateCurrentResultUnsafe() { // update statuses we already know about, starting from the ones that will never change if (!isAvailableInPlatform()) { return RESULT_NOT_AVAILABLE; } if (!mHasIsSupported || mListenerMap.isEmpty()) { // we'll update once we have a supported status available return RESULT_UNKNOWN; } if (!mIsSupported) { return RESULT_NOT_SUPPORTED; } if (!isGpsEnabled()) { return RESULT_GPS_LOCATION_DISABLED; } return RESULT_SUCCESS; } private class LinkedListener implements IBinder.DeathRecipient { private final TListener mListener; public LinkedListener(@NonNull TListener listener) { mListener = listener; } @NonNull public TListener getUnderlyingListener() { return mListener; } @Override public void binderDied() { Log.d(mTag, "Remote Listener died: " + mListener); removeListener(mListener); } } private class HandlerRunnable implements Runnable { private final TListener mListener; private final ListenerOperation<TListener> mOperation; public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) { mListener = listener; mOperation = operation; } @Override public void run() { try { mOperation.execute(mListener); } catch (RemoteException e) { Log.v(mTag, "Error in monitored listener.", e); } } } }