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