blob: 590afbaca29e13d4562ffb6a2bea2b86b23b7076 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.android.impl.google.services.gcm;
import android.app.IntentService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;
import org.joda.time.Duration;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import io.v.android.v23.V;
import io.v.v23.VFutures;
import io.v.v23.context.VContext;
import io.v.v23.services.wakeup.WakeUpClient;
import io.v.v23.services.wakeup.WakeUpClientFactory;
import io.v.v23.verror.VException;
/**
* Communicates with GCM servers to obtain a new registration token and then controls
* all app services that have registered themselves as persistent.
*/
public class GcmRegistrationService extends IntentService {
/**
* A key in {@link SharedPreferences} under which the mount root for
* app's persistent services is stored.
*/
public static final String WAKEUP_MOUNT_ROOT_PREF_KEY =
"io.v.android.impl.google.services.gcm.WAKEUP_MOUNT_ROOT";
/**
* Registers the service and starts it (via {@link #startService}).
* <p>
* If the registration fails, the service will be started but may never be woken up
* afterward.
* <p>
* If the service has already been registered it will only be started.
*/
public static void registerAndStartService(Context ctx, ComponentName service) {
Intent intent = new Intent(ctx, GcmRegistrationService.class);
intent.putExtra(EXTRA_MODE, Mode.REGISTER_AND_START_SERVICE.getValue());
intent.putExtra(EXTRA_SERVICE_INFO, service.flattenToString());
ctx.startService(intent);
}
/**
* Unregisters the services and stops it (via {@link #stopService}).
*/
public static void unregisterAndStopService(Context ctx, ComponentName service) {
Intent intent = new Intent(ctx, GcmRegistrationService.class);
intent.putExtra(EXTRA_MODE, Mode.UNREGISTER_AND_STOP_SERVICE.getValue());
intent.putExtra(EXTRA_SERVICE_INFO, service.flattenToString());
ctx.startService(intent);
}
/**
* Refreshes the GCM token and restarts all registered services (via {@link #stopService}
* followed by {@link #startService}.
*/
public static void refreshTokenAndRestartRegisteredServices(Context ctx) {
Intent intent = new Intent(ctx, GcmRegistrationService.class);
intent.putExtra(EXTRA_MODE, Mode.REFRESH_TOKEN_AND_RESTART_REGISTERED_SERVICES.getValue());
ctx.startService(intent);
}
// Stores the set of all persistent services' flattened ComponentNames.
private static final String REGISTERED_SERVICES_PREF_KEY =
"io.v.android.impl.google.services.gcm.REGISTERED_SERVICES_PREF_KEY";
private static final String EXTRA_MODE = "EXTRA_MODE";
private static final String EXTRA_SERVICE_INFO= "EXTRA_SERVICE_INFO";
private static final String VANADIUM_WAKEUP_SERVICE =
"/ns.dev.v.io:8101/users/jenkins.veyron@gmail.com/wakeup/server";
private static final String TAG = "GcmRegistrationService";
private enum Mode {
REGISTER_AND_START_SERVICE (1),
UNREGISTER_AND_STOP_SERVICE (2),
REFRESH_TOKEN_AND_RESTART_REGISTERED_SERVICES (3);
private final int value;
Mode(int value) {
this.value = value;
}
int getValue() {
return value;
}
static Mode fromInt(int value) {
for (Mode m : Mode.values()) {
if (m.getValue() == value) {
return m;
}
}
return null;
}
}
public GcmRegistrationService() {
super("GcmRegistrationService");
}
@Override
protected synchronized void onHandleIntent(Intent intent) {
Mode mode = Mode.fromInt(intent.getIntExtra(EXTRA_MODE, -1));
switch (mode) {
case REGISTER_AND_START_SERVICE:
registerAndStartService(intent);
break;
case UNREGISTER_AND_STOP_SERVICE:
unregisterAndStopService(intent);
break;
case REFRESH_TOKEN_AND_RESTART_REGISTERED_SERVICES:
refreshTokenAndRestartRegisteredServices();
break;
default:
Log.e(TAG, String.format("Invalid mode %s for GCMRegistrationService. " +
"Dropping the request.", mode));
}
}
private void registerAndStartService(Intent intent) {
ComponentName service = ComponentName.unflattenFromString(
intent.getStringExtra(EXTRA_SERVICE_INFO));
if (service == null) {
Log.e(TAG, "Couldn't extract service information from intent - dropping the request.");
return;
}
registerService(service);
if (loadMountRoot().isEmpty()) {
refreshToken();
}
startService(service);
}
private void unregisterAndStopService(Intent intent) {
ComponentName service = intent.getParcelableExtra(EXTRA_SERVICE_INFO);
if (service == null) {
Log.e(TAG, "Couldn't extract service information from intent - dropping the request.");
return;
}
unregisterService(service);
stopService(service);
}
private void refreshTokenAndRestartRegisteredServices() {
refreshToken();
for (ComponentName service : getRegisteredServices(this)) {
stopService(service);
startService(service);
}
}
private void refreshToken() {
VContext ctx = V.init(this);
try {
// Get token from Google GCM servers.
InstanceID instanceID = InstanceID.getInstance(this);
// TODO(spetrovic): Add a TTL for the token.
String token = instanceID.getToken("632758215260", GoogleCloudMessaging.INSTANCE_ID_SCOPE);
Log.d(TAG, "Token is: " + token);
// Register the token with the Vanadium wakeup service.
WakeUpClient wake = WakeUpClientFactory.getWakeUpClient(VANADIUM_WAKEUP_SERVICE);
VContext ctxT = ctx.withTimeout(Duration.standardSeconds(20));
String mountRoot = VFutures.sync(wake.register(ctxT, token));
// Store the wakeup mount root in SharedPreferences.
storeMountRoot(mountRoot);
} catch (IOException e) {
Log.e(TAG, "Couldn't fetch GCM registration token: " + e.toString());
} catch (VException e) {
Log.e(TAG, "Couldn't register GCM token with vanadium services: " + e.toString());
} finally {
ctx.cancel();
}
}
private void registerService(ComponentName service) {
Set<String> s = loadRegisteredServices(this);
s.add(service.flattenToString());
storeRegisteredServices(s);
}
private void unregisterService(ComponentName service) {
Set<String> s = loadRegisteredServices(this);
s.remove(service.flattenToString());
storeRegisteredServices(s);
}
/**
* Returns {@code true} iff the given {@code service} is a registered persistent service.
*/
public static boolean isServiceRegistered(Context context, ComponentName service) {
Set<String> s = loadRegisteredServices(context);
return s.contains(service.flattenToString());
}
/**
* Returns a list of all registered persistent services.
*/
public static ComponentName[] getRegisteredServices(Context context) {
Set<String> s = loadRegisteredServices(context);
String[] names = s.toArray(new String[s.size()]);
ComponentName[] ret = new ComponentName[s.size()];
for (int i = 0; i < names.length; ++i) {
ret[i] = ComponentName.unflattenFromString(names[i]);
}
return ret;
}
private static Set<String> loadRegisteredServices(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getStringSet(
REGISTERED_SERVICES_PREF_KEY, new HashSet<String>());
}
private void storeRegisteredServices(Set<String> services) {
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(this).edit();
editor.putStringSet(REGISTERED_SERVICES_PREF_KEY, services);
editor.commit();
}
private String loadMountRoot() {
return PreferenceManager.getDefaultSharedPreferences(this).getString(
WAKEUP_MOUNT_ROOT_PREF_KEY, "");
}
private void storeMountRoot(String mountRoot) {
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(this).edit();
editor.putString(WAKEUP_MOUNT_ROOT_PREF_KEY, mountRoot);
editor.commit();
}
private void startService(ComponentName service) {
final Intent intent = new Intent();
intent.setComponent(service);
startService(intent);
}
private void stopService(ComponentName service) {
Intent intent = new Intent();
intent.setComponent(service);
stopService(intent);
}
}