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.lang.Runnable;
29import java.util.HashMap;
30import java.util.Map;
31
32/**
33 * A helper class, that handles operations in remote listeners, and tracks for remote process death.
34 */
35abstract class RemoteListenerHelper<TListener extends IInterface> {
36
37    protected static final int RESULT_SUCCESS = 0;
38    protected static final int RESULT_NOT_AVAILABLE = 1;
39    protected static final int RESULT_NOT_SUPPORTED = 2;
40    protected static final int RESULT_GPS_LOCATION_DISABLED = 3;
41    protected static final int RESULT_INTERNAL_ERROR = 4;
42    protected static final int RESULT_UNKNOWN = 5;
43
44    private final Handler mHandler;
45    private final String mTag;
46
47    private final Map<IBinder, LinkedListener> mListenerMap = new HashMap<>();
48
49    private boolean mIsRegistered;  // must access only on handler thread
50    private boolean mHasIsSupported;
51    private boolean mIsSupported;
52
53    private int mLastReportedResult = RESULT_UNKNOWN;
54
55    protected RemoteListenerHelper(Handler handler, String name) {
56        Preconditions.checkNotNull(name);
57        mHandler = handler;
58        mTag = name;
59    }
60
61    public boolean addListener(@NonNull TListener listener) {
62        Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener.");
63        IBinder binder = listener.asBinder();
64        LinkedListener deathListener = new LinkedListener(listener);
65        synchronized (mListenerMap) {
66            if (mListenerMap.containsKey(binder)) {
67                // listener already added
68                return true;
69            }
70            try {
71                binder.linkToDeath(deathListener, 0 /* flags */);
72            } catch (RemoteException e) {
73                // if the remote process registering the listener is already death, just swallow the
74                // exception and return
75                Log.v(mTag, "Remote listener already died.", e);
76                return false;
77            }
78            mListenerMap.put(binder, deathListener);
79
80            // update statuses we already know about, starting from the ones that will never change
81            int result;
82            if (!isAvailableInPlatform()) {
83                result = RESULT_NOT_AVAILABLE;
84            } else if (mHasIsSupported && !mIsSupported) {
85                result = RESULT_NOT_SUPPORTED;
86            } else if (!isGpsEnabled()) {
87                // only attempt to register if GPS is enabled, otherwise we will register once GPS
88                // becomes available
89                result = RESULT_GPS_LOCATION_DISABLED;
90            } else if (mHasIsSupported && mIsSupported) {
91                tryRegister();
92                // initially presume success, possible internal error could follow asynchornously
93                result = RESULT_SUCCESS;
94            } else {
95                // at this point if the supported flag is not set, the notification will be sent
96                // asynchronously in the future
97                return true;
98            }
99            post(listener, getHandlerOperation(result));
100        }
101        return true;
102    }
103
104    public void removeListener(@NonNull TListener listener) {
105        Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener.");
106        IBinder binder = listener.asBinder();
107        LinkedListener linkedListener;
108        synchronized (mListenerMap) {
109            linkedListener = mListenerMap.remove(binder);
110            if (mListenerMap.isEmpty()) {
111                tryUnregister();
112            }
113        }
114        if (linkedListener != null) {
115            binder.unlinkToDeath(linkedListener, 0 /* flags */);
116        }
117    }
118
119    protected abstract boolean isAvailableInPlatform();
120    protected abstract boolean isGpsEnabled();
121    protected abstract boolean registerWithService(); // must access only on handler thread
122    protected abstract void unregisterFromService(); // must access only on handler thread
123    protected abstract ListenerOperation<TListener> getHandlerOperation(int result);
124
125    protected interface ListenerOperation<TListener extends IInterface> {
126        void execute(TListener listener) throws RemoteException;
127    }
128
129    protected void foreach(ListenerOperation<TListener> operation) {
130        synchronized (mListenerMap) {
131            foreachUnsafe(operation);
132        }
133    }
134
135    protected void setSupported(boolean value) {
136        synchronized (mListenerMap) {
137            mHasIsSupported = true;
138            mIsSupported = value;
139        }
140    }
141
142    protected void tryUpdateRegistrationWithService() {
143        synchronized (mListenerMap) {
144            if (!isGpsEnabled()) {
145                tryUnregister();
146                return;
147            }
148            if (mListenerMap.isEmpty()) {
149                return;
150            }
151            tryRegister();
152        }
153    }
154
155    protected void updateResult() {
156        synchronized (mListenerMap) {
157            int newResult = calculateCurrentResultUnsafe();
158            if (mLastReportedResult == newResult) {
159                return;
160            }
161            foreachUnsafe(getHandlerOperation(newResult));
162            mLastReportedResult = newResult;
163        }
164    }
165
166    private void foreachUnsafe(ListenerOperation<TListener> operation) {
167        for (LinkedListener linkedListener : mListenerMap.values()) {
168            post(linkedListener.getUnderlyingListener(), operation);
169        }
170    }
171
172    private void post(TListener listener, ListenerOperation<TListener> operation) {
173        if (operation != null) {
174            mHandler.post(new HandlerRunnable(listener, operation));
175        }
176    }
177
178    private void tryRegister() {
179        mHandler.post(new Runnable() {
180            @Override
181            public void run() {
182                if (!mIsRegistered) {
183                    mIsRegistered = registerWithService();
184                }
185                if (!mIsRegistered) {
186                    // post back a failure
187                    mHandler.post(new Runnable() {
188                        @Override
189                        public void run() {
190                            synchronized (mListenerMap) {
191                                ListenerOperation<TListener> operation = getHandlerOperation(RESULT_INTERNAL_ERROR);
192                                foreachUnsafe(operation);
193                            }
194                        }
195                    });
196                }
197            }
198        });
199    }
200
201    private void tryUnregister() {
202        mHandler.post(new Runnable() {
203            @Override
204            public void run() {
205                if (!mIsRegistered) {
206                    return;
207                }
208                unregisterFromService();
209                mIsRegistered = false;
210            }
211        });
212    }
213
214    private int calculateCurrentResultUnsafe() {
215        // update statuses we already know about, starting from the ones that will never change
216        if (!isAvailableInPlatform()) {
217            return RESULT_NOT_AVAILABLE;
218        }
219        if (!mHasIsSupported || mListenerMap.isEmpty()) {
220            // we'll update once we have a supported status available
221            return RESULT_UNKNOWN;
222        }
223        if (!mIsSupported) {
224            return RESULT_NOT_SUPPORTED;
225        }
226        if (!isGpsEnabled()) {
227            return RESULT_GPS_LOCATION_DISABLED;
228        }
229        return RESULT_SUCCESS;
230    }
231
232    private class LinkedListener implements IBinder.DeathRecipient {
233        private final TListener mListener;
234
235        public LinkedListener(@NonNull TListener listener) {
236            mListener = listener;
237        }
238
239        @NonNull
240        public TListener getUnderlyingListener() {
241            return mListener;
242        }
243
244        @Override
245        public void binderDied() {
246            Log.d(mTag, "Remote Listener died: " + mListener);
247            removeListener(mListener);
248        }
249    }
250
251    private class HandlerRunnable implements Runnable {
252        private final TListener mListener;
253        private final ListenerOperation<TListener> mOperation;
254
255        public HandlerRunnable(TListener listener, ListenerOperation<TListener> operation) {
256            mListener = listener;
257            mOperation = operation;
258        }
259
260        @Override
261        public void run() {
262            try {
263                mOperation.execute(mListener);
264            } catch (RemoteException e) {
265                Log.v(mTag, "Error in monitored listener.", e);
266            }
267        }
268    }
269}
270