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
17package com.android.server.location;
18
19import com.android.internal.util.Preconditions;
20
21import android.annotation.NonNull;
22import android.os.Handler;
23import android.os.IBinder;
24import android.os.IInterface;
25import android.os.RemoteException;
26import android.util.Log;
27
28import java.util.HashMap;
29
30/**
31 * A helper class, that handles operations in remote listeners, and tracks for remote process death.
32 */
33abstract class RemoteListenerHelper<TListener extends IInterface> {
34    protected static final int RESULT_SUCCESS = 0;
35    protected static final int RESULT_NOT_AVAILABLE = 1;
36    protected static final int RESULT_NOT_SUPPORTED = 2;
37    protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
38    protected static final int RESULT_INTERNAL_ERROR = 4;
39
40    private final Handler mHandler;
41    private final String mTag;
42
43    private final HashMap<IBinder, LinkedListener> mListenerMap = new HashMap<>();
44
45    private boolean mIsRegistered;
46    private boolean mHasIsSupported;
47    private boolean mIsSupported;
48
49    protected RemoteListenerHelper(Handler handler, String name) {
50        Preconditions.checkNotNull(name);
51        mHandler = handler;
52        mTag = name;
53    }
54
55    public boolean addListener(@NonNull TListener listener) {
56        Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener.");
57        IBinder binder = listener.asBinder();
58        LinkedListener deathListener = new LinkedListener(listener);
59        synchronized (mListenerMap) {
60            if (mListenerMap.containsKey(binder)) {
61                // listener already added
62                return true;
63            }
64            try {
65                binder.linkToDeath(deathListener, 0 /* flags */);
66            } catch (RemoteException e) {
67                // if the remote process registering the listener is already death, just swallow the
68                // exception and return
69                Log.v(mTag, "Remote listener already died.", e);
70                return false;
71            }
72            mListenerMap.put(binder, deathListener);
73
74            // update statuses we already know about, starting from the ones that will never change
75            int result;
76            if (!isAvailableInPlatform()) {
77                result = RESULT_NOT_AVAILABLE;
78            } else if (mHasIsSupported && !mIsSupported) {
79                result = RESULT_NOT_SUPPORTED;
80            } else if (!isGpsEnabled()) {
81                result = RESULT_GPS_LOCATION_DISABLED;
82            } else if (!tryRegister()) {
83                // only attempt to register if GPS is enabled, otherwise we will register once GPS
84                // becomes available
85                result = RESULT_INTERNAL_ERROR;
86            } else if (mHasIsSupported && mIsSupported) {
87                result = RESULT_SUCCESS;
88            } else {
89                // at this point if the supported flag is not set, the notification will be sent
90                // asynchronously in the future
91                return true;
92            }
93            post(listener, getHandlerOperation(result));
94        }
95        return true;
96    }
97
98    public void removeListener(@NonNull TListener listener) {
99        Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener.");
100        IBinder binder = listener.asBinder();
101        LinkedListener linkedListener;
102        synchronized (mListenerMap) {
103            linkedListener = mListenerMap.remove(binder);
104            if (mListenerMap.isEmpty()) {
105                tryUnregister();
106            }
107        }
108        if (linkedListener != null) {
109            binder.unlinkToDeath(linkedListener, 0 /* flags */);
110        }
111    }
112
113    public void onGpsEnabledChanged(boolean enabled) {
114        // handle first the sub-class implementation, so any error in registration can take
115        // precedence
116        handleGpsEnabledChanged(enabled);
117        synchronized (mListenerMap) {
118            if (!enabled) {
119                tryUnregister();
120                return;
121            }
122            if (mListenerMap.isEmpty()) {
123                return;
124            }
125            if (tryRegister()) {
126                // registration was successful, there is no need to update the state
127                return;
128            }
129            ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR);
130            foreachUnsafe(operation);
131        }
132    }
133
134    protected abstract boolean isAvailableInPlatform();
135    protected abstract boolean isGpsEnabled();
136    protected abstract boolean registerWithService();
137    protected abstract void unregisterFromService();
138    protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
139    protected abstract void handleGpsEnabledChanged(boolean enabled);
140
141    protected interface ListenerOperation<TListener extends IInterface> {
142        void execute(TListener listener) throws RemoteException;
143    }
144
145    protected void foreach(ListenerOperation<TListener> operation) {
146        synchronized (mListenerMap) {
147            foreachUnsafe(operation);
148        }
149    }
150
151    protected void setSupported(boolean value, ListenerOperation<TListener> notifier) {
152        synchronized (mListenerMap) {
153            mHasIsSupported = true;
154            mIsSupported = value;
155            foreachUnsafe(notifier);
156        }
157    }
158
159    private void foreachUnsafe(ListenerOperation<TListener> operation) {
160        for (LinkedListener linkedListener : mListenerMap.values()) {
161            post(linkedListener.getUnderlyingListener(), operation);
162        }
163    }
164
165    private void post(TListener listener, ListenerOperation<TListener> operation) {
166        if (operation != null) {
167            mHandler.post(new HandlerRunnable(listener, operation));
168        }
169    }
170
171    private boolean tryRegister() {
172        if (!mIsRegistered) {
173            mIsRegistered = registerWithService();
174        }
175        return mIsRegistered;
176    }
177
178    private void tryUnregister() {
179        if (!mIsRegistered) {
180            return;
181        }
182        unregisterFromService();
183        mIsRegistered = false;
184    }
185
186    private class LinkedListener implements IBinder.DeathRecipient {
187        private final TListener mListener;
188
189        public LinkedListener(@NonNull TListener listener) {
190            mListener = listener;
191        }
192
193        @NonNull
194        public TListener getUnderlyingListener() {
195            return mListener;
196        }
197
198        @Override
199        public void binderDied() {
200            Log.d(mTag, "Remote Listener died: " + mListener);
201            removeListener(mListener);
202        }
203    }
204
205    private class HandlerRunnable implements Runnable {
206        private final TListener mListener;
207        private final ListenerOperation<TListener> mOperation;
208
209        public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) {
210            mListener = listener;
211            mOperation = operation;
212        }
213
214        @Override
215        public void run() {
216            try {
217                mOperation.execute(mListener);
218            } catch (RemoteException e) {
219                Log.v(mTag, "Error in monitored listener.", e);
220            }
221        }
222    }
223}
224