1 /*
2  * Copyright (C) 2016 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 package com.android.car.cluster;
17 
18 import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER;
19 
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.app.ActivityOptions;
23 import android.car.CarAppFocusManager;
24 import android.car.cluster.IInstrumentClusterManagerCallback;
25 import android.car.cluster.IInstrumentClusterManagerService;
26 import android.car.cluster.renderer.IInstrumentCluster;
27 import android.car.cluster.renderer.IInstrumentClusterHelper;
28 import android.car.cluster.renderer.IInstrumentClusterNavigation;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.os.Binder;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.IBinder;
37 import android.os.Message;
38 import android.os.RemoteException;
39 import android.os.UserHandle;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.view.KeyEvent;
43 
44 import com.android.car.AppFocusService;
45 import com.android.car.AppFocusService.FocusOwnershipCallback;
46 import com.android.car.CarInputService;
47 import com.android.car.CarInputService.KeyEventListener;
48 import com.android.car.CarLocalServices;
49 import com.android.car.CarLog;
50 import com.android.car.CarServiceBase;
51 import com.android.car.R;
52 import com.android.car.am.FixedActivityService;
53 import com.android.car.user.CarUserService;
54 import com.android.internal.annotations.GuardedBy;
55 
56 import java.io.PrintWriter;
57 import java.util.Objects;
58 
59 /**
60  * Service responsible for interaction with car's instrument cluster.
61  *
62  * @hide
63  */
64 @SystemApi
65 public class InstrumentClusterService implements CarServiceBase, FocusOwnershipCallback,
66         KeyEventListener {
67     private static final String TAG = CarLog.TAG_CLUSTER;
68     private static final ContextOwner NO_OWNER = new ContextOwner(0, 0);
69 
70     private final Context mContext;
71     private final AppFocusService mAppFocusService;
72     private final CarInputService mCarInputService;
73     /**
74      * TODO: (b/121277787) Remove this on master.
75      * @deprecated CarInstrumentClusterManager is being deprecated.
76      */
77     @Deprecated
78     private final ClusterManagerService mClusterManagerService = new ClusterManagerService();
79     private final Object mLock = new Object();
80     @GuardedBy("mLock")
81     private ContextOwner mNavContextOwner = NO_OWNER;
82     @GuardedBy("mLock")
83     private IInstrumentCluster mRendererService;
84     // If renderer service crashed / stopped and this class fails to rebind with it immediately,
85     // we should wait some time before next attempt. This may happen during APK update for example.
86     @GuardedBy("mLock")
87     private DeferredRebinder mDeferredRebinder;
88     // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound
89     // (although not necessarily connected)
90     @GuardedBy("mLock")
91     private boolean mRendererBound = false;
92 
93     /**
94      * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService}
95      */
96     private final ServiceConnection mRendererServiceConnection = new ServiceConnection() {
97         @Override
98         public void onServiceConnected(ComponentName name, IBinder binder) {
99             if (Log.isLoggable(TAG, Log.DEBUG)) {
100                 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
101             }
102             IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
103             ContextOwner navContextOwner;
104             synchronized (mLock) {
105                 mRendererService = service;
106                 navContextOwner = mNavContextOwner;
107             }
108             if (navContextOwner != null && service != null) {
109                 notifyNavContextOwnerChanged(service, navContextOwner);
110             }
111         }
112 
113         @Override
114         public void onServiceDisconnected(ComponentName name) {
115             if (Log.isLoggable(TAG, Log.DEBUG)) {
116                 Log.d(TAG, "onServiceDisconnected, name: " + name);
117             }
118             mContext.unbindService(this);
119             DeferredRebinder rebinder;
120             synchronized (mLock) {
121                 mRendererBound = false;
122                 mRendererService = null;
123                 if (mDeferredRebinder == null) {
124                     mDeferredRebinder = new DeferredRebinder();
125                 }
126                 rebinder = mDeferredRebinder;
127             }
128             rebinder.rebind();
129         }
130     };
131 
132     private final IInstrumentClusterHelper mInstrumentClusterHelper =
133             new IInstrumentClusterHelper.Stub() {
134                 @Override
135                 public boolean startFixedActivityModeForDisplayAndUser(Intent intent,
136                         Bundle activityOptionsBundle, int userId) {
137                     Binder.clearCallingIdentity();
138                     ActivityOptions options = new ActivityOptions(activityOptionsBundle);
139                     FixedActivityService service = CarLocalServices.getService(
140                             FixedActivityService.class);
141                     return service.startFixedActivityModeForDisplayAndUser(intent, options,
142                             options.getLaunchDisplayId(), userId);
143                 }
144 
145                 @Override
146                 public void stopFixedActivityMode(int displayId) {
147                     Binder.clearCallingIdentity();
148                     FixedActivityService service = CarLocalServices.getService(
149                             FixedActivityService.class);
150                     service.stopFixedActivityMode(displayId);
151                 }
152             };
153 
InstrumentClusterService(Context context, AppFocusService appFocusService, CarInputService carInputService)154     public InstrumentClusterService(Context context, AppFocusService appFocusService,
155             CarInputService carInputService) {
156         mContext = context;
157         mAppFocusService = appFocusService;
158         mCarInputService = carInputService;
159     }
160 
161     @Override
init()162     public void init() {
163         if (Log.isLoggable(TAG, Log.DEBUG)) {
164             Log.d(TAG, "init");
165         }
166 
167         mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */);
168         mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */);
169         // TODO(b/124246323) Start earlier once data storage for cluster is clarified
170         //  for early boot.
171         CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> {
172             mRendererBound = bindInstrumentClusterRendererService();
173         });
174     }
175 
176     @Override
release()177     public void release() {
178         if (Log.isLoggable(TAG, Log.DEBUG)) {
179             Log.d(TAG, "release");
180         }
181 
182         mAppFocusService.unregisterContextOwnerChangedCallback(this);
183         if (mRendererBound) {
184             mContext.unbindService(mRendererServiceConnection);
185             mRendererBound = false;
186         }
187     }
188 
189     @Override
dump(PrintWriter writer)190     public void dump(PrintWriter writer) {
191         writer.println("**" + getClass().getSimpleName() + "**");
192         writer.println("bound with renderer: " + mRendererBound);
193         writer.println("renderer service: " + mRendererService);
194         writer.println("context owner: " + mNavContextOwner);
195     }
196 
197     @Override
onFocusAcquired(int appType, int uid, int pid)198     public void onFocusAcquired(int appType, int uid, int pid) {
199         changeNavContextOwner(appType, uid, pid, true);
200     }
201 
202     @Override
onFocusAbandoned(int appType, int uid, int pid)203     public void onFocusAbandoned(int appType, int uid, int pid) {
204         changeNavContextOwner(appType, uid, pid, false);
205     }
206 
changeNavContextOwner(int appType, int uid, int pid, boolean acquire)207     private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) {
208         if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
209             return;
210         }
211 
212         IInstrumentCluster service;
213         ContextOwner requester = new ContextOwner(uid, pid);
214         ContextOwner newOwner = acquire ? requester : NO_OWNER;
215         synchronized (mLock) {
216             if ((acquire && Objects.equals(mNavContextOwner, requester))
217                     || (!acquire && !Objects.equals(mNavContextOwner, requester))) {
218                 // Nothing to do here. Either the same owner is acquiring twice, or someone is
219                 // abandoning a focus they didn't have.
220                 Log.w(TAG, "Invalid nav context owner change (acquiring: " + acquire
221                         + "), current owner: [" + mNavContextOwner
222                         + "], requester: [" + requester + "]");
223                 return;
224             }
225 
226             mNavContextOwner = newOwner;
227             service = mRendererService;
228         }
229 
230         if (service != null) {
231             notifyNavContextOwnerChanged(service, newOwner);
232         }
233     }
234 
notifyNavContextOwnerChanged(IInstrumentCluster service, ContextOwner owner)235     private static void notifyNavContextOwnerChanged(IInstrumentCluster service,
236             ContextOwner owner) {
237         try {
238             service.setNavigationContextOwner(owner.uid, owner.pid);
239         } catch (RemoteException e) {
240             Log.e(TAG, "Failed to call setNavigationContextOwner", e);
241         }
242     }
243 
bindInstrumentClusterRendererService()244     private boolean bindInstrumentClusterRendererService() {
245         String rendererService = mContext.getString(R.string.instrumentClusterRendererService);
246         if (TextUtils.isEmpty(rendererService)) {
247             Log.i(TAG, "Instrument cluster renderer was not configured");
248             return false;
249         }
250 
251         Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService);
252 
253         Intent intent = new Intent();
254         intent.setComponent(ComponentName.unflattenFromString(rendererService));
255         // Litle bit inefficiency here as Intent.getIBinderExtra() is a hidden API.
256         Bundle bundle = new Bundle();
257         bundle.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER,
258                 mInstrumentClusterHelper.asBinder());
259         intent.putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, bundle);
260         return mContext.bindServiceAsUser(intent, mRendererServiceConnection,
261                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM);
262     }
263 
264     @Nullable
getNavigationService()265     public IInstrumentClusterNavigation getNavigationService() {
266         try {
267             IInstrumentCluster service = getInstrumentClusterRendererService();
268             return service == null ? null : service.getNavigationService();
269         } catch (RemoteException e) {
270             Log.e(TAG, "getNavigationServiceBinder" , e);
271             return null;
272         }
273     }
274 
275     /**
276      * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated.
277      */
278     @Deprecated
getManagerService()279     public IInstrumentClusterManagerService.Stub getManagerService() {
280         return mClusterManagerService;
281     }
282 
283     @Override
onKeyEvent(KeyEvent event)284     public void onKeyEvent(KeyEvent event) {
285         if (Log.isLoggable(TAG, Log.DEBUG)) {
286             Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event);
287         }
288 
289         IInstrumentCluster service = getInstrumentClusterRendererService();
290         if (service != null) {
291             try {
292                 service.onKeyEvent(event);
293             } catch (RemoteException e) {
294                 Log.e(TAG, "onKeyEvent", e);
295             }
296         }
297     }
298 
getInstrumentClusterRendererService()299     private IInstrumentCluster getInstrumentClusterRendererService() {
300         IInstrumentCluster service;
301         synchronized (mLock) {
302             service = mRendererService;
303         }
304         return service;
305     }
306 
307     private static class ContextOwner {
308         final int uid;
309         final int pid;
310 
ContextOwner(int uid, int pid)311         ContextOwner(int uid, int pid) {
312             this.uid = uid;
313             this.pid = pid;
314         }
315 
316         @Override
toString()317         public String toString() {
318             return "uid: " + uid + ", pid: " + pid;
319         }
320 
321         @Override
equals(Object o)322         public boolean equals(Object o) {
323             if (this == o) return true;
324             if (o == null || getClass() != o.getClass()) return false;
325             ContextOwner that = (ContextOwner) o;
326             return uid == that.uid && pid == that.pid;
327         }
328 
329         @Override
hashCode()330         public int hashCode() {
331             return Objects.hash(uid, pid);
332         }
333     }
334 
335     /**
336      * TODO: (b/121277787) Remove on master
337      * @deprecated CarClusterManager is being deprecated.
338      */
339     @Deprecated
340     private class ClusterManagerService extends IInstrumentClusterManagerService.Stub {
341         @Override
startClusterActivity(Intent intent)342         public void startClusterActivity(Intent intent) throws RemoteException {
343             // No op.
344         }
345 
346         @Override
registerCallback(IInstrumentClusterManagerCallback callback)347         public void registerCallback(IInstrumentClusterManagerCallback callback)
348                 throws RemoteException {
349             // No op.
350         }
351 
352         @Override
unregisterCallback(IInstrumentClusterManagerCallback callback)353         public void unregisterCallback(IInstrumentClusterManagerCallback callback)
354                 throws RemoteException {
355             // No op.
356         }
357     }
358 
359     private class DeferredRebinder extends Handler {
360         private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L;
361         private static final int NUMBER_OF_ATTEMPTS = 10;
362 
rebind()363         public void rebind() {
364             mRendererBound = bindInstrumentClusterRendererService();
365 
366             if (!mRendererBound) {
367                 removeMessages(0);
368                 sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0),
369                         NEXT_REBIND_ATTEMPT_DELAY_MS);
370             }
371         }
372 
373         @Override
handleMessage(Message msg)374         public void handleMessage(Message msg) {
375             mRendererBound = bindInstrumentClusterRendererService();
376 
377             if (mRendererBound) {
378                 Log.w(TAG, "Failed to bound to render service, next attempt in "
379                         + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms.");
380 
381                 int attempts = msg.arg1;
382                 if (--attempts >= 0) {
383                     sendMessageDelayed(obtainMessage(0, attempts, 0), NEXT_REBIND_ATTEMPT_DELAY_MS);
384                 } else {
385                     Log.wtf(TAG, "Failed to rebind with cluster rendering service");
386                 }
387             }
388         }
389     }
390 }
391