blob: 913457f727ec02b29e57c4c85cf075ed33dc6138 [file] [log] [blame]
package com.android.clockwork.healthservices;
import static com.android.clockwork.healthservices.Utils.DEBUG_HAL;
import static com.google.android.clockwork.healthservices.IHealthService.NAME;
import static com.google.android.clockwork.healthservices.IHealthService.Stub;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
import android.os.IBinder;
import android.os.IHwBinder;
import android.os.ServiceManager;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.SystemService;
import com.google.android.clockwork.healthservices.types.AchievedGoal;
import com.google.android.clockwork.healthservices.types.AutoExerciseEvent;
import com.google.android.clockwork.healthservices.types.AutoStartConfig;
import com.google.android.clockwork.healthservices.types.AutoStartCapabilities;
import com.google.android.clockwork.healthservices.types.AutoStartEvent;
import com.google.android.clockwork.healthservices.types.AvailabilityUpdate;
import com.google.android.clockwork.healthservices.types.DataTypeGoal;
import com.google.android.clockwork.healthservices.types.DataTypeOffset;
import com.google.android.clockwork.healthservices.types.DataUpdate;
import com.google.android.clockwork.healthservices.types.ExerciseEvent;
import com.google.android.clockwork.healthservices.types.GolfShotDetectionParams;
import com.google.android.clockwork.healthservices.types.HealthEvent;
import com.google.android.clockwork.healthservices.types.HrAlertParams;
import com.google.android.clockwork.healthservices.IHealthServiceCallback;
import com.google.android.clockwork.healthservices.types.TrackingConfig;
import java.util.Arrays;
import java.util.ArrayList;
import vendor.google_clockwork.healthservices.V1_0.IHealthServices;
/** A {@link SystemService} that binds to the Wear Health Services application (WHS). */
public class HealthService extends SystemService
implements IBinder.DeathRecipient, HalAdapter.HalListener {
private static final String TAG = "HealthService";
public static final String SERVICE_NAME = NAME;
static final String WHS_PACKAGE = "com.google.android.wearable.healthservices";
@VisibleForTesting static final int BOOT_BIND_DELAY_MILLIS = 4000;
@VisibleForTesting static final int WHS_CRASH_BIND_DELAY_MILLIS = 4000;
@VisibleForTesting static final int PACKAGE_UPDATE_BIND_DELAY_MILLIS = 4000;
@VisibleForTesting static final int USER_UNLOCK_BIND_DELAY_MILLIS = 3000;
/** Interface that handles binding to WHS. */
public static interface BindingAgent {
/** Attempts binding to WHS after {@code delayMillis} ms. */
void bind(long delayMillis);
/** Cancel pending bind attempts. */
void cancelPendingBinds();
}
@VisibleForTesting
final PackageEventsReceiver mWhsPackageEventsReceiver = new PackageEventsReceiver();
private final Context mContext;
private HandlerBindingAgent mWhsBindingAgent;
private ConnectionTracker mWhsConnectionTracker;
private final HalAdapter mHalAdapter;
private final Object mHealthServicesCallbackLock = new Object();
/**
* The ID to use for the next incoming flush request. The SystemService is always expected to be
* running so this should be an increasing count from boot time. It is okay if it resets after a
* reboot scenario or due to reaching MAX_INTEGER since earlier flushes will most likely have been
* completed at that point.
*/
@VisibleForTesting public int nextFlushId = Utils.STARTING_FLUSH_ID;
@GuardedBy("mHealthServicesCallbackLock")
private IHealthServiceCallback mHealthServiceCallback;
@VisibleForTesting
public HealthService(
Context context,
HandlerBindingAgent bindingAgent,
ConnectionTracker connectionTracker,
HalAdapter halAdapter) {
super(context);
mContext = context;
mWhsBindingAgent = bindingAgent;
mWhsConnectionTracker = connectionTracker;
mHalAdapter = halAdapter;
}
public HealthService(Context context) {
super(context);
mContext = context;
mHalAdapter = new HalAdapter(this);
}
@Override // SystemService
public void onStart() {
publishBinderService();
if (mWhsBindingAgent == null) {
mWhsBindingAgent = new HandlerBindingAgent(mContext);
}
if (mWhsConnectionTracker == null) {
mWhsConnectionTracker = new ConnectionTracker(/* deathRecipient= */ this);
}
mWhsBindingAgent.setConnectionTracker(mWhsConnectionTracker);
mWhsConnectionTracker.setBindingAgent(mWhsBindingAgent);
IntentFilter packageActionsFilter = new IntentFilter();
packageActionsFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
packageActionsFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageActionsFilter.addDataScheme("package");
mContext.registerReceiver(mWhsPackageEventsReceiver, packageActionsFilter);
mHalAdapter.registerHalV1_0RegistrationNotification();
mHalAdapter.maybeGetAidlHalService();
}
// Allow the unit tests a way to mask this method due to problems testing ServiceManager.
@VisibleForTesting
void publishBinderService() {
publishBinderService(SERVICE_NAME, new BinderService());
}
@Override // SystemService
public void onUserUnlocked(TargetUser user) {
if (mWhsConnectionTracker.isConnected()) {
Log.w(TAG, "User unlocked the device, but WHS is already connected.");
return;
}
Log.d(TAG, "User unlocked. Will attempt a bind after " + USER_UNLOCK_BIND_DELAY_MILLIS + "ms.");
mWhsBindingAgent.bind(USER_UNLOCK_BIND_DELAY_MILLIS);
}
@Override // SystemService
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
Log.d(TAG, "Boot complete. Will attempt to bind after " + BOOT_BIND_DELAY_MILLIS + "ms.");
mWhsBindingAgent.bind(BOOT_BIND_DELAY_MILLIS);
}
}
@Override // IBinder.DeathRecipient
public void binderDied() {
Log.d(TAG, "WHS died. Will attempt to bind after " + WHS_CRASH_BIND_DELAY_MILLIS + "ms.");
mWhsBindingAgent.bind(WHS_CRASH_BIND_DELAY_MILLIS);
}
@Override // HalAdapter.HalListener
public void onHalConnected() {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback != null) {
try {
mHealthServiceCallback.onHalServiceConnected();
} catch (RemoteException e) {
Log.w(TAG, "Failed to notify WHS of HAL connection:", e);
}
} else {
Log.w(TAG, "No callback registered! Failed to notify WHS of HAL connection");
}
}
}
@Override // HalAdapter.HalListener
public void onHalDied() {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback != null) {
try {
mHealthServiceCallback.onHalServiceDied();
} catch (RemoteException e) {
Log.w(TAG, "Failed to notify WHS of HAL crash:", e);
}
} else {
Log.w(TAG, "No callback registered! Failed to notify WHS of HAL Death");
}
}
}
@Override // HalAdapter.HalListener
public void onAutoExerciseEvent(AutoExerciseEvent autoExerciseEvent) {
if (DEBUG_HAL) {
Log.d(TAG, "onAutoExerciseEvent");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit auto-exercise Event");
return;
}
mHealthServiceCallback.onAutoExerciseEvent(autoExerciseEvent);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit auto-exercise event:", e);
}
}
@Override // HalAdapter.HalListener
public void onExerciseEvent(ExerciseEvent[] exerciseEvents) {
if (DEBUG_HAL) {
Log.d(TAG, "onExerciseEvent");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit exercise Event");
return;
}
mHealthServiceCallback.onExerciseEvent(exerciseEvents);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit exercise event:", e);
}
}
@Override // HalAdapter.HalListener
public void onAvailabilityUpdate(AvailabilityUpdate[] availabilityUpdates) {
if (DEBUG_HAL) {
Log.d(TAG, "onAvailabilityUpdate");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit availability update");
return;
}
mHealthServiceCallback.onAvailabilityUpdate(availabilityUpdates);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit availability update:", e);
}
}
@Override // HalAdapter.HalListener
public void onDataUpdate(DataUpdate[] dataUpdates) {
if (DEBUG_HAL) {
Log.d(TAG, "onDataUpdate");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit data updates");
return;
}
mHealthServiceCallback.onDataUpdate(dataUpdates);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit data updates:", e);
}
}
@Override // HalAdapter.HalListener
public void onFlushCompleted(int flushId) {
if (DEBUG_HAL) {
Log.d(TAG, "onFlushCompleted");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit flush complete signal");
return;
}
mHealthServiceCallback.onFlushCompletedWithId(flushId);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit flush complete signal:", e);
}
}
@Override // HalAdapter.HalListener
public void onGoalAchieved(AchievedGoal[] achievedGoals) {
if (DEBUG_HAL) {
Log.d(TAG, "onGoalAchieved");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit achieved goal");
return;
}
mHealthServiceCallback.onGoalAchieved(achievedGoals);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit achieved goal:", e);
}
}
@Override // HalAdapter.HalListener
public void onHealthEventDetected(HealthEvent healthEvent) {
if (DEBUG_HAL) {
Log.d(TAG, "onHealthEventDetected");
}
try {
synchronized (mHealthServicesCallbackLock) {
if (mHealthServiceCallback == null) {
Log.w(TAG, "No callback registered! Failed to emit Health Event");
return;
}
mHealthServiceCallback.onHealthEventDetected(healthEvent);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to emit health event:", e);
}
}
@VisibleForTesting
final class PackageEventsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (!Intent.ACTION_PACKAGE_RESTARTED.equals(action)
&& !Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
return;
}
String packageFromEvent =
mContext.getPackageManager().getNameForUid(intent.getIntExtra(Intent.EXTRA_UID, 0));
if (!WHS_PACKAGE.equals(packageFromEvent)) {
return;
}
if (mWhsConnectionTracker.isConnected()) {
Log.w(TAG, "WHS package update, but is already connected.");
return;
}
Log.d(
TAG,
"WHS package updated. Will attempt to bind after "
+ PACKAGE_UPDATE_BIND_DELAY_MILLIS
+ "ms.");
mWhsBindingAgent.bind(PACKAGE_UPDATE_BIND_DELAY_MILLIS);
}
}
final class BinderService extends Stub {
@Override
public int[] getSupportedDataTypes() {
if (DEBUG_HAL) {
Log.d(TAG, String.format("getSupportedDataTypes()"));
}
return mHalAdapter.getSupportedDataTypes();
}
@Override
public DataTypeGoal[] getSupportedGoals() {
if (DEBUG_HAL) {
Log.d(TAG, String.format("getSupportedGoals()"));
}
return mHalAdapter.getSupportedGoals();
}
@Override
public int[] getAutoPauseAndResumeEnabledExerciseTypes() {
if (DEBUG_HAL) {
Log.d(TAG, String.format("getAutoPauseAndResumeEnabledExerciseTypes()"));
}
return mHalAdapter.getAutoPauseAndResumeEnabledExerciseTypes();
}
@Override
public int[] getAutoStopEnabledExerciseTypes() {
if (DEBUG_HAL) {
Log.d(TAG, "getAutoStopEnabledExerciseTypes");
}
return mHalAdapter.getAutoStopEnabledExerciseTypes();
}
/**
* @return the AutoStartCapabilities.
*/
@Override
public AutoStartCapabilities getAutoStartCapabilities() {
if (DEBUG_HAL) {
Log.d(TAG, "getAutoStartCapabilities");
}
return mHalAdapter.getAutoStartCapabilities();
}
@Override
public void startTracking(
TrackingConfig config, int[] dataTypes, DataTypeOffset[] offsets, boolean isPaused) {
if (DEBUG_HAL) {
Log.d(
TAG,
String.format(
"startTracking: %s %s %s %s",
config, Arrays.toString(dataTypes), Arrays.toString(offsets), isPaused));
}
mHalAdapter.startTracking(config, dataTypes, offsets, isPaused);
}
/** Starts automatic exercise detection for the given configs. */
@Override
public void startAutomaticExerciseDetection(
AutoStartConfig[] autoStartConfigs, AutoStartEvent autoStartOffset)
throws RemoteException {
if (DEBUG_HAL) {
Log.d(
TAG,
String.format(
"startAutomaticExerciseDetection: num_configs: %s", autoStartConfigs.length));
}
mHalAdapter.startAutomaticExerciseDetection(autoStartConfigs, autoStartOffset);
}
/** Stops automatic exercise detection for the given {@link ExerciseTypes}s. */
@Override
public void stopAutomaticExerciseDetection(int[] exerciseTypes) throws RemoteException {
if (DEBUG_HAL) {
Log.d(
TAG,
String.format(
"stopAutomaticExerciseDetection: num_exerciseTypes: %s", exerciseTypes.length));
}
mHalAdapter.stopAutomaticExerciseDetection(exerciseTypes);
}
@Override
public void updateTrackingConfig(TrackingConfig config, int[] dataTypes) {
if (DEBUG_HAL) {
Log.d(
TAG, String.format("updateTrackingConfig: %s %s", config, Arrays.toString(dataTypes)));
}
mHalAdapter.updateTrackingConfig(config, dataTypes);
}
@Override
public void pauseTracking(int[] dataTypes) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("pauseTracking: %s", Arrays.toString(dataTypes)));
}
mHalAdapter.pauseTracking(dataTypes);
}
@Override
public void resumeTracking(int[] dataTypes) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("resumeTracking: %s", Arrays.toString(dataTypes)));
}
mHalAdapter.resumeTracking(dataTypes);
}
@Override
public void stopTracking(int[] dataTypes) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("stopTracking: %s", Arrays.toString(dataTypes)));
}
mHalAdapter.stopTracking(dataTypes);
}
@Override
public int flush(int[] dataTypes) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("flush: %s", Arrays.toString(dataTypes)));
}
// Note: It is assumed that flush requests are made sequentially.
int flushIdForRequest = nextFlushId;
nextFlushId = Utils.generateNextFlushId(flushIdForRequest);
mHalAdapter.flush(flushIdForRequest, dataTypes);
return flushIdForRequest;
}
@Override
public void resetDataTypeOffsets(DataTypeOffset[] offsets) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("resetDataTypeOffsets: %s", Arrays.toString(offsets)));
}
mHalAdapter.resetDataTypeOffsets(offsets);
}
@Override
public void setHealthServiceCallback(IHealthServiceCallback callback) {
if (DEBUG_HAL) {
Log.d(TAG, "Setting IHealthServiceCallback");
}
synchronized (mHealthServicesCallbackLock) {
if (callback == null) {
Log.d(TAG, "Health Service Callback is null");
return;
}
mHealthServiceCallback = callback;
}
}
@Override
public void addGoal(DataTypeGoal goal) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("addGoal: %s", goal));
}
mHalAdapter.addGoal(goal);
}
@Override
public void removeGoal(DataTypeGoal goal) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("removeGoal: %s", goal));
}
mHalAdapter.removeGoal(goal);
}
@Override
public void setProfile(float heightCm, float weightKg, int ageYears, byte gender) {
if (DEBUG_HAL) {
Log.d(TAG, "setProfile");
}
mHalAdapter.setProfile(heightCm, weightKg, ageYears, gender);
}
@Override
public void setSwimmingPoolLength(int lengthMeters) {
if (DEBUG_HAL) {
Log.d(TAG, "setSwimmingPoolLength");
}
mHalAdapter.setSwimmingPoolLength(lengthMeters);
}
@Override
public void setHeartRateAlertParams(HrAlertParams params) {
if (DEBUG_HAL) {
Log.d(TAG, "setHeartRateAlertParams");
}
mHalAdapter.setHeartRateAlertParams(params);
}
@Override
public void setGolfShotDetectionParams(GolfShotDetectionParams params) {
if (DEBUG_HAL) {
Log.d(TAG, "setGolfShotDetectionParams");
}
mHalAdapter.setGolfShotDetectionParams(params);
}
@Override
public void setTestMode(boolean enableTestMode) {
if (DEBUG_HAL) {
Log.d(TAG, "setTestMode");
}
mHalAdapter.setTestMode(enableTestMode);
}
@Override
public void setOemCustomConfiguration(int configId, byte[] configValue) {
if (DEBUG_HAL) {
Log.d(TAG, String.format("setOemCustomConfiguration: %s", configId));
}
mHalAdapter.setOemCustomConfiguration(configId, configValue);
}
@Override
public boolean isHalServiceConnected() {
return mHalAdapter.isHalServiceConnected();
}
}
}