Home | History | Annotate | Download | only in car
      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  */
     16 
     17 package android.car;
     18 
     19 import android.annotation.IntDef;
     20 import android.os.Handler;
     21 import android.os.IBinder;
     22 import android.os.RemoteException;
     23 
     24 import java.lang.annotation.Retention;
     25 import java.lang.annotation.RetentionPolicy;
     26 import java.lang.ref.WeakReference;
     27 import java.util.HashMap;
     28 import java.util.HashSet;
     29 import java.util.Map;
     30 import java.util.Set;
     31 
     32 /**
     33  * CarAppFocusManager allows applications to set and listen for the current application focus
     34  * like active navigation or active voice command. Usually only one instance of such application
     35  * should run in the system, and other app setting the flag for the matching app should
     36  * lead into other app to stop.
     37  */
     38 public final class CarAppFocusManager implements CarManagerBase {
     39     /**
     40      * Listener to get notification for app getting information on application type status changes.
     41      */
     42     public interface OnAppFocusChangedListener {
     43         /**
     44          * Application focus has changed. Note that {@link CarAppFocusManager} instance
     45          * causing the change will not get this notification.
     46          * @param appType
     47          * @param active
     48          */
     49         void onAppFocusChanged(@AppFocusType int appType, boolean active);
     50     }
     51 
     52     /**
     53      * Listener to get notification for app getting information on app type ownership loss.
     54      */
     55     public interface OnAppFocusOwnershipCallback {
     56         /**
     57          * Lost ownership for the focus, which happens when other app has set the focus.
     58          * The app losing focus should stop the action associated with the focus.
     59          * For example, navigation app currently running active navigation should stop navigation
     60          * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}.
     61          * @param appType
     62          */
     63         void onAppFocusOwnershipLost(@AppFocusType int appType);
     64 
     65         /**
     66          * Granted ownership for the focus, which happens when app has requested the focus.
     67          * The app getting focus can start the action associated with the focus.
     68          * For example, navigation app can start navigation
     69          * upon getting this for {@link CarAppFocusManager#APP_FOCUS_TYPE_NAVIGATION}.
     70          * @param appType
     71          */
     72         void onAppFocusOwnershipGranted(@AppFocusType int appType);
     73     }
     74 
     75     /**
     76      * Represents navigation focus.
     77      */
     78     public static final int APP_FOCUS_TYPE_NAVIGATION = 1;
     79     /**
     80      * Represents voice command focus.
     81      */
     82     public static final int APP_FOCUS_TYPE_VOICE_COMMAND = 2;
     83     /**
     84      * Update this after adding a new app type.
     85      * @hide
     86      */
     87     public static final int APP_FOCUS_MAX = 2;
     88 
     89     /** @hide */
     90     @IntDef({
     91         APP_FOCUS_TYPE_NAVIGATION,
     92         APP_FOCUS_TYPE_VOICE_COMMAND
     93     })
     94     @Retention(RetentionPolicy.SOURCE)
     95     public @interface AppFocusType {}
     96 
     97     /**
     98      * A failed focus change request.
     99      */
    100     public static final int APP_FOCUS_REQUEST_FAILED = 0;
    101     /**
    102      * A successful focus change request.
    103      */
    104     public static final int APP_FOCUS_REQUEST_SUCCEEDED = 1;
    105 
    106     /** @hide */
    107     @IntDef({
    108         APP_FOCUS_REQUEST_FAILED,
    109         APP_FOCUS_REQUEST_SUCCEEDED
    110     })
    111     @Retention(RetentionPolicy.SOURCE)
    112     public @interface AppFocusRequestResult {}
    113 
    114     private final IAppFocus mService;
    115     private final Handler mHandler;
    116     private final Map<OnAppFocusChangedListener, IAppFocusListenerImpl> mChangeBinders =
    117             new HashMap<>();
    118     private final Map<OnAppFocusOwnershipCallback, IAppFocusOwnershipCallbackImpl>
    119             mOwnershipBinders = new HashMap<>();
    120 
    121     /**
    122      * @hide
    123      */
    124     CarAppFocusManager(IBinder service, Handler handler) {
    125         mService = IAppFocus.Stub.asInterface(service);
    126         mHandler = handler;
    127     }
    128 
    129     /**
    130      * Register listener to monitor app focus change.
    131      * @param listener
    132      * @param appType Application type to get notification for.
    133      * @throws CarNotConnectedException if the connection to the car service has been lost.
    134      */
    135     public void addFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType)
    136             throws CarNotConnectedException {
    137         if (listener == null) {
    138             throw new IllegalArgumentException("null listener");
    139         }
    140         IAppFocusListenerImpl binder;
    141         synchronized (this) {
    142             binder = mChangeBinders.get(listener);
    143             if (binder == null) {
    144                 binder = new IAppFocusListenerImpl(this, listener);
    145                 mChangeBinders.put(listener, binder);
    146             }
    147             binder.addAppType(appType);
    148         }
    149         try {
    150             mService.registerFocusListener(binder, appType);
    151         } catch (RemoteException e) {
    152             throw new CarNotConnectedException(e);
    153         }
    154     }
    155 
    156     /**
    157      * Unregister listener for application type and stop listening focus change events.
    158      * @param listener
    159      * @param appType
    160      * @throws CarNotConnectedException if the connection to the car service has been lost.
    161      */
    162     public void removeFocusListener(OnAppFocusChangedListener listener, @AppFocusType int appType) {
    163         IAppFocusListenerImpl binder;
    164         synchronized (this) {
    165             binder = mChangeBinders.get(listener);
    166             if (binder == null) {
    167                 return;
    168             }
    169         }
    170         try {
    171             mService.unregisterFocusListener(binder, appType);
    172         } catch (RemoteException e) {
    173             //ignore
    174         }
    175         synchronized (this) {
    176             binder.removeAppType(appType);
    177             if (!binder.hasAppTypes()) {
    178                 mChangeBinders.remove(listener);
    179             }
    180 
    181         }
    182     }
    183 
    184     /**
    185      * Unregister listener and stop listening focus change events.
    186      * @param listener
    187      * @throws CarNotConnectedException if the connection to the car service has been lost.
    188      */
    189     public void removeFocusListener(OnAppFocusChangedListener listener) {
    190         IAppFocusListenerImpl binder;
    191         synchronized (this) {
    192             binder = mChangeBinders.remove(listener);
    193             if (binder == null) {
    194                 return;
    195             }
    196         }
    197         try {
    198             for (Integer appType : binder.getAppTypes()) {
    199                 mService.unregisterFocusListener(binder, appType);
    200             }
    201         } catch (RemoteException e) {
    202             //ignore
    203         }
    204     }
    205 
    206     /**
    207      * Returns application types currently active in the system.
    208      * @throws CarNotConnectedException if the connection to the car service has been lost.
    209      * @hide
    210      */
    211     public int[] getActiveAppTypes() throws CarNotConnectedException {
    212         try {
    213             return mService.getActiveAppTypes();
    214         } catch (RemoteException e) {
    215             throw new CarNotConnectedException(e);
    216         }
    217     }
    218 
    219     /**
    220      * Checks if listener is associated with active a focus
    221      * @param callback
    222      * @param appType
    223      * @throws CarNotConnectedException if the connection to the car service has been lost.
    224      */
    225     public boolean isOwningFocus(OnAppFocusOwnershipCallback callback, @AppFocusType int appType)
    226             throws CarNotConnectedException {
    227         IAppFocusOwnershipCallbackImpl binder;
    228         synchronized (this) {
    229             binder = mOwnershipBinders.get(callback);
    230             if (binder == null) {
    231                 return false;
    232             }
    233         }
    234         try {
    235             return mService.isOwningFocus(binder, appType);
    236         } catch (RemoteException e) {
    237             throw new CarNotConnectedException(e);
    238         }
    239     }
    240 
    241     /**
    242      * Requests application focus.
    243      * By requesting this, the application is becoming owner of the focus, and will get
    244      * {@link OnAppFocusOwnershipCallback#onAppFocusOwnershipLost(int)}
    245      * if ownership is given to other app by calling this. Fore-ground app will have higher priority
    246      * and other app cannot set the same focus while owner is in fore-ground.
    247      * @param appType
    248      * @param ownershipCallback
    249      * @return {@link #APP_FOCUS_REQUEST_FAILED} or {@link #APP_FOCUS_REQUEST_SUCCEEDED}
    250      * @throws CarNotConnectedException if the connection to the car service has been lost.
    251      * @throws SecurityException If owner cannot be changed.
    252      */
    253     public @AppFocusRequestResult int requestAppFocus(int appType,
    254             OnAppFocusOwnershipCallback ownershipCallback)
    255                     throws SecurityException, CarNotConnectedException {
    256         if (ownershipCallback == null) {
    257             throw new IllegalArgumentException("null listener");
    258         }
    259         IAppFocusOwnershipCallbackImpl binder;
    260         synchronized (this) {
    261             binder = mOwnershipBinders.get(ownershipCallback);
    262             if (binder == null) {
    263                 binder = new IAppFocusOwnershipCallbackImpl(this, ownershipCallback);
    264                 mOwnershipBinders.put(ownershipCallback, binder);
    265             }
    266             binder.addAppType(appType);
    267         }
    268         try {
    269             return mService.requestAppFocus(binder, appType);
    270         } catch (RemoteException e) {
    271             throw new CarNotConnectedException(e);
    272         }
    273     }
    274 
    275     /**
    276      * Abandon the given focus, i.e. mark it as inactive. This also involves releasing ownership
    277      * for the focus.
    278      * @param ownershipCallback
    279      * @param appType
    280      * @throws CarNotConnectedException if the connection to the car service has been lost.
    281      */
    282     public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback,
    283             @AppFocusType int appType) {
    284         if (ownershipCallback == null) {
    285             throw new IllegalArgumentException("null callback");
    286         }
    287         IAppFocusOwnershipCallbackImpl binder;
    288         synchronized (this) {
    289             binder = mOwnershipBinders.get(ownershipCallback);
    290             if (binder == null) {
    291                 return;
    292             }
    293         }
    294         try {
    295             mService.abandonAppFocus(binder, appType);
    296         } catch (RemoteException e) {
    297             //ignore
    298         }
    299         synchronized (this) {
    300             binder.removeAppType(appType);
    301             if (!binder.hasAppTypes()) {
    302                 mOwnershipBinders.remove(ownershipCallback);
    303             }
    304         }
    305     }
    306 
    307     /**
    308      * Abandon all focuses, i.e. mark them as inactive. This also involves releasing ownership
    309      * for the focus.
    310      * @param ownershipCallback
    311      * @throws CarNotConnectedException if the connection to the car service has been lost.
    312      */
    313     public void abandonAppFocus(OnAppFocusOwnershipCallback ownershipCallback) {
    314         IAppFocusOwnershipCallbackImpl binder;
    315         synchronized (this) {
    316             binder = mOwnershipBinders.remove(ownershipCallback);
    317             if (binder == null) {
    318                 return;
    319             }
    320         }
    321         try {
    322             for (Integer appType : binder.getAppTypes()) {
    323                 mService.abandonAppFocus(binder, appType);
    324             }
    325         } catch (RemoteException e) {
    326             //ignore
    327         }
    328     }
    329 
    330     /** @hide */
    331     @Override
    332     public void onCarDisconnected() {
    333         // nothing to do
    334     }
    335 
    336     private static class IAppFocusListenerImpl extends IAppFocusListener.Stub {
    337 
    338         private final WeakReference<CarAppFocusManager> mManager;
    339         private final WeakReference<OnAppFocusChangedListener> mListener;
    340         private final Set<Integer> mAppTypes = new HashSet<>();
    341 
    342         private IAppFocusListenerImpl(CarAppFocusManager manager,
    343                 OnAppFocusChangedListener listener) {
    344             mManager = new WeakReference<>(manager);
    345             mListener = new WeakReference<>(listener);
    346         }
    347 
    348         public void addAppType(@AppFocusType int appType) {
    349             mAppTypes.add(appType);
    350         }
    351 
    352         public void removeAppType(@AppFocusType int appType) {
    353             mAppTypes.remove(appType);
    354         }
    355 
    356         public Set<Integer> getAppTypes() {
    357             return mAppTypes;
    358         }
    359 
    360         public boolean hasAppTypes() {
    361             return !mAppTypes.isEmpty();
    362         }
    363 
    364         @Override
    365         public void onAppFocusChanged(final @AppFocusType int appType, final boolean active) {
    366             final CarAppFocusManager manager = mManager.get();
    367             final OnAppFocusChangedListener listener = mListener.get();
    368             if (manager == null || listener == null) {
    369                 return;
    370             }
    371             manager.mHandler.post(new Runnable() {
    372                 @Override
    373                 public void run() {
    374                     listener.onAppFocusChanged(appType, active);
    375                 }
    376             });
    377         }
    378     }
    379 
    380     private static class IAppFocusOwnershipCallbackImpl extends IAppFocusOwnershipCallback.Stub {
    381 
    382         private final WeakReference<CarAppFocusManager> mManager;
    383         private final WeakReference<OnAppFocusOwnershipCallback> mCallback;
    384         private final Set<Integer> mAppTypes = new HashSet<>();
    385 
    386         private IAppFocusOwnershipCallbackImpl(CarAppFocusManager manager,
    387                 OnAppFocusOwnershipCallback callback) {
    388             mManager = new WeakReference<>(manager);
    389             mCallback = new WeakReference<>(callback);
    390         }
    391 
    392         public void addAppType(@AppFocusType int appType) {
    393             mAppTypes.add(appType);
    394         }
    395 
    396         public void removeAppType(@AppFocusType int appType) {
    397             mAppTypes.remove(appType);
    398         }
    399 
    400         public Set<Integer> getAppTypes() {
    401             return mAppTypes;
    402         }
    403 
    404         public boolean hasAppTypes() {
    405             return !mAppTypes.isEmpty();
    406         }
    407 
    408         @Override
    409         public void onAppFocusOwnershipLost(final @AppFocusType int appType) {
    410             final CarAppFocusManager manager = mManager.get();
    411             final OnAppFocusOwnershipCallback callback = mCallback.get();
    412             if (manager == null || callback == null) {
    413                 return;
    414             }
    415             manager.mHandler.post(new Runnable() {
    416                 @Override
    417                 public void run() {
    418                     callback.onAppFocusOwnershipLost(appType);
    419                 }
    420             });
    421         }
    422 
    423         @Override
    424         public void onAppFocusOwnershipGranted(final @AppFocusType int appType) {
    425             final CarAppFocusManager manager = mManager.get();
    426             final OnAppFocusOwnershipCallback callback = mCallback.get();
    427             if (manager == null || callback == null) {
    428                 return;
    429             }
    430             manager.mHandler.post(new Runnable() {
    431                 @Override
    432                 public void run() {
    433                     callback.onAppFocusOwnershipGranted(appType);
    434                 }
    435             });
    436         }
    437     }
    438 }
    439