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