1/*
2 * Copyright (C) 2015 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 */
16package com.android.car;
17
18import android.car.CarAppContextManager;
19import android.car.IAppContext;
20import android.car.IAppContextListener;
21import android.content.Context;
22import android.os.Binder;
23import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.Looper;
26import android.os.Message;
27import android.os.RemoteException;
28import android.util.Log;
29
30import com.android.car.hal.VehicleHal;
31
32import java.io.PrintWriter;
33import java.util.HashMap;
34
35public class AppContextService extends IAppContext.Stub implements CarServiceBase,
36        BinderInterfaceContainer.BinderEventHandler<IAppContextListener> {
37    private static final boolean DBG = true;
38    private static final boolean DBG_EVENT = false;
39
40    private final ClientHolder mAllClients;
41    /** K: context flag, V: client owning it */
42    private final HashMap<Integer, ClientInfo> mContextOwners = new HashMap<>();
43    private int mActiveContexts;
44
45    private final HandlerThread mHandlerThread;
46    private final DispatchHandler mDispatchHandler;
47
48    public AppContextService(Context context) {
49        mAllClients = new ClientHolder(this);
50        mHandlerThread = new HandlerThread(AppContextService.class.getSimpleName());
51        mHandlerThread.start();
52        mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
53    }
54
55    @Override
56    public void registerContextListener(IAppContextListener listener, int filter) {
57        synchronized (this) {
58            ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
59            if (info == null) {
60                info = new ClientInfo(mAllClients, listener, Binder.getCallingUid(),
61                        Binder.getCallingPid(), filter);
62                mAllClients.addBinderInterface(info);
63            } else {
64                info.setFilter(filter);
65            }
66        }
67    }
68
69    @Override
70    public void unregisterContextListener(IAppContextListener listener) {
71        synchronized (this) {
72            ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
73            if (info == null) {
74                return;
75            }
76            resetActiveContexts(listener, info.getOwnedContexts());
77            mAllClients.removeBinder(listener);
78        }
79    }
80
81    @Override
82    public int getActiveAppContexts() {
83        synchronized (this) {
84            return mActiveContexts;
85        }
86    }
87
88    @Override
89    public boolean isOwningContext(IAppContextListener listener, int context) {
90        synchronized (this) {
91            ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
92            if (info == null) {
93                return false;
94            }
95            int ownedContexts = info.getOwnedContexts();
96            return (ownedContexts & context) == context;
97        }
98    }
99
100    @Override
101    public void setActiveContexts(IAppContextListener listener, int contexts) {
102        synchronized (this) {
103            ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
104            if (info == null) {
105                throw new IllegalStateException("listener not registered");
106            }
107            int alreadyOwnedContexts = info.getOwnedContexts();
108            int addedContexts = 0;
109            for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG;
110                    c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) {
111                if ((c & contexts) != 0 && (c & alreadyOwnedContexts) == 0) {
112                    ClientInfo ownerInfo = mContextOwners.get(c);
113                    if (ownerInfo != null && ownerInfo != info) {
114                        //TODO check if current owner is having fore-ground activity. If yes,
115                        //reject request. Always grant if requestor is fore-ground activity.
116                        ownerInfo.setOwnedContexts(ownerInfo.getOwnedContexts() & ~c);
117                        mDispatchHandler.requestAppContextOwnershipLossDispatch(
118                                ownerInfo.binderInterface, c);
119                        if (DBG) {
120                            Log.i(CarLog.TAG_APP_CONTEXT, "losing context " +
121                                    Integer.toHexString(c) + "," + ownerInfo.toString());
122                        }
123                    } else {
124                        addedContexts |= c;
125                    }
126                    mContextOwners.put(c, info);
127                }
128            }
129            info.setOwnedContexts(alreadyOwnedContexts | contexts);
130            mActiveContexts |= addedContexts;
131            if (addedContexts != 0) {
132                if (DBG) {
133                    Log.i(CarLog.TAG_APP_CONTEXT, "setting context " +
134                            Integer.toHexString(addedContexts) + "," + info.toString());
135                }
136                for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client :
137                    mAllClients.getInterfaces()) {
138                    ClientInfo clientInfo = (ClientInfo) client;
139                    // dispatch events only when there is change after filter and the listener
140                    // is not coming from the current caller.
141                    int clientFilter = clientInfo.getFilter();
142                    if ((addedContexts & clientFilter) != 0 && clientInfo != info) {
143                        mDispatchHandler.requestAppContextChangeDispatch(clientInfo.binderInterface,
144                                mActiveContexts & clientFilter);
145                    }
146                }
147            }
148        }
149    }
150
151    @Override
152    public void resetActiveContexts(IAppContextListener listener, int contexts) {
153        synchronized (this) {
154            ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
155            if (info == null) {
156                // ignore as this client cannot have owned anything.
157                return;
158            }
159            if ((contexts & mActiveContexts) == 0) {
160                // ignore as none of them are active;
161                return;
162            }
163            int removedContexts = 0;
164            int currentlyOwnedContexts = info.getOwnedContexts();
165            for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG;
166                    c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) {
167                if ((c & contexts) != 0 && (c & currentlyOwnedContexts) != 0) {
168                    removedContexts |= c;
169                    mContextOwners.remove(c);
170                }
171            }
172            if (removedContexts != 0) {
173                mActiveContexts &= ~removedContexts;
174                info.setOwnedContexts(currentlyOwnedContexts & ~removedContexts);
175                if (DBG) {
176                    Log.i(CarLog.TAG_APP_CONTEXT, "resetting context " +
177                            Integer.toHexString(removedContexts) + "," + info.toString());
178                }
179                for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client :
180                    mAllClients.getInterfaces()) {
181                    ClientInfo clientInfo = (ClientInfo) client;
182                    int clientFilter = clientInfo.getFilter();
183                    if ((removedContexts & clientFilter) != 0 && clientInfo != info) {
184                        mDispatchHandler.requestAppContextChangeDispatch(clientInfo.binderInterface,
185                                mActiveContexts & clientFilter);
186                    }
187                }
188            }
189        }
190    }
191
192    @Override
193    public void init() {
194        // nothing to do
195    }
196
197    @Override
198    public void release() {
199        synchronized (this) {
200            mAllClients.clear();
201            mContextOwners.clear();
202            mActiveContexts = 0;
203        }
204    }
205
206    @Override
207    public void onBinderDeath(
208            BinderInterfaceContainer.BinderInterface<IAppContextListener> bInterface) {
209        ClientInfo info = (ClientInfo) bInterface;
210        int ownedContexts = info.getOwnedContexts();
211        if (ownedContexts != 0) {
212            resetActiveContexts(bInterface.binderInterface, ownedContexts);
213        }
214    }
215
216    @Override
217    public void dump(PrintWriter writer) {
218        writer.println("**AppContextService**");
219        synchronized (this) {
220            writer.println("mActiveContexts:" + Integer.toHexString(mActiveContexts));
221            for (BinderInterfaceContainer.BinderInterface<IAppContextListener> client :
222                mAllClients.getInterfaces()) {
223                ClientInfo clientInfo = (ClientInfo) client;
224                writer.println(clientInfo.toString());
225            }
226        }
227    }
228
229    /**
230     * Returns true if process with given uid and pid owns provided context.
231     */
232    public boolean isContextOwner(int uid, int pid, int context) {
233        synchronized (this) {
234            if (mContextOwners.containsKey(context)) {
235                ClientInfo clientInfo = mContextOwners.get(context);
236                return clientInfo.uid == uid && clientInfo.pid == pid;
237            }
238        }
239        return false;
240    }
241
242    private void dispatchAppContextOwnershipLoss(IAppContextListener listener, int contexts) {
243        try {
244            listener.onAppContextOwnershipLoss(contexts);
245        } catch (RemoteException e) {
246        }
247    }
248
249    private void dispatchAppContextChange(IAppContextListener listener, int contexts) {
250        try {
251            listener.onAppContextChange(contexts);
252        } catch (RemoteException e) {
253        }
254    }
255
256    private static class ClientHolder extends BinderInterfaceContainer<IAppContextListener> {
257        private ClientHolder(AppContextService service) {
258            super(service);
259        }
260    }
261
262    private static class ClientInfo extends
263            BinderInterfaceContainer.BinderInterface<IAppContextListener> {
264        private final int uid;
265        private final int pid;
266        private int mFilter;
267        /** contexts owned by this client */
268        private int mOwnedContexts;
269
270        private ClientInfo(ClientHolder holder, IAppContextListener binder, int uid, int pid,
271                int filter) {
272            super(holder, binder);
273            this.uid = uid;
274            this.pid = pid;
275            this.mFilter = filter;
276        }
277
278        private synchronized int getFilter() {
279            return mFilter;
280        }
281
282        private synchronized void setFilter(int filter) {
283            mFilter = filter;
284        }
285
286        private synchronized int getOwnedContexts() {
287            if (DBG_EVENT) {
288                Log.i(CarLog.TAG_APP_CONTEXT, "getOwnedContexts " +
289                        Integer.toHexString(mOwnedContexts));
290            }
291            return mOwnedContexts;
292        }
293
294        private synchronized void setOwnedContexts(int contexts) {
295            if (DBG_EVENT) {
296                Log.i(CarLog.TAG_APP_CONTEXT, "setOwnedContexts " + Integer.toHexString(contexts));
297            }
298            mOwnedContexts = contexts;
299        }
300
301        @Override
302        public String toString() {
303            synchronized (this) {
304                return "ClientInfo{uid=" + uid + ",pid=" + pid +
305                        ",filter=" + Integer.toHexString(mFilter) +
306                        ",owned=" + Integer.toHexString(mOwnedContexts) + "}";
307            }
308        }
309    }
310
311    private class DispatchHandler extends Handler {
312        private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0;
313        private static final int MSG_DISPATCH_CONTEXT_CHANGE = 1;
314
315        private DispatchHandler(Looper looper) {
316            super(looper);
317        }
318
319        private void requestAppContextOwnershipLossDispatch(IAppContextListener listener,
320                int contexts) {
321            Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, contexts, 0, listener);
322            sendMessage(msg);
323        }
324
325        private void requestAppContextChangeDispatch(IAppContextListener listener, int contexts) {
326            Message msg = obtainMessage(MSG_DISPATCH_CONTEXT_CHANGE, contexts, 0, listener);
327            sendMessage(msg);
328        }
329
330        @Override
331        public void handleMessage(Message msg) {
332            switch (msg.what) {
333                case MSG_DISPATCH_OWNERSHIP_LOSS:
334                    dispatchAppContextOwnershipLoss((IAppContextListener) msg.obj, msg.arg1);
335                    break;
336                case MSG_DISPATCH_CONTEXT_CHANGE:
337                    dispatchAppContextChange((IAppContextListener) msg.obj, msg.arg1);
338                    break;
339            }
340        }
341    }
342}
343