blob: cc0a6b7ef6ae8917a533bdde055ba03bbbdee55d [file] [log] [blame]
/*
* 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");
}
}
}
}
}