| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.car.cluster; |
| |
| import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER; |
| |
| import android.annotation.Nullable; |
| import android.annotation.SystemApi; |
| import android.app.ActivityOptions; |
| import android.car.CarAppFocusManager; |
| import android.car.cluster.IInstrumentClusterManagerCallback; |
| import android.car.cluster.IInstrumentClusterManagerService; |
| import android.car.cluster.renderer.IInstrumentCluster; |
| import android.car.cluster.renderer.IInstrumentClusterHelper; |
| import android.car.cluster.renderer.IInstrumentClusterNavigation; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| |
| import com.android.car.AppFocusService; |
| import com.android.car.AppFocusService.FocusOwnershipCallback; |
| import com.android.car.CarInputService; |
| import com.android.car.CarInputService.KeyEventListener; |
| import com.android.car.CarLocalServices; |
| import com.android.car.CarLog; |
| import com.android.car.CarServiceBase; |
| import com.android.car.R; |
| import com.android.car.am.FixedActivityService; |
| import com.android.car.user.CarUserService; |
| import com.android.internal.annotations.GuardedBy; |
| |
| import java.io.PrintWriter; |
| import java.util.Objects; |
| |
| /** |
| * Service responsible for interaction with car's instrument cluster. |
| * |
| * @hide |
| */ |
| @SystemApi |
| public class InstrumentClusterService implements CarServiceBase, FocusOwnershipCallback, |
| KeyEventListener { |
| private static final String TAG = CarLog.TAG_CLUSTER; |
| private static final ContextOwner NO_OWNER = new ContextOwner(0, 0); |
| |
| private final Context mContext; |
| private final AppFocusService mAppFocusService; |
| private final CarInputService mCarInputService; |
| /** |
| * TODO: (b/121277787) Remove this on master. |
| * @deprecated CarInstrumentClusterManager is being deprecated. |
| */ |
| @Deprecated |
| private final ClusterManagerService mClusterManagerService = new ClusterManagerService(); |
| private final Object mLock = new Object(); |
| @GuardedBy("mLock") |
| private ContextOwner mNavContextOwner = NO_OWNER; |
| @GuardedBy("mLock") |
| private IInstrumentCluster mRendererService; |
| // If renderer service crashed / stopped and this class fails to rebind with it immediately, |
| // we should wait some time before next attempt. This may happen during APK update for example. |
| @GuardedBy("mLock") |
| private DeferredRebinder mDeferredRebinder; |
| // Whether {@link android.car.cluster.renderer.InstrumentClusterRendererService} is bound |
| // (although not necessarily connected) |
| @GuardedBy("mLock") |
| private boolean mRendererBound = false; |
| |
| /** |
| * Connection to {@link android.car.cluster.renderer.InstrumentClusterRendererService} |
| */ |
| private final ServiceConnection mRendererServiceConnection = new ServiceConnection() { |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder binder) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); |
| } |
| IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder); |
| ContextOwner navContextOwner; |
| synchronized (mLock) { |
| mRendererService = service; |
| navContextOwner = mNavContextOwner; |
| } |
| if (navContextOwner != null && service != null) { |
| notifyNavContextOwnerChanged(service, navContextOwner); |
| } |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "onServiceDisconnected, name: " + name); |
| } |
| mContext.unbindService(this); |
| DeferredRebinder rebinder; |
| synchronized (mLock) { |
| mRendererBound = false; |
| mRendererService = null; |
| if (mDeferredRebinder == null) { |
| mDeferredRebinder = new DeferredRebinder(); |
| } |
| rebinder = mDeferredRebinder; |
| } |
| rebinder.rebind(); |
| } |
| }; |
| |
| private final IInstrumentClusterHelper mInstrumentClusterHelper = |
| new IInstrumentClusterHelper.Stub() { |
| @Override |
| public boolean startFixedActivityModeForDisplayAndUser(Intent intent, |
| Bundle activityOptionsBundle, int userId) { |
| Binder.clearCallingIdentity(); |
| ActivityOptions options = new ActivityOptions(activityOptionsBundle); |
| FixedActivityService service = CarLocalServices.getService( |
| FixedActivityService.class); |
| return service.startFixedActivityModeForDisplayAndUser(intent, options, |
| options.getLaunchDisplayId(), userId); |
| } |
| |
| @Override |
| public void stopFixedActivityMode(int displayId) { |
| Binder.clearCallingIdentity(); |
| FixedActivityService service = CarLocalServices.getService( |
| FixedActivityService.class); |
| service.stopFixedActivityMode(displayId); |
| } |
| }; |
| |
| public InstrumentClusterService(Context context, AppFocusService appFocusService, |
| CarInputService carInputService) { |
| mContext = context; |
| mAppFocusService = appFocusService; |
| mCarInputService = carInputService; |
| } |
| |
| @Override |
| public void init() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "init"); |
| } |
| |
| mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */); |
| mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */); |
| // TODO(b/124246323) Start earlier once data storage for cluster is clarified |
| // for early boot. |
| CarLocalServices.getService(CarUserService.class).runOnUser0Unlock(() -> { |
| mRendererBound = bindInstrumentClusterRendererService(); |
| }); |
| } |
| |
| @Override |
| public void release() { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "release"); |
| } |
| |
| mAppFocusService.unregisterContextOwnerChangedCallback(this); |
| if (mRendererBound) { |
| mContext.unbindService(mRendererServiceConnection); |
| mRendererBound = false; |
| } |
| } |
| |
| @Override |
| public void dump(PrintWriter writer) { |
| writer.println("**" + getClass().getSimpleName() + "**"); |
| writer.println("bound with renderer: " + mRendererBound); |
| writer.println("renderer service: " + mRendererService); |
| writer.println("context owner: " + mNavContextOwner); |
| } |
| |
| @Override |
| public void onFocusAcquired(int appType, int uid, int pid) { |
| changeNavContextOwner(appType, uid, pid, true); |
| } |
| |
| @Override |
| public void onFocusAbandoned(int appType, int uid, int pid) { |
| changeNavContextOwner(appType, uid, pid, false); |
| } |
| |
| private void changeNavContextOwner(int appType, int uid, int pid, boolean acquire) { |
| if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { |
| return; |
| } |
| |
| IInstrumentCluster service; |
| ContextOwner requester = new ContextOwner(uid, pid); |
| ContextOwner newOwner = acquire ? requester : NO_OWNER; |
| synchronized (mLock) { |
| if ((acquire && Objects.equals(mNavContextOwner, requester)) |
| || (!acquire && !Objects.equals(mNavContextOwner, requester))) { |
| // Nothing to do here. Either the same owner is acquiring twice, or someone is |
| // abandoning a focus they didn't have. |
| Log.w(TAG, "Invalid nav context owner change (acquiring: " + acquire |
| + "), current owner: [" + mNavContextOwner |
| + "], requester: [" + requester + "]"); |
| return; |
| } |
| |
| mNavContextOwner = newOwner; |
| service = mRendererService; |
| } |
| |
| if (service != null) { |
| notifyNavContextOwnerChanged(service, newOwner); |
| } |
| } |
| |
| private static void notifyNavContextOwnerChanged(IInstrumentCluster service, |
| ContextOwner owner) { |
| try { |
| service.setNavigationContextOwner(owner.uid, owner.pid); |
| } catch (RemoteException e) { |
| Log.e(TAG, "Failed to call setNavigationContextOwner", e); |
| } |
| } |
| |
| private boolean bindInstrumentClusterRendererService() { |
| String rendererService = mContext.getString(R.string.instrumentClusterRendererService); |
| if (TextUtils.isEmpty(rendererService)) { |
| Log.i(TAG, "Instrument cluster renderer was not configured"); |
| return false; |
| } |
| |
| Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService); |
| |
| Intent intent = new Intent(); |
| intent.setComponent(ComponentName.unflattenFromString(rendererService)); |
| // Litle bit inefficiency here as Intent.getIBinderExtra() is a hidden API. |
| Bundle bundle = new Bundle(); |
| bundle.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, |
| mInstrumentClusterHelper.asBinder()); |
| intent.putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, bundle); |
| return mContext.bindServiceAsUser(intent, mRendererServiceConnection, |
| Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM); |
| } |
| |
| @Nullable |
| public IInstrumentClusterNavigation getNavigationService() { |
| try { |
| IInstrumentCluster service = getInstrumentClusterRendererService(); |
| return service == null ? null : service.getNavigationService(); |
| } catch (RemoteException e) { |
| Log.e(TAG, "getNavigationServiceBinder" , e); |
| return null; |
| } |
| } |
| |
| /** |
| * @deprecated {@link android.car.cluster.CarInstrumentClusterManager} is now deprecated. |
| */ |
| @Deprecated |
| public IInstrumentClusterManagerService.Stub getManagerService() { |
| return mClusterManagerService; |
| } |
| |
| @Override |
| public void onKeyEvent(KeyEvent event) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event); |
| } |
| |
| IInstrumentCluster service = getInstrumentClusterRendererService(); |
| if (service != null) { |
| try { |
| service.onKeyEvent(event); |
| } catch (RemoteException e) { |
| Log.e(TAG, "onKeyEvent", e); |
| } |
| } |
| } |
| |
| private IInstrumentCluster getInstrumentClusterRendererService() { |
| IInstrumentCluster service; |
| synchronized (mLock) { |
| service = mRendererService; |
| } |
| return service; |
| } |
| |
| private static class ContextOwner { |
| final int uid; |
| final int pid; |
| |
| ContextOwner(int uid, int pid) { |
| this.uid = uid; |
| this.pid = pid; |
| } |
| |
| @Override |
| public String toString() { |
| return "uid: " + uid + ", pid: " + pid; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| ContextOwner that = (ContextOwner) o; |
| return uid == that.uid && pid == that.pid; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(uid, pid); |
| } |
| } |
| |
| /** |
| * TODO: (b/121277787) Remove on master |
| * @deprecated CarClusterManager is being deprecated. |
| */ |
| @Deprecated |
| private class ClusterManagerService extends IInstrumentClusterManagerService.Stub { |
| @Override |
| public void startClusterActivity(Intent intent) throws RemoteException { |
| // No op. |
| } |
| |
| @Override |
| public void registerCallback(IInstrumentClusterManagerCallback callback) |
| throws RemoteException { |
| // No op. |
| } |
| |
| @Override |
| public void unregisterCallback(IInstrumentClusterManagerCallback callback) |
| throws RemoteException { |
| // No op. |
| } |
| } |
| |
| private class DeferredRebinder extends Handler { |
| private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L; |
| private static final int NUMBER_OF_ATTEMPTS = 10; |
| |
| public void rebind() { |
| mRendererBound = bindInstrumentClusterRendererService(); |
| |
| if (!mRendererBound) { |
| removeMessages(0); |
| sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0), |
| NEXT_REBIND_ATTEMPT_DELAY_MS); |
| } |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| mRendererBound = bindInstrumentClusterRendererService(); |
| |
| if (mRendererBound) { |
| Log.w(TAG, "Failed to bound to render service, next attempt in " |
| + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms."); |
| |
| int attempts = msg.arg1; |
| if (--attempts >= 0) { |
| sendMessageDelayed(obtainMessage(0, attempts, 0), NEXT_REBIND_ATTEMPT_DELAY_MS); |
| } else { |
| Log.wtf(TAG, "Failed to rebind with cluster rendering service"); |
| } |
| } |
| } |
| } |
| } |