1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.sync.notifier; 6 7import android.accounts.Account; 8import android.content.Context; 9import android.content.SharedPreferences; 10import android.preference.PreferenceManager; 11import android.util.Base64; 12import android.util.Log; 13 14import com.google.ipc.invalidation.external.client.types.ObjectId; 15 16import org.chromium.base.VisibleForTesting; 17 18import java.util.Collection; 19import java.util.HashSet; 20import java.util.Set; 21 22import javax.annotation.Nullable; 23 24/** 25 * Class to manage the preferences used by the invalidation client. 26 * <p> 27 * This class provides methods to read and write the preferences used by the invalidation client. 28 * <p> 29 * To read a preference, call the appropriate {@code get...} method. 30 * <p> 31 * To write a preference, first call {@link #edit} to obtain a {@link EditContext}. Then, make 32 * one or more calls to a {@code set...} method, providing the same edit context to each call. 33 * Finally, call {@link #commit(EditContext)} to save the changes to stable storage. 34 * 35 * @author dsmyers@google.com (Daniel Myers) 36 */ 37public class InvalidationPreferences { 38 /** 39 * Wrapper around a {@link android.content.SharedPreferences.Editor} for the preferences. 40 * Used to avoid exposing raw preference objects to users of this class. 41 */ 42 public class EditContext { 43 private final SharedPreferences.Editor mEditor; 44 45 EditContext() { 46 mEditor = PreferenceManager.getDefaultSharedPreferences(mContext).edit(); 47 } 48 } 49 50 @VisibleForTesting 51 public static class PrefKeys { 52 /** 53 * Shared preference key to store the invalidation types that we want to register 54 * for. 55 */ 56 @VisibleForTesting 57 public static final String SYNC_TANGO_TYPES = "sync_tango_types"; 58 59 /** 60 * Shared preference key to store tango object ids for additional objects that we want to 61 * register for. 62 */ 63 @VisibleForTesting 64 public static final String TANGO_OBJECT_IDS = "tango_object_ids"; 65 66 /** Shared preference key to store the name of the account in use. */ 67 @VisibleForTesting 68 public static final String SYNC_ACCT_NAME = "sync_acct_name"; 69 70 /** Shared preference key to store the type of account in use. */ 71 static final String SYNC_ACCT_TYPE = "sync_acct_type"; 72 73 /** Shared preference key to store internal notification client library state. */ 74 static final String SYNC_TANGO_INTERNAL_STATE = "sync_tango_internal_state"; 75 } 76 77 private static final String TAG = "InvalidationPreferences"; 78 79 private final Context mContext; 80 81 public InvalidationPreferences(Context context) { 82 Context appContext = context.getApplicationContext(); 83 if (appContext == null) throw new NullPointerException("Unable to get application context"); 84 mContext = appContext; 85 } 86 87 /** Returns a new {@link EditContext} to modify the preferences managed by this class. */ 88 public EditContext edit() { 89 return new EditContext(); 90 } 91 92 /** 93 * Applies the changes accumulated in {@code editContext}. Returns whether they were 94 * successfully written. 95 * <p> 96 * NOTE: this method performs blocking I/O and must not be called from the UI thread. 97 */ 98 public boolean commit(EditContext editContext) { 99 if (!editContext.mEditor.commit()) { 100 Log.w(TAG, "Failed to commit invalidation preferences"); 101 return false; 102 } 103 return true; 104 } 105 106 /** Returns the saved sync types, or {@code null} if none exist. */ 107 @Nullable public Set<String> getSavedSyncedTypes() { 108 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); 109 return preferences.getStringSet(PrefKeys.SYNC_TANGO_TYPES, null); 110 } 111 112 /** Sets the saved sync types to {@code syncTypes} in {@code editContext}. */ 113 public void setSyncTypes(EditContext editContext, Collection<String> syncTypes) { 114 if (syncTypes == null) throw new NullPointerException("syncTypes is null."); 115 Set<String> selectedTypesSet = new HashSet<String>(syncTypes); 116 editContext.mEditor.putStringSet(PrefKeys.SYNC_TANGO_TYPES, selectedTypesSet); 117 } 118 119 /** Returns the saved non-sync object ids, or {@code null} if none exist. */ 120 @Nullable 121 public Set<ObjectId> getSavedObjectIds() { 122 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); 123 Set<String> objectIdStrings = preferences.getStringSet(PrefKeys.TANGO_OBJECT_IDS, null); 124 if (objectIdStrings == null) { 125 return null; 126 } 127 Set<ObjectId> objectIds = new HashSet<ObjectId>(objectIdStrings.size()); 128 for (String objectIdString : objectIdStrings) { 129 ObjectId objectId = getObjectId(objectIdString); 130 if (objectId != null) { 131 objectIds.add(objectId); 132 } 133 } 134 return objectIds; 135 } 136 137 /** Sets the saved non-sync object ids */ 138 public void setObjectIds(EditContext editContext, Collection<ObjectId> objectIds) { 139 if (objectIds == null) throw new NullPointerException("objectIds is null."); 140 Set<String> objectIdStrings = new HashSet<String>(objectIds.size()); 141 for (ObjectId objectId : objectIds) { 142 objectIdStrings.add(getObjectIdString(objectId)); 143 } 144 editContext.mEditor.putStringSet(PrefKeys.TANGO_OBJECT_IDS, objectIdStrings); 145 } 146 147 /** Returns the saved account, or {@code null} if none exists. */ 148 @Nullable public Account getSavedSyncedAccount() { 149 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); 150 String accountName = preferences.getString(PrefKeys.SYNC_ACCT_NAME, null); 151 String accountType = preferences.getString(PrefKeys.SYNC_ACCT_TYPE, null); 152 if (accountName == null || accountType == null) { 153 return null; 154 } 155 return new Account(accountName, accountType); 156 } 157 158 /** Sets the saved account to {@code account} in {@code editContext}. */ 159 public void setAccount(EditContext editContext, Account account) { 160 editContext.mEditor.putString(PrefKeys.SYNC_ACCT_NAME, account.name); 161 editContext.mEditor.putString(PrefKeys.SYNC_ACCT_TYPE, account.type); 162 } 163 164 /** Returns the notification client internal state. */ 165 @Nullable public byte[] getInternalNotificationClientState() { 166 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); 167 String base64State = preferences.getString(PrefKeys.SYNC_TANGO_INTERNAL_STATE, null); 168 if (base64State == null) { 169 return null; 170 } 171 return Base64.decode(base64State, Base64.DEFAULT); 172 } 173 174 /** Sets the notification client internal state to {@code state}. */ 175 public void setInternalNotificationClientState(EditContext editContext, byte[] state) { 176 editContext.mEditor.putString(PrefKeys.SYNC_TANGO_INTERNAL_STATE, 177 Base64.encodeToString(state, Base64.DEFAULT)); 178 } 179 180 /** Converts the given object id to a string for storage in preferences. */ 181 private String getObjectIdString(ObjectId objectId) { 182 return objectId.getSource() + ":" + new String(objectId.getName()); 183 } 184 185 /** 186 * Converts the given object id string stored in preferences to an object id. 187 * Returns null if the string does not represent a valid object id. 188 */ 189 private ObjectId getObjectId(String objectIdString) { 190 int separatorPos = objectIdString.indexOf(':'); 191 // Ensure that the separator is surrounded by at least one character on each side. 192 if (separatorPos < 1 || separatorPos == objectIdString.length() - 1) { 193 return null; 194 } 195 int objectSource; 196 try { 197 objectSource = Integer.parseInt(objectIdString.substring(0, separatorPos)); 198 } catch (NumberFormatException e) { 199 return null; 200 } 201 byte[] objectName = objectIdString.substring(separatorPos + 1).getBytes(); 202 return ObjectId.newInstance(objectSource, objectName); 203 } 204} 205