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.CarAppFocusManager;
19import android.car.IAppFocus;
20import android.car.IAppFocusListener;
21import android.car.IAppFocusOwnershipCallback;
22import android.content.Context;
23import android.os.Binder;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Looper;
27import android.os.Message;
28import android.os.RemoteException;
29import android.util.Log;
30
31import java.io.PrintWriter;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.Map;
35import java.util.Set;
36import java.util.concurrent.CopyOnWriteArrayList;
37
38/**
39 * App focus service ensures only one instance of application type is active at a time.
40 */
41public class AppFocusService extends IAppFocus.Stub implements CarServiceBase,
42        BinderInterfaceContainer.BinderEventHandler<IAppFocusOwnershipCallback> {
43    private static final boolean DBG = false;
44    private static final boolean DBG_EVENT = false;
45
46    private final SystemActivityMonitoringService mSystemActivityMonitoringService;
47    private final ClientHolder mAllChangeClients;
48    private final OwnershipClientHolder mAllOwnershipClients;
49    /** K: appType, V: client owning it */
50    private final HashMap<Integer, OwnershipClientInfo> mFocusOwners = new HashMap<>();
51    private final Set<Integer> mActiveAppTypes = new HashSet<>();
52    private final CopyOnWriteArrayList<FocusOwnershipCallback> mFocusOwnershipCallbacks =
53            new CopyOnWriteArrayList<>();
54    private final BinderInterfaceContainer.BinderEventHandler<IAppFocusListener>
55            mAllBinderEventHandler = bInterface -> { /* nothing to do.*/ };
56
57    private DispatchHandler mDispatchHandler;
58    private HandlerThread mHandlerThread;
59
60    public AppFocusService(Context context,
61            SystemActivityMonitoringService systemActivityMonitoringService) {
62        mSystemActivityMonitoringService = systemActivityMonitoringService;
63        mAllChangeClients = new ClientHolder(mAllBinderEventHandler);
64        mAllOwnershipClients = new OwnershipClientHolder(this);
65    }
66
67    @Override
68    public void registerFocusListener(IAppFocusListener listener, int appType) {
69        synchronized (this) {
70            ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
71            if (info == null) {
72                info = new ClientInfo(mAllChangeClients, listener, Binder.getCallingUid(),
73                        Binder.getCallingPid(), appType);
74                mAllChangeClients.addBinderInterface(info);
75            } else {
76                info.addAppType(appType);
77            }
78        }
79    }
80
81    @Override
82    public void unregisterFocusListener(IAppFocusListener listener, int appType) {
83        synchronized (this) {
84            ClientInfo info = (ClientInfo) mAllChangeClients.getBinderInterface(listener);
85            if (info == null) {
86                return;
87            }
88            info.removeAppType(appType);
89            if (info.getAppTypes().isEmpty()) {
90                mAllChangeClients.removeBinder(listener);
91            }
92        }
93    }
94
95    @Override
96    public int[] getActiveAppTypes() {
97        synchronized (this) {
98            return toIntArray(mActiveAppTypes);
99        }
100    }
101
102    @Override
103    public boolean isOwningFocus(IAppFocusOwnershipCallback callback, int appType) {
104        synchronized (this) {
105            OwnershipClientInfo info =
106                    (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
107            if (info == null) {
108                return false;
109            }
110            return info.getOwnedAppTypes().contains(appType);
111        }
112    }
113
114    @Override
115    public int requestAppFocus(IAppFocusOwnershipCallback callback, int appType) {
116        synchronized (this) {
117            OwnershipClientInfo info =
118                    (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
119            if (info == null) {
120                info = new OwnershipClientInfo(mAllOwnershipClients, callback,
121                        Binder.getCallingUid(), Binder.getCallingPid());
122                mAllOwnershipClients.addBinderInterface(info);
123            }
124            Set<Integer> alreadyOwnedAppTypes = info.getOwnedAppTypes();
125            if (!alreadyOwnedAppTypes.contains(appType)) {
126                OwnershipClientInfo ownerInfo = mFocusOwners.get(appType);
127                if (ownerInfo != null && ownerInfo != info) {
128                    if (mSystemActivityMonitoringService.isInForeground(
129                                ownerInfo.getPid(), ownerInfo.getUid()) &&
130                        !mSystemActivityMonitoringService.isInForeground(
131                                info.getPid(), info.getUid())) {
132                        Log.w(CarLog.TAG_APP_FOCUS, "Focus request failed for non-foreground app("
133                              + "pid=" + info.getPid() + ", uid=" + info.getUid() + ")."
134                              + "Foreground app (pid=" + ownerInfo.getPid() + ", uid="
135                              + ownerInfo.getUid() + ") owns it.");
136                        return CarAppFocusManager.APP_FOCUS_REQUEST_FAILED;
137                    }
138                    ownerInfo.removeOwnedAppType(appType);
139                    mDispatchHandler.requestAppFocusOwnershipLossDispatch(
140                            ownerInfo.binderInterface, appType);
141                    if (DBG) {
142                        Log.i(CarLog.TAG_APP_FOCUS, "losing app type "
143                                + appType + "," + ownerInfo.toString());
144                    }
145                }
146                updateFocusOwner(appType, info);
147            }
148            info.addOwnedAppType(appType);
149            mDispatchHandler.requestAppFocusOwnershipGrantDispatch(
150                    info.binderInterface, appType);
151            if (mActiveAppTypes.add(appType)) {
152                if (DBG) {
153                    Log.i(CarLog.TAG_APP_FOCUS, "adding active app type " + appType + ","
154                            + info.toString());
155                }
156                for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
157                        mAllChangeClients.getInterfaces()) {
158                    ClientInfo clientInfo = (ClientInfo) client;
159                    // dispatch events only when there is change after filter and the listener
160                    // is not coming from the current caller.
161                    if (clientInfo.getAppTypes().contains(appType)) {
162                        mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
163                                appType, true);
164                    }
165                }
166            }
167        }
168        return CarAppFocusManager.APP_FOCUS_REQUEST_SUCCEEDED;
169    }
170
171    @Override
172    public void abandonAppFocus(IAppFocusOwnershipCallback callback, int appType) {
173        synchronized (this) {
174            OwnershipClientInfo info =
175                    (OwnershipClientInfo) mAllOwnershipClients.getBinderInterface(callback);
176            if (info == null) {
177                // ignore as this client cannot have owned anything.
178                return;
179            }
180            if (!mActiveAppTypes.contains(appType)) {
181                // ignore as none of them are active;
182                return;
183            }
184            Set<Integer> currentlyOwnedAppTypes = info.getOwnedAppTypes();
185            if (!currentlyOwnedAppTypes.contains(appType)) {
186                // ignore as listener doesn't own focus.
187                return;
188            }
189            if (mFocusOwners.remove(appType) != null) {
190                mActiveAppTypes.remove(appType);
191                info.removeOwnedAppType(appType);
192                if (DBG) {
193                    Log.i(CarLog.TAG_APP_FOCUS, "abandoning focus " + appType
194                            + "," + info.toString());
195                }
196                for (FocusOwnershipCallback ownershipCallback : mFocusOwnershipCallbacks) {
197                    ownershipCallback.onFocusAbandoned(appType, info.mUid, info.mPid);
198                }
199                for (BinderInterfaceContainer.BinderInterface<IAppFocusListener> client :
200                        mAllChangeClients.getInterfaces()) {
201                    ClientInfo clientInfo = (ClientInfo) client;
202                    if (clientInfo.getAppTypes().contains(appType)) {
203                        mDispatchHandler.requestAppFocusChangeDispatch(clientInfo.binderInterface,
204                                appType, false);
205                    }
206                }
207            }
208        }
209    }
210
211    @Override
212    public void init() {
213        synchronized (this) {
214            mHandlerThread = new HandlerThread(AppFocusService.class.getSimpleName());
215            mHandlerThread.start();
216            mDispatchHandler = new DispatchHandler(mHandlerThread.getLooper());
217        }
218    }
219
220    @Override
221    public void release() {
222        synchronized (this) {
223            mHandlerThread.quitSafely();
224            try {
225                mHandlerThread.join(1000);
226            } catch (InterruptedException e) {
227                Log.e(CarLog.TAG_APP_FOCUS, "Timeout while waiting for handler thread to join.");
228            }
229            mDispatchHandler = null;
230            mAllChangeClients.clear();
231            mAllOwnershipClients.clear();
232            mFocusOwners.clear();
233            mActiveAppTypes.clear();
234        }
235    }
236
237    @Override
238    public void onBinderDeath(
239            BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> bInterface) {
240        OwnershipClientInfo info = (OwnershipClientInfo) bInterface;
241        for (Integer appType : info.getOwnedAppTypes()) {
242            abandonAppFocus(bInterface.binderInterface, appType);
243        }
244    }
245
246    @Override
247    public void dump(PrintWriter writer) {
248        writer.println("**AppFocusService**");
249        synchronized (this) {
250            writer.println("mActiveAppTypes:" + mActiveAppTypes);
251            for (BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> client :
252                    mAllOwnershipClients.getInterfaces()) {
253                OwnershipClientInfo clientInfo = (OwnershipClientInfo) client;
254                writer.println(clientInfo.toString());
255            }
256        }
257    }
258
259    /**
260     * Returns true if process with given uid and pid owns provided focus.
261     */
262    public boolean isFocusOwner(int uid, int pid, int appType) {
263        synchronized (this) {
264            if (mFocusOwners.containsKey(appType)) {
265                OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
266                return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
267            }
268        }
269        return false;
270    }
271
272    /**
273     * Defines callback functions that will be called when ownership has been changed.
274     */
275    public interface FocusOwnershipCallback {
276        void onFocusAcquired(int appType, int uid, int pid);
277        void onFocusAbandoned(int appType, int uid, int pid);
278    }
279
280    /**
281     * Registers callback.
282     *
283     * If any focus already acquired it will trigger
284     * {@link FocusOwnershipCallback#onFocusAcquired} call immediately in the same thread.
285     */
286    public void registerContextOwnerChangedCallback(FocusOwnershipCallback callback) {
287        mFocusOwnershipCallbacks.add(callback);
288
289        HashSet<Map.Entry<Integer, OwnershipClientInfo>> owners;
290        synchronized (this) {
291            owners = new HashSet<>(mFocusOwners.entrySet());
292        }
293
294        for (Map.Entry<Integer, OwnershipClientInfo> entry : owners) {
295            OwnershipClientInfo clientInfo = entry.getValue();
296            callback.onFocusAcquired(entry.getKey(), clientInfo.getUid(), clientInfo.getPid());
297        }
298    }
299
300    /**
301     * Unregisters provided callback.
302     */
303    public void unregisterContextOwnerChangedCallback(FocusOwnershipCallback callback) {
304        mFocusOwnershipCallbacks.remove(callback);
305    }
306
307    private void updateFocusOwner(int appType, OwnershipClientInfo owner) {
308        CarServiceUtils.runOnMain(() -> {
309            synchronized (this) {
310                mFocusOwners.put(appType, owner);
311            }
312
313            for (FocusOwnershipCallback callback : mFocusOwnershipCallbacks) {
314                callback.onFocusAcquired(appType, owner.getUid(), owner.getPid());
315            }
316        });
317    }
318
319    private void dispatchAppFocusOwnershipLoss(IAppFocusOwnershipCallback callback, int appType) {
320        try {
321            callback.onAppFocusOwnershipLost(appType);
322        } catch (RemoteException e) {
323        }
324    }
325
326    private void dispatchAppFocusOwnershipGrant(IAppFocusOwnershipCallback callback, int appType) {
327        try {
328            callback.onAppFocusOwnershipGranted(appType);
329        } catch (RemoteException e) {
330        }
331    }
332
333    private void dispatchAppFocusChange(IAppFocusListener listener, int appType, boolean active) {
334        try {
335            listener.onAppFocusChanged(appType, active);
336        } catch (RemoteException e) {
337        }
338    }
339
340    private static class ClientHolder extends BinderInterfaceContainer<IAppFocusListener> {
341        private ClientHolder(BinderEventHandler<IAppFocusListener> holder) {
342            super(holder);
343        }
344    }
345
346    private static class OwnershipClientHolder extends
347            BinderInterfaceContainer<IAppFocusOwnershipCallback> {
348        private OwnershipClientHolder(AppFocusService service) {
349            super(service);
350        }
351    }
352
353    private static class ClientInfo extends
354            BinderInterfaceContainer.BinderInterface<IAppFocusListener> {
355        private final int mUid;
356        private final int mPid;
357        private final Set<Integer> mAppTypes = new HashSet<>();
358
359        private ClientInfo(ClientHolder holder, IAppFocusListener binder, int uid, int pid,
360                int appType) {
361            super(holder, binder);
362            this.mUid = uid;
363            this.mPid = pid;
364            this.mAppTypes.add(appType);
365        }
366
367        private synchronized Set<Integer> getAppTypes() {
368            return mAppTypes;
369        }
370
371        private synchronized boolean addAppType(Integer appType) {
372            return mAppTypes.add(appType);
373        }
374
375        private synchronized boolean removeAppType(Integer appType) {
376            return mAppTypes.remove(appType);
377        }
378
379        @Override
380        public String toString() {
381            synchronized (this) {
382                return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
383                        + ",appTypes=" + mAppTypes + "}";
384            }
385        }
386    }
387
388    private static class OwnershipClientInfo extends
389            BinderInterfaceContainer.BinderInterface<IAppFocusOwnershipCallback> {
390        private final int mUid;
391        private final int mPid;
392        private final Set<Integer> mOwnedAppTypes = new HashSet<>();
393
394        private OwnershipClientInfo(OwnershipClientHolder holder, IAppFocusOwnershipCallback binder,
395                int uid, int pid) {
396            super(holder, binder);
397            this.mUid = uid;
398            this.mPid = pid;
399        }
400
401        private synchronized Set<Integer> getOwnedAppTypes() {
402            if (DBG_EVENT) {
403                Log.i(CarLog.TAG_APP_FOCUS, "getOwnedAppTypes " + mOwnedAppTypes);
404            }
405            return mOwnedAppTypes;
406        }
407
408        private synchronized boolean addOwnedAppType(Integer appType) {
409            if (DBG_EVENT) {
410                Log.i(CarLog.TAG_APP_FOCUS, "addOwnedAppType " + appType);
411            }
412            return mOwnedAppTypes.add(appType);
413        }
414
415        private synchronized boolean removeOwnedAppType(Integer appType) {
416            if (DBG_EVENT) {
417                Log.i(CarLog.TAG_APP_FOCUS, "removeOwnedAppType " + appType);
418            }
419            return mOwnedAppTypes.remove(appType);
420        }
421
422        int getUid() {
423            return mUid;
424        }
425
426        int getPid() {
427            return mPid;
428        }
429
430        @Override
431        public String toString() {
432            synchronized (this) {
433                return "ClientInfo{mUid=" + mUid + ",mPid=" + mPid
434                        + ",owned=" + mOwnedAppTypes + "}";
435            }
436        }
437    }
438
439    private class DispatchHandler extends Handler {
440        private static final int MSG_DISPATCH_OWNERSHIP_LOSS = 0;
441        private static final int MSG_DISPATCH_OWNERSHIP_GRANT = 1;
442        private static final int MSG_DISPATCH_FOCUS_CHANGE = 2;
443
444        private DispatchHandler(Looper looper) {
445            super(looper);
446        }
447
448        private void requestAppFocusOwnershipLossDispatch(IAppFocusOwnershipCallback callback,
449                int appType) {
450            Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_LOSS, appType, 0, callback);
451            sendMessage(msg);
452        }
453
454        private void requestAppFocusOwnershipGrantDispatch(IAppFocusOwnershipCallback callback,
455                int appType) {
456            Message msg = obtainMessage(MSG_DISPATCH_OWNERSHIP_GRANT, appType, 0, callback);
457            sendMessage(msg);
458        }
459
460        private void requestAppFocusChangeDispatch(IAppFocusListener listener, int appType,
461                boolean active) {
462            Message msg = obtainMessage(MSG_DISPATCH_FOCUS_CHANGE, appType, active ? 1 : 0,
463                    listener);
464            sendMessage(msg);
465        }
466
467        @Override
468        public void handleMessage(Message msg) {
469            switch (msg.what) {
470                case MSG_DISPATCH_OWNERSHIP_LOSS:
471                    dispatchAppFocusOwnershipLoss((IAppFocusOwnershipCallback) msg.obj, msg.arg1);
472                    break;
473                case MSG_DISPATCH_OWNERSHIP_GRANT:
474                    dispatchAppFocusOwnershipGrant((IAppFocusOwnershipCallback) msg.obj, msg.arg1);
475                    break;
476                case MSG_DISPATCH_FOCUS_CHANGE:
477                    dispatchAppFocusChange((IAppFocusListener) msg.obj, msg.arg1, msg.arg2 == 1);
478                    break;
479                default:
480                    Log.e(CarLog.TAG_APP_FOCUS, "Can't dispatch message: " + msg);
481            }
482        }
483    }
484
485    private static int[] toIntArray(Set<Integer> intSet) {
486        int[] intArr = new int[intSet.size()];
487        int index = 0;
488        for (Integer value : intSet) {
489            intArr[index++] = value;
490        }
491        return intArr;
492    }
493}
494