// Copyright 2013 The Chromium 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 org.chromium.sync.notifier;
import android.accounts.Account;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import com.google.ipc.invalidation.external.client.types.ObjectId;
import org.chromium.base.VisibleForTesting;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Class to manage the preferences used by the invalidation client.
*
* This class provides methods to read and write the preferences used by the invalidation client.
*
* To read a preference, call the appropriate {@code get...} method.
*
* To write a preference, first call {@link #edit} to obtain a {@link EditContext}. Then, make
* one or more calls to a {@code set...} method, providing the same edit context to each call.
* Finally, call {@link #commit(EditContext)} to save the changes to stable storage.
*
* @author dsmyers@google.com (Daniel Myers)
*/
public class InvalidationPreferences {
/**
* Wrapper around a {@link android.content.SharedPreferences.Editor} for the preferences.
* Used to avoid exposing raw preference objects to users of this class.
*/
public class EditContext {
private final SharedPreferences.Editor mEditor;
EditContext() {
mEditor = PreferenceManager.getDefaultSharedPreferences(mContext).edit();
}
}
@VisibleForTesting
public static class PrefKeys {
/**
* Shared preference key to store the invalidation types that we want to register
* for.
*/
@VisibleForTesting
public static final String SYNC_TANGO_TYPES = "sync_tango_types";
/**
* Shared preference key to store tango object ids for additional objects that we want to
* register for.
*/
@VisibleForTesting
public static final String TANGO_OBJECT_IDS = "tango_object_ids";
/** Shared preference key to store the name of the account in use. */
@VisibleForTesting
public static final String SYNC_ACCT_NAME = "sync_acct_name";
/** Shared preference key to store the type of account in use. */
static final String SYNC_ACCT_TYPE = "sync_acct_type";
/** Shared preference key to store internal notification client library state. */
static final String SYNC_TANGO_INTERNAL_STATE = "sync_tango_internal_state";
}
private static final String TAG = "InvalidationPreferences";
private final Context mContext;
public InvalidationPreferences(Context context) {
Context appContext = context.getApplicationContext();
if (appContext == null) throw new NullPointerException("Unable to get application context");
mContext = appContext;
}
/** Returns a new {@link EditContext} to modify the preferences managed by this class. */
public EditContext edit() {
return new EditContext();
}
/**
* Applies the changes accumulated in {@code editContext}. Returns whether they were
* successfully written.
*
* NOTE: this method performs blocking I/O and must not be called from the UI thread.
*/
public boolean commit(EditContext editContext) {
if (!editContext.mEditor.commit()) {
Log.w(TAG, "Failed to commit invalidation preferences");
return false;
}
return true;
}
/** Returns the saved sync types, or {@code null} if none exist. */
@Nullable public Set getSavedSyncedTypes() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
return preferences.getStringSet(PrefKeys.SYNC_TANGO_TYPES, null);
}
/** Sets the saved sync types to {@code syncTypes} in {@code editContext}. */
public void setSyncTypes(EditContext editContext, Collection syncTypes) {
if (syncTypes == null) throw new NullPointerException("syncTypes is null.");
Set selectedTypesSet = new HashSet(syncTypes);
editContext.mEditor.putStringSet(PrefKeys.SYNC_TANGO_TYPES, selectedTypesSet);
}
/** Returns the saved non-sync object ids, or {@code null} if none exist. */
@Nullable
public Set getSavedObjectIds() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
Set objectIdStrings = preferences.getStringSet(PrefKeys.TANGO_OBJECT_IDS, null);
if (objectIdStrings == null) {
return null;
}
Set objectIds = new HashSet(objectIdStrings.size());
for (String objectIdString : objectIdStrings) {
ObjectId objectId = getObjectId(objectIdString);
if (objectId != null) {
objectIds.add(objectId);
}
}
return objectIds;
}
/** Sets the saved non-sync object ids */
public void setObjectIds(EditContext editContext, Collection objectIds) {
if (objectIds == null) throw new NullPointerException("objectIds is null.");
Set objectIdStrings = new HashSet(objectIds.size());
for (ObjectId objectId : objectIds) {
objectIdStrings.add(getObjectIdString(objectId));
}
editContext.mEditor.putStringSet(PrefKeys.TANGO_OBJECT_IDS, objectIdStrings);
}
/** Returns the saved account, or {@code null} if none exists. */
@Nullable public Account getSavedSyncedAccount() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String accountName = preferences.getString(PrefKeys.SYNC_ACCT_NAME, null);
String accountType = preferences.getString(PrefKeys.SYNC_ACCT_TYPE, null);
if (accountName == null || accountType == null) {
return null;
}
return new Account(accountName, accountType);
}
/** Sets the saved account to {@code account} in {@code editContext}. */
public void setAccount(EditContext editContext, Account account) {
editContext.mEditor.putString(PrefKeys.SYNC_ACCT_NAME, account.name);
editContext.mEditor.putString(PrefKeys.SYNC_ACCT_TYPE, account.type);
}
/** Returns the notification client internal state. */
@Nullable public byte[] getInternalNotificationClientState() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String base64State = preferences.getString(PrefKeys.SYNC_TANGO_INTERNAL_STATE, null);
if (base64State == null) {
return null;
}
return Base64.decode(base64State, Base64.DEFAULT);
}
/** Sets the notification client internal state to {@code state}. */
public void setInternalNotificationClientState(EditContext editContext, byte[] state) {
editContext.mEditor.putString(PrefKeys.SYNC_TANGO_INTERNAL_STATE,
Base64.encodeToString(state, Base64.DEFAULT));
}
/** Converts the given object id to a string for storage in preferences. */
private String getObjectIdString(ObjectId objectId) {
return objectId.getSource() + ":" + new String(objectId.getName());
}
/**
* Converts the given object id string stored in preferences to an object id.
* Returns null if the string does not represent a valid object id.
*/
private ObjectId getObjectId(String objectIdString) {
int separatorPos = objectIdString.indexOf(':');
// Ensure that the separator is surrounded by at least one character on each side.
if (separatorPos < 1 || separatorPos == objectIdString.length() - 1) {
return null;
}
int objectSource;
try {
objectSource = Integer.parseInt(objectIdString.substring(0, separatorPos));
} catch (NumberFormatException e) {
return null;
}
byte[] objectName = objectIdString.substring(separatorPos + 1).getBytes();
return ObjectId.newInstance(objectSource, objectName);
}
}