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 */
16package com.android.car.cluster;
17
18import android.annotation.Nullable;
19import android.annotation.SystemApi;
20import android.car.CarAppFocusManager;
21import android.car.cluster.renderer.IInstrumentCluster;
22import android.car.cluster.renderer.IInstrumentClusterNavigation;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.ServiceConnection;
27import android.os.IBinder;
28import android.os.RemoteException;
29import android.text.TextUtils;
30import android.util.Log;
31import android.view.KeyEvent;
32
33import com.android.car.AppFocusService;
34import com.android.car.AppFocusService.FocusOwnershipCallback;
35import com.android.car.CarInputService;
36import com.android.car.CarInputService.KeyEventListener;
37import com.android.car.CarLog;
38import com.android.car.CarServiceBase;
39import com.android.car.R;
40import com.android.internal.annotations.GuardedBy;
41
42import java.io.PrintWriter;
43
44/**
45 * Service responsible for interaction with car's instrument cluster.
46 *
47 * @hide
48 */
49@SystemApi
50public class InstrumentClusterService implements CarServiceBase,
51        FocusOwnershipCallback, KeyEventListener {
52
53    private static final String TAG = CarLog.TAG_CLUSTER;
54    private static final Boolean DBG = false;
55
56    private final Context mContext;
57    private final AppFocusService mAppFocusService;
58    private final CarInputService mCarInputService;
59    private final Object mSync = new Object();
60
61    @GuardedBy("mSync")
62    private ContextOwner mNavContextOwner;
63    @GuardedBy("mSync")
64    private IInstrumentCluster mRendererService;
65
66    private boolean mRendererBound = false;
67
68    private final ServiceConnection mRendererServiceConnection = new ServiceConnection() {
69        @Override
70        public void onServiceConnected(ComponentName name, IBinder binder) {
71            if (DBG) {
72                Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
73            }
74            IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder);
75            ContextOwner navContextOwner;
76            synchronized (mSync) {
77                mRendererService = service;
78                navContextOwner = mNavContextOwner;
79            }
80            if (navContextOwner !=  null && service != null) {
81                notifyNavContextOwnerChanged(service, navContextOwner.uid, navContextOwner.pid);
82            }
83        }
84
85        @Override
86        public void onServiceDisconnected(ComponentName name) {
87            Log.d(TAG, "onServiceDisconnected, name: " + name);
88            mRendererService = null;
89            // Try to rebind with instrument cluster.
90            mRendererBound = bindInstrumentClusterRendererService();
91        }
92    };
93
94    public InstrumentClusterService(Context context, AppFocusService appFocusService,
95            CarInputService carInputService) {
96        mContext = context;
97        mAppFocusService = appFocusService;
98        mCarInputService = carInputService;
99    }
100
101    @Override
102    public void init() {
103        if (DBG) {
104            Log.d(TAG, "init");
105        }
106
107        mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */);
108        mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */);
109        mRendererBound = bindInstrumentClusterRendererService();
110    }
111
112    @Override
113    public void release() {
114        if (DBG) {
115            Log.d(TAG, "release");
116        }
117
118        mAppFocusService.unregisterContextOwnerChangedCallback(this);
119        if (mRendererBound) {
120            mContext.unbindService(mRendererServiceConnection);
121            mRendererBound = false;
122        }
123    }
124
125    @Override
126    public void dump(PrintWriter writer) {
127        writer.println("**" + getClass().getSimpleName() + "**");
128        writer.println("bound with renderer: " + mRendererBound);
129        writer.println("renderer service: " + mRendererService);
130    }
131
132    @Override
133    public void onFocusAcquired(int appType, int uid, int pid) {
134        if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
135            return;
136        }
137
138        IInstrumentCluster service;
139        synchronized (mSync) {
140            mNavContextOwner = new ContextOwner(uid, pid);
141            service = mRendererService;
142        }
143
144        if (service != null) {
145            notifyNavContextOwnerChanged(service, uid, pid);
146        }
147    }
148
149    @Override
150    public void onFocusAbandoned(int appType, int uid, int pid) {
151        if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) {
152            return;
153        }
154
155        IInstrumentCluster service;
156        synchronized (mSync) {
157            if (mNavContextOwner == null
158                    || mNavContextOwner.uid != uid
159                    || mNavContextOwner.pid != pid) {
160                return;  // Nothing to do here, no active focus or not owned by this client.
161            }
162
163            mNavContextOwner = null;
164            service = mRendererService;
165        }
166
167        if (service != null) {
168            notifyNavContextOwnerChanged(service, 0, 0);
169        }
170    }
171
172    private static void notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid) {
173        try {
174            service.setNavigationContextOwner(uid, pid);
175        } catch (RemoteException e) {
176            Log.e(TAG, "Failed to call setNavigationContextOwner", e);
177        }
178    }
179
180    private boolean bindInstrumentClusterRendererService() {
181        String rendererService = mContext.getString(R.string.instrumentClusterRendererService);
182        if (TextUtils.isEmpty(rendererService)) {
183            Log.i(TAG, "Instrument cluster renderer was not configured");
184            return false;
185        }
186
187        Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService);
188
189        Intent intent = new Intent();
190        intent.setComponent(ComponentName.unflattenFromString(rendererService));
191        return mContext.bindService(intent, mRendererServiceConnection, Context.BIND_AUTO_CREATE);
192    }
193
194    @Nullable
195    public IInstrumentClusterNavigation getNavigationService() {
196        IInstrumentCluster service;
197        synchronized (mSync) {
198            service = mRendererService;
199        }
200
201        try {
202            return service == null ? null : service.getNavigationService();
203        } catch (RemoteException e) {
204            Log.e(TAG, "getNavigationServiceBinder" , e);
205            return null;
206        }
207    }
208
209    @Override
210    public boolean onKeyEvent(KeyEvent event) {
211        if (DBG) {
212            Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event);
213        }
214
215        IInstrumentCluster service;
216        synchronized (mSync) {
217            service = mRendererService;
218        }
219
220        if (service != null) {
221            try {
222                service.onKeyEvent(event);
223            } catch (RemoteException e) {
224                Log.e(TAG, "onKeyEvent", e);
225            }
226        }
227        return true;
228    }
229
230    private static class ContextOwner {
231        final int uid;
232        final int pid;
233
234        ContextOwner(int uid, int pid) {
235            this.uid = uid;
236            this.pid = pid;
237        }
238    }
239}
240