зеркало из https://github.com/mozilla/gecko-dev.git
Bug 790931 - Broadcast when Sync Android Account is being deleted to many Firefox Apps. r=rnewman
This commit is contained in:
Родитель
dcad379d15
Коммит
86b4e88932
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -18,4 +18,33 @@ public class GlobalConstants {
|
|||
public static final String BROWSER_INTENT_CLASS = BROWSER_INTENT_PACKAGE + ".App";
|
||||
|
||||
public static final String ACCOUNTTYPE_SYNC = "@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@";
|
||||
|
||||
/**
|
||||
* Bug 790931: this signing-level permission protects broadcast intents that
|
||||
* should be received only by Firefox versions sharing the same Android
|
||||
* Account type.
|
||||
*/
|
||||
public static final String PER_ACCOUNT_TYPE_PERMISSION = "@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE";
|
||||
|
||||
/**
|
||||
* Bug 790931: this action is broadcast when an Android Account is deleted.
|
||||
* This allows each installed Firefox to delete any pickle file and to (try
|
||||
* to) wipe its client record from the server.
|
||||
* <p>
|
||||
* It is protected by signing-level permission PER_ACCOUNT_TYPE_PERMISSION and
|
||||
* can be received only by Firefox versions sharing the same Android Account
|
||||
* type.
|
||||
* <p>
|
||||
* See {@link SyncAccounts#makeSyncAccountDeletedIntent(android.content.Context, android.accounts.AccountManager, android.accounts.Account)}
|
||||
* for contents of the intent.
|
||||
*/
|
||||
public static final String SYNC_ACCOUNT_DELETED_ACTION = "@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.accounts.SYNC_ACCOUNT_DELETED_ACTION";
|
||||
|
||||
/**
|
||||
* Bug 790931: version number of contents of SYNC_ACCOUNT_DELETED_ACTION intent.
|
||||
* <p>
|
||||
* See {@link SyncAccounts#makeSyncAccountDeletedIntent(android.content.Context, android.accounts.AccountManager, android.accounts.Account)}
|
||||
* for contents of the intent.
|
||||
*/
|
||||
public static final long SYNC_ACCOUNT_DELETED_INTENT_VERSION = 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.receivers;
|
||||
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
public class SyncAccountDeletedReceiver extends BroadcastReceiver {
|
||||
public static final String LOG_TAG = "SyncAccountDeletedReceiver";
|
||||
|
||||
/**
|
||||
* This receiver can be killed as soon as it returns, but we have things to do
|
||||
* that can't be done on the main thread (network activity). Therefore we
|
||||
* start a service to do our clean up work for us, with Android doing the
|
||||
* heavy lifting for the service's lifecycle.
|
||||
* <p>
|
||||
* See <a href="http://developer.android.com/reference/android/content/BroadcastReceiver.html#ReceiverLifecycle">the Android documentation</a>
|
||||
* for details.
|
||||
*/
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent broadcastIntent) {
|
||||
Logger.debug(LOG_TAG, "Sync Account Deleted broadcast received.");
|
||||
|
||||
Intent serviceIntent = new Intent(context, SyncAccountDeletedService.class);
|
||||
serviceIntent.putExtras(broadcastIntent);
|
||||
context.startService(serviceIntent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.receivers;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.config.AccountPickler;
|
||||
import org.mozilla.gecko.sync.config.ClientRecordTerminator;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
|
||||
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class SyncAccountDeletedService extends IntentService {
|
||||
public static final String LOG_TAG = "SyncAccountDeletedService";
|
||||
|
||||
public SyncAccountDeletedService() {
|
||||
super(LOG_TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
final Context context = this;
|
||||
|
||||
long intentVersion = intent.getLongExtra(Constants.JSON_KEY_VERSION, 0);
|
||||
long expectedVersion = GlobalConstants.SYNC_ACCOUNT_DELETED_INTENT_VERSION;
|
||||
if (intentVersion != expectedVersion) {
|
||||
Logger.warn(LOG_TAG, "Intent malformed: version " + intentVersion + " given but version " + expectedVersion + "expected. " +
|
||||
"Not cleaning up after deleted Account.");
|
||||
return;
|
||||
}
|
||||
|
||||
String accountName = intent.getStringExtra(Constants.JSON_KEY_ACCOUNT); // Android Account name, not Sync encoded account name.
|
||||
if (accountName == null) {
|
||||
Logger.warn(LOG_TAG, "Intent malformed: no account name given. Not cleaning up after deleted Account.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the Account pickle.
|
||||
Logger.info(LOG_TAG, "Sync account named " + accountName + " being removed; " +
|
||||
"deleting saved pickle file '" + Constants.ACCOUNT_PICKLE_FILENAME + "'.");
|
||||
deletePickle(context);
|
||||
|
||||
SyncAccountParameters params;
|
||||
try {
|
||||
String payload = intent.getStringExtra(Constants.JSON_KEY_PAYLOAD);
|
||||
if (payload == null) {
|
||||
Logger.warn(LOG_TAG, "Intent malformed: no payload given. Not deleting client record.");
|
||||
return;
|
||||
}
|
||||
ExtendedJSONObject o = ExtendedJSONObject.parseJSONObject(payload);
|
||||
params = new SyncAccountParameters(context, AccountManager.get(context), o);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception fetching account parameters from intent data; not deleting client record.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bug 770785: delete the Account's client record.
|
||||
Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
|
||||
"deleting client record from server.");
|
||||
deleteClientRecord(context, accountName, params.password, params.serverURL);
|
||||
}
|
||||
|
||||
public static void deletePickle(final Context context) {
|
||||
try {
|
||||
AccountPickler.deletePickle(context, Constants.ACCOUNT_PICKLE_FILENAME);
|
||||
} catch (Exception e) {
|
||||
// This should never happen, but we really don't want to die in a background thread.
|
||||
Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void deleteClientRecord(final Context context, final String accountName,
|
||||
final String password, final String serverURL) {
|
||||
String encodedUsername;
|
||||
try {
|
||||
encodedUsername = Utils.usernameFromAccount(accountName);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountName == null || encodedUsername == null || password == null || serverURL == null) {
|
||||
Logger.warn(LOG_TAG, "Account parameters were null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not exactly modular. We need to get some information about
|
||||
// the account, namely the current clusterURL and client GUID, and we
|
||||
// extract it by hand. We're not worried about the Account being
|
||||
// deleted out from under us since the prefs remain even after Account
|
||||
// deletion.
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||
|
||||
SharedPreferences prefs;
|
||||
try {
|
||||
prefs = Utils.getSharedPreferences(context, product, encodedUsername, serverURL, profile, version);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception fetching preferences; not deleting client record from server.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
final String clientGuid = prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
|
||||
final String clusterURL = prefs.getString(SyncConfiguration.PREF_CLUSTER_URL, null);
|
||||
|
||||
// Finally, a good place to do this.
|
||||
prefs.edit().clear().commit();
|
||||
|
||||
if (clientGuid == null) {
|
||||
Logger.warn(LOG_TAG, "Client GUID was null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clusterURL == null) {
|
||||
Logger.warn(LOG_TAG, "Cluster URL was null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ClientRecordTerminator.deleteClientRecord(encodedUsername, password, clusterURL, clientGuid);
|
||||
} catch (Exception e) {
|
||||
// This should never happen, but we really don't want to die in a background thread.
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,6 @@ package org.mozilla.gecko.sync.setup;
|
|||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.CredentialException;
|
||||
|
@ -23,8 +21,6 @@ import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
|||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
@ -60,34 +56,6 @@ public class SyncAccounts {
|
|||
return AccountManager.get(c).getAccountsByType(GlobalConstants.ACCOUNTTYPE_SYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously invalidate the auth token for a Sync account.
|
||||
*
|
||||
* @param accountManager Android account manager.
|
||||
* @param account Android account.
|
||||
*/
|
||||
public static void invalidateAuthToken(final AccountManager accountManager, final Account account) {
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// blockingGetAuthToken must not be called from the main thread.
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String authToken;
|
||||
try {
|
||||
authToken = accountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE_PLAIN, true);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception while invalidating auth token.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
accountManager.invalidateAuthToken(GlobalConstants.ACCOUNTTYPE_SYNC, authToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a Sync account is set up, or we have a pickled Sync account
|
||||
* on disk that should be un-pickled (Bug 769745). If we have a pickled Sync
|
||||
|
@ -488,39 +456,6 @@ public class SyncAccounts {
|
|||
return intent;
|
||||
}
|
||||
|
||||
protected static class SyncAccountVersion0Callback implements AccountManagerCallback<Bundle> {
|
||||
protected final Context context;
|
||||
protected final CountDownLatch latch;
|
||||
|
||||
public String authToken = null;
|
||||
|
||||
public SyncAccountVersion0Callback(final Context context, final CountDownLatch latch) {
|
||||
this.context = context;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
try {
|
||||
Bundle bundle = future.getResult(60L, TimeUnit.SECONDS);
|
||||
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
|
||||
throw new IllegalStateException("KEY_INTENT included in AccountManagerFuture bundle.");
|
||||
}
|
||||
if (bundle.containsKey(AccountManager.KEY_ERROR_MESSAGE)) {
|
||||
throw new IllegalStateException("KEY_ERROR_MESSAGE (= " + bundle.getString(AccountManager.KEY_ERROR_MESSAGE) + ") "
|
||||
+ " included in AccountManagerFuture bundle.");
|
||||
}
|
||||
|
||||
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
|
||||
} catch (Exception e) {
|
||||
// Do nothing -- caller will find null authToken.
|
||||
Logger.warn(LOG_TAG, "Got exception fetching auth token; ignoring and returning null auth token instead.", e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously extract Sync account parameters from Android account version
|
||||
* 0, using plain auth token type.
|
||||
|
@ -528,6 +463,7 @@ public class SyncAccounts {
|
|||
* Safe to call from main thread.
|
||||
*
|
||||
* @param context
|
||||
* Android context.
|
||||
* @param accountManager
|
||||
* Android account manager.
|
||||
* @param account
|
||||
|
@ -537,24 +473,6 @@ public class SyncAccounts {
|
|||
*/
|
||||
public static SyncAccountParameters blockingFromAndroidAccountV0(final Context context, final AccountManager accountManager, final Account account)
|
||||
throws CredentialException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final SyncAccountVersion0Callback callback = new SyncAccountVersion0Callback(context, latch);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Get an auth token.
|
||||
accountManager.getAuthToken(account, Constants.AUTHTOKEN_TYPE_PLAIN, true, callback, null);
|
||||
}
|
||||
}).start();
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Logger.warn(LOG_TAG, "Got exception waiting for Sync account parameters; throwing.");
|
||||
throw new CredentialException.MissingAllCredentialsException(e);
|
||||
}
|
||||
|
||||
String username;
|
||||
try {
|
||||
username = Utils.usernameFromAccount(account.name);
|
||||
|
@ -564,17 +482,17 @@ public class SyncAccounts {
|
|||
throw new CredentialException.MissingCredentialException("username");
|
||||
}
|
||||
|
||||
final String password = callback.authToken;
|
||||
|
||||
/*
|
||||
* If we are accessing an Account that we don't own, Android will throw an
|
||||
* unchecked <code>SecurityException</code> saying
|
||||
* "W FxSync(XXXX) java.lang.SecurityException: caller uid XXXXX is different than the authenticator's uid".
|
||||
* We catch that error and throw accordingly.
|
||||
*/
|
||||
String password;
|
||||
String syncKey;
|
||||
String serverURL;
|
||||
try {
|
||||
password = accountManager.getPassword(account);
|
||||
syncKey = accountManager.getUserData(account, Constants.OPTION_SYNCKEY);
|
||||
serverURL = accountManager.getUserData(account, Constants.OPTION_SERVER);
|
||||
} catch (SecurityException e) {
|
||||
|
@ -610,4 +528,56 @@ public class SyncAccounts {
|
|||
throw new CredentialException.MissingAllCredentialsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bug 790931: create an intent announcing that a Sync account will be
|
||||
* deleted.
|
||||
* <p>
|
||||
* This intent <b>must</b> be broadcast with secure permissions, because it
|
||||
* contains sensitive user information including the Sync account password and
|
||||
* Sync key.
|
||||
* <p>
|
||||
* Version 1 of the created intent includes extras with keys
|
||||
* <code>Constants.JSON_KEY_VERSION</code>,
|
||||
* <code>Constants.JSON_KEY_TIMESTAMP</code>, and
|
||||
* <code>Constants.JSON_KEY_ACCOUNT</code> (which is the Android Account name,
|
||||
* not the encoded Sync Account name).
|
||||
* <p>
|
||||
* If possible, it contains the key <code>Constants.JSON_KEY_PAYLOAD</code>
|
||||
* with value the Sync account parameters as JSON, <b>except the Sync key has
|
||||
* been replaced with the empty string</b>. (We replace, rather than remove,
|
||||
* the Sync key because SyncAccountParameters expects a non-null Sync key.)
|
||||
*
|
||||
* @see SyncAccountParameters#asJSON
|
||||
*
|
||||
* @param context
|
||||
* Android context.
|
||||
* @param accountManager
|
||||
* Android account manager.
|
||||
* @param account
|
||||
* Android account being removed.
|
||||
* @return <code>Intent</code> to broadcast.
|
||||
*/
|
||||
public static Intent makeSyncAccountDeletedIntent(final Context context, final AccountManager accountManager, final Account account) {
|
||||
final Intent intent = new Intent(GlobalConstants.SYNC_ACCOUNT_DELETED_ACTION);
|
||||
|
||||
intent.putExtra(Constants.JSON_KEY_VERSION, Long.valueOf(GlobalConstants.SYNC_ACCOUNT_DELETED_INTENT_VERSION));
|
||||
intent.putExtra(Constants.JSON_KEY_TIMESTAMP, Long.valueOf(System.currentTimeMillis()));
|
||||
intent.putExtra(Constants.JSON_KEY_ACCOUNT, account.name);
|
||||
|
||||
SyncAccountParameters accountParameters = null;
|
||||
try {
|
||||
accountParameters = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception fetching account parameters.", e);
|
||||
}
|
||||
|
||||
if (accountParameters != null) {
|
||||
ExtendedJSONObject json = accountParameters.asJSON();
|
||||
json.put(Constants.JSON_KEY_SYNCKEY, ""); // Reduce attack surface area by removing Sync key.
|
||||
intent.putExtra(Constants.JSON_KEY_PAYLOAD, json.toJSONString());
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,7 @@ import java.security.NoSuchAlgorithmException;
|
|||
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.config.AccountPickler;
|
||||
import org.mozilla.gecko.sync.config.ClientRecordTerminator;
|
||||
import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity;
|
||||
|
||||
import android.accounts.AbstractAccountAuthenticator;
|
||||
|
@ -24,12 +20,12 @@ import android.accounts.NetworkErrorException;
|
|||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class SyncAuthenticatorService extends Service {
|
||||
private static final String LOG_TAG = "SyncAuthService";
|
||||
|
||||
private SyncAccountAuthenticator sAccountAuthenticator = null;
|
||||
|
||||
@Override
|
||||
|
@ -213,10 +209,11 @@ public class SyncAuthenticatorService extends Service {
|
|||
* Bug 769745: persist pickled Sync account settings so that we can unpickle
|
||||
* after Fennec is moved to the SD card.
|
||||
* <p>
|
||||
* This is <b>not</b> called when an Android Account is blown away due to the
|
||||
* SD card being unmounted.
|
||||
* This is <b>not</b> called when an Android Account is blown away due to
|
||||
* the SD card being unmounted.
|
||||
* <p>
|
||||
* This is a terrible hack, but it's better than the catching the generic
|
||||
* Broadcasting a Firefox intent to version sharing this Android Account is
|
||||
* a terrible hack, but it's better than the catching the generic
|
||||
* "accounts changed" broadcast intent and trying to figure out whether our
|
||||
* Account disappeared.
|
||||
*/
|
||||
|
@ -236,92 +233,21 @@ public class SyncAuthenticatorService extends Service {
|
|||
return result;
|
||||
}
|
||||
|
||||
final String accountName = account.name;
|
||||
|
||||
// Delete the Account pickle in the background.
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
|
||||
"deleting saved pickle file '" + Constants.ACCOUNT_PICKLE_FILENAME + "'.");
|
||||
try {
|
||||
AccountPickler.deletePickle(mContext, Constants.ACCOUNT_PICKLE_FILENAME);
|
||||
} catch (Exception e) {
|
||||
// This should never happen, but we really don't want to die in a background thread.
|
||||
Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Bug 770785: delete the Account's client record in the background. We
|
||||
// want to get the Account's data synchronously, though, since it is
|
||||
// possible the Account object will be invalid by the time the Runnable
|
||||
// executes. We don't need to worry about accessing prefs too early since
|
||||
// deleting the Account doesn't remove them -- at least, not yet. We would
|
||||
// prefer to use SyncAccounts.blockingFromAndroidAccountV0, but that
|
||||
// hangs, possibly because the Account Manager doesn't appreciate giving
|
||||
// out an auth token while deleting the account.
|
||||
|
||||
final AccountManager accountManager = AccountManager.get(mContext);
|
||||
final String password = accountManager.getPassword(account);
|
||||
final String serverURL = accountManager.getUserData(account, Constants.OPTION_SERVER);
|
||||
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Logger.info(LOG_TAG, "Account named " + accountName + " being removed; " +
|
||||
"deleting client record from server.");
|
||||
|
||||
String encodedUsername;
|
||||
try {
|
||||
encodedUsername = Utils.usernameFromAccount(accountName);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accountName == null || encodedUsername == null || password == null || serverURL == null) {
|
||||
Logger.warn(LOG_TAG, "Account parameters were null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not exactly modular. We need to get some information about
|
||||
// the account, namely the current clusterURL and client GUID, and we
|
||||
// extract it by hand. We're not worried about the Account being
|
||||
// deleted out from under us since the prefs remain even after Account
|
||||
// deletion.
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||
|
||||
SharedPreferences prefs;
|
||||
try {
|
||||
prefs = Utils.getSharedPreferences(mContext, product, encodedUsername, serverURL, profile, version);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception fetching preferences; not deleting client record from server.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
final String clientGuid = prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
|
||||
final String clusterURL = prefs.getString(SyncConfiguration.PREF_CLUSTER_URL, null);
|
||||
|
||||
if (clientGuid == null) {
|
||||
Logger.warn(LOG_TAG, "Client GUID was null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clusterURL == null) {
|
||||
Logger.warn(LOG_TAG, "Cluster URL was null; not deleting client record from server.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ClientRecordTerminator.deleteClientRecord(encodedUsername, password, clusterURL, clientGuid);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception deleting client record from server; ignoring.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Bug 790931: Broadcast a message to all Firefox versions sharing this
|
||||
// Android Account type telling that this Sync Account has been deleted.
|
||||
//
|
||||
// We would really prefer to receive Android's
|
||||
// LOGIN_ACCOUNTS_CHANGED_ACTION broadcast, but that
|
||||
// doesn't include enough information about which Accounts changed to
|
||||
// correctly identify whether a Sync account has been removed (when some
|
||||
// Firefox versions are installed on the SD card).
|
||||
//
|
||||
// Broadcast intents protected with permissions are secure, so it's okay
|
||||
// to include password and sync key, etc.
|
||||
final Intent intent = SyncAccounts.makeSyncAccountDeletedIntent(mContext, AccountManager.get(mContext), account);
|
||||
Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " +
|
||||
"broadcasting secure intent " + intent.getAction() + ".");
|
||||
mContext.sendBroadcast(intent, GlobalConstants.PER_ACCOUNT_TYPE_PERMISSION);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
}
|
||||
|
||||
/**
|
||||
* Handle an exception: update stats, invalidate auth token, log errors, etc.
|
||||
* Handle an exception: update stats, log errors, etc.
|
||||
* Wakes up sleeping threads by calling notifyMonitor().
|
||||
*
|
||||
* @param globalSession
|
||||
|
@ -97,9 +97,6 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
*/
|
||||
protected void processException(final GlobalSession globalSession, final Exception e) {
|
||||
try {
|
||||
// Just in case, invalidate auth token.
|
||||
SyncAccounts.invalidateAuthToken(AccountManager.get(mContext), localAccount);
|
||||
|
||||
if (e instanceof SQLiteConstraintException) {
|
||||
Logger.error(LOG_TAG, "Constraint exception. Aborting sync.", e);
|
||||
syncResult.stats.numParseExceptions++; // This is as good as we can do.
|
||||
|
|
|
@ -97,6 +97,8 @@ sync/NonObjectJSONException.java
|
|||
sync/NullClusterURLException.java
|
||||
sync/PersistedMetaGlobal.java
|
||||
sync/PrefsSource.java
|
||||
sync/receivers/SyncAccountDeletedReceiver.java
|
||||
sync/receivers/SyncAccountDeletedService.java
|
||||
sync/receivers/UpgradeReceiver.java
|
||||
sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java
|
||||
sync/repositories/android/AndroidBrowserBookmarksRepository.java
|
||||
|
|
|
@ -50,6 +50,16 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="org.mozilla.gecko.sync.receivers.SyncAccountDeletedReceiver"
|
||||
android:permission="@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE">
|
||||
<intent-filter>
|
||||
<!-- This needs to be kept the same as
|
||||
GlobalConstants.SYNC_ACCOUNT_DELETED_ACTION. -->
|
||||
<action android:name="@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.accounts.SYNC_ACCOUNT_DELETED_ACTION"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:theme="@style/SyncTheme"
|
||||
android:icon="@drawable/icon"
|
||||
|
|
|
@ -7,3 +7,13 @@
|
|||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
|
||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
|
||||
<!-- A signature level permission granted only to the Firefox
|
||||
versions sharing an Android Account type. This needs to
|
||||
agree with GlobalConstants.PER_ACCOUNT_TYPE_PERMISSION. -->
|
||||
<permission
|
||||
android:name="@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE"
|
||||
android:protectionLevel="signature">
|
||||
</permission>
|
||||
|
||||
<uses-permission android:name="@MOZ_ANDROID_SHARED_ACCOUNT_TYPE@.permission.PER_ACCOUNT_TYPE" />
|
||||
|
|
|
@ -20,3 +20,7 @@
|
|||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/sync_syncadapter" />
|
||||
</service>
|
||||
<service
|
||||
android:exported="false"
|
||||
android:name="org.mozilla.gecko.sync.receivers.SyncAccountDeletedService" >
|
||||
</service>
|
||||
|
|
Загрузка…
Ссылка в новой задаче