mobile/android/base/sync/setup/activities/SendTabActivity.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.sync.setup.activities;
     7 import java.util.ArrayList;
     8 import java.util.Collection;
     9 import java.util.List;
    10 import java.util.Map;
    11 import java.util.Map.Entry;
    13 import org.mozilla.gecko.R;
    14 import org.mozilla.gecko.background.common.log.Logger;
    15 import org.mozilla.gecko.fxa.FirefoxAccounts;
    16 import org.mozilla.gecko.fxa.FxAccountConstants;
    17 import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
    18 import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
    19 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
    20 import org.mozilla.gecko.fxa.login.State.Action;
    21 import org.mozilla.gecko.sync.CommandProcessor;
    22 import org.mozilla.gecko.sync.CommandRunner;
    23 import org.mozilla.gecko.sync.GlobalSession;
    24 import org.mozilla.gecko.sync.SyncConfiguration;
    25 import org.mozilla.gecko.sync.SyncConstants;
    26 import org.mozilla.gecko.sync.repositories.NullCursorException;
    27 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
    28 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
    29 import org.mozilla.gecko.sync.setup.SyncAccounts;
    30 import org.mozilla.gecko.sync.setup.activities.LocaleAware.LocaleAwareActivity;
    31 import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
    32 import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
    34 import android.accounts.Account;
    35 import android.accounts.AccountManager;
    36 import android.app.Activity;
    37 import android.content.Context;
    38 import android.content.Intent;
    39 import android.content.SharedPreferences;
    40 import android.os.AsyncTask;
    41 import android.os.Bundle;
    42 import android.view.View;
    43 import android.widget.ListView;
    44 import android.widget.TextView;
    45 import android.widget.Toast;
    47 public class SendTabActivity extends LocaleAwareActivity {
    48   private interface TabSender {
    49     static final String[] CLIENTS_STAGE = new String[] { SyncClientsEngineStage.COLLECTION_NAME };
    51     /**
    52      * @return Return null if the account isn't correctly initialized. Return
    53      *         the account GUID otherwise.
    54      */
    55     String getAccountGUID();
    57     /**
    58      * Sync this account, specifying only clients as the engine to sync.
    59      */
    60     void syncClientsStage();
    61   }
    63   private static class FxAccountTabSender implements TabSender {
    64     private final AndroidFxAccount fxAccount;
    66     public FxAccountTabSender(Context context, AndroidFxAccount fxAccount) {
    67       this.fxAccount = fxAccount;
    68     }
    70     @Override
    71     public String getAccountGUID() {
    72       try {
    73         final SharedPreferences prefs = this.fxAccount.getSyncPrefs();
    74         return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
    75       } catch (Exception e) {
    76         Logger.warn(LOG_TAG, "Could not get Firefox Account parameters or preferences; aborting.");
    77         return null;
    78       }
    79     }
    81     @Override
    82     public void syncClientsStage() {
    83       fxAccount.requestSync(FirefoxAccounts.FORCE, CLIENTS_STAGE, null);
    84     }
    85   }
    87   private static class Sync11TabSender implements TabSender {
    88     private final Account account;
    89     private final AccountManager accountManager;
    90     private final Context context;
    92     private Sync11TabSender(Context context, Account syncAccount, AccountManager accountManager) {
    93       this.context = context;
    94       this.account = syncAccount;
    95       this.accountManager = accountManager;
    96     }
    98     @Override
    99     public String getAccountGUID() {
   100       try {
   101         final SharedPreferences prefs = SyncAccounts.blockingPrefsFromDefaultProfileV0(this.context, this.accountManager, this.account);
   102         return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
   103       } catch (Exception e) {
   104         Logger.warn(LOG_TAG, "Could not get Sync account parameters or preferences; aborting.");
   105         return null;
   106       }
   107     }
   109     @Override
   110     public void syncClientsStage() {
   111       SyncAdapter.requestImmediateSync(this.account, CLIENTS_STAGE);
   112     }
   113   }
   115   public static final String LOG_TAG = "SendTabActivity";
   116   private ClientRecordArrayAdapter arrayAdapter;
   118   private TabSender tabSender;
   119   private SendTabData sendTabData;
   121   @Override
   122   public void onCreate(Bundle savedInstanceState) {
   123     super.onCreate(savedInstanceState);
   125     try {
   126       sendTabData = getSendTabData(getIntent());
   127     } catch (IllegalArgumentException e) {
   128       notifyAndFinish(false);
   129       return;
   130     }
   132     setContentView(R.layout.sync_send_tab);
   134     final ListView listview = (ListView) findViewById(R.id.device_list);
   135     listview.setItemsCanFocus(true);
   136     listview.setTextFilterEnabled(true);
   137     listview.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
   139     arrayAdapter = new ClientRecordArrayAdapter(this, R.layout.sync_list_item);
   140     listview.setAdapter(arrayAdapter);
   142     TextView textView = (TextView) findViewById(R.id.title);
   143     textView.setText(sendTabData.title);
   145     textView = (TextView) findViewById(R.id.uri);
   146     textView.setText(sendTabData.uri);
   148     enableSend(false);
   150     // will enableSend if appropriate.
   151     updateClientList();
   152   }
   154   protected static SendTabData getSendTabData(Intent intent) throws IllegalArgumentException {
   155     if (intent == null) {
   156       Logger.warn(LOG_TAG, "intent was null; aborting without sending tab.");
   157       throw new IllegalArgumentException();
   158     }
   160     Bundle extras = intent.getExtras();
   161     if (extras == null) {
   162       Logger.warn(LOG_TAG, "extras was null; aborting without sending tab.");
   163       throw new IllegalArgumentException();
   164     }
   166     SendTabData sendTabData = SendTabData.fromBundle(extras);
   167     if (sendTabData == null) {
   168       Logger.warn(LOG_TAG, "send tab data was null; aborting without sending tab.");
   169       throw new IllegalArgumentException();
   170     }
   172     if (sendTabData.uri == null) {
   173       Logger.warn(LOG_TAG, "uri was null; aborting without sending tab.");
   174       throw new IllegalArgumentException();
   175     }
   177     if (sendTabData.title == null) {
   178       Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway.");
   179     }
   181     return sendTabData;
   182   }
   184   /**
   185    * Ensure that the view's list of clients is backed by a recently populated
   186    * array adapter.
   187    */
   188   protected synchronized void updateClientList() {
   189     // Fetching the client list hits the clients database, so we spin this onto
   190     // a background task.
   191     new AsyncTask<Void, Void, Collection<ClientRecord>>() {
   193       @Override
   194       protected Collection<ClientRecord> doInBackground(Void... params) {
   195         return getOtherClients();
   196       }
   198       @Override
   199       protected void onPostExecute(final Collection<ClientRecord> clientArray) {
   200         // We're allowed to update the UI from here.
   202         Logger.debug(LOG_TAG, "Got " + clientArray.size() + " clients.");
   203         arrayAdapter.setClientRecordList(clientArray);
   204         if (clientArray.size() == 1) {
   205           arrayAdapter.checkItem(0, true);
   206         }
   208         enableSend(arrayAdapter.getNumCheckedGUIDs() > 0);
   209       }
   210     }.execute();
   211   }
   213   @Override
   214   public void onResume() {
   215     ActivityUtils.prepareLogging();
   216     Logger.info(LOG_TAG, "Called SendTabActivity.onResume.");
   217     super.onResume();
   219     /*
   220      * First, decide if we are able to send anything.
   221      */
   222     final Context applicationContext = getApplicationContext();
   223     final AccountManager accountManager = AccountManager.get(applicationContext);
   225     final Account[] fxAccounts = accountManager.getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
   226     if (fxAccounts.length > 0) {
   227       final AndroidFxAccount fxAccount = new AndroidFxAccount(applicationContext, fxAccounts[0]);
   228       if (fxAccount.getState().getNeededAction() != Action.None) {
   229         // We have a Firefox Account, but it's definitely not able to send a tab
   230         // right now. Redirect to the status activity.
   231         Logger.warn(LOG_TAG, "Firefox Account named like " + fxAccount.getObfuscatedEmail() +
   232             " needs action before it can send a tab; redirecting to status activity.");
   233         redirectToNewTask(FxAccountStatusActivity.class, false);
   234         return;
   235       }
   237       this.tabSender = new FxAccountTabSender(applicationContext, fxAccount);
   239       Logger.info(LOG_TAG, "Allowing tab send for Firefox Account.");
   240       registerDisplayURICommand();
   241       return;
   242     }
   244     final Account[] syncAccounts = accountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
   245     if (syncAccounts.length > 0) {
   246       this.tabSender = new Sync11TabSender(applicationContext, syncAccounts[0], accountManager);
   248       Logger.info(LOG_TAG, "Allowing tab send for Sync account.");
   249       registerDisplayURICommand();
   250       return;
   251     }
   253     // Offer to set up a Firefox Account, and finish this activity.
   254     redirectToNewTask(FxAccountGetStartedActivity.class, false);
   255   }
   257   private static void registerDisplayURICommand() {
   258     final CommandProcessor processor = CommandProcessor.getProcessor();
   259     processor.registerCommand("displayURI", new CommandRunner(3) {
   260       @Override
   261       public void executeCommand(final GlobalSession session, List<String> args) {
   262         CommandProcessor.displayURI(args, session.getContext());
   263       }
   264     });
   265   }
   267   public void sendClickHandler(View view) {
   268     Logger.info(LOG_TAG, "Send was clicked.");
   269     final List<String> remoteClientGuids = arrayAdapter.getCheckedGUIDs();
   271     if (remoteClientGuids == null) {
   272       // Should never happen.
   273       Logger.warn(LOG_TAG, "guids was null; aborting without sending tab.");
   274       notifyAndFinish(false);
   275       return;
   276     }
   278     final TabSender sender = this.tabSender;
   279     if (sender == null) {
   280       // This should never happen.
   281       Logger.warn(LOG_TAG, "tabSender was null; aborting without sending tab.");
   282       notifyAndFinish(false);
   283       return;
   284     }
   286     // Fetching local client GUID hits the DB, and we want to update the UI
   287     // afterward, so we perform the tab sending on another thread.
   288     new AsyncTask<Void, Void, Boolean>() {
   290       @Override
   291       protected Boolean doInBackground(Void... params) {
   292         final CommandProcessor processor = CommandProcessor.getProcessor();
   294         final String accountGUID = sender.getAccountGUID();
   295         Logger.debug(LOG_TAG, "Retrieved local account GUID '" + accountGUID + "'.");
   296         if (accountGUID == null) {
   297           return false;
   298         }
   300         for (String remoteClientGuid : remoteClientGuids) {
   301           processor.sendURIToClientForDisplay(sendTabData.uri, remoteClientGuid, sendTabData.title, accountGUID, getApplicationContext());
   302         }
   304         Logger.info(LOG_TAG, "Requesting immediate clients stage sync.");
   305         sender.syncClientsStage();
   307         return true;
   308       }
   310       @Override
   311       protected void onPostExecute(final Boolean success) {
   312         // We're allowed to update the UI from here.
   313         notifyAndFinish(success.booleanValue());
   314       }
   315     }.execute();
   316   }
   318   /**
   319    * Notify the user about sent tabs status and then finish the activity.
   320    * <p>
   321    * "Success" is a bit of a misnomer: we wrote "displayURI" commands to the local
   322    * command database, and they will be sent on next sync. There is no way to
   323    * verify that the commands were successfully received by the intended remote
   324    * client, so we lie and say they were sent.
   325    *
   326    * @param success true if tab was sent successfully; false otherwise.
   327    */
   328   protected void notifyAndFinish(final boolean success) {
   329     int textId;
   330     if (success) {
   331       textId = R.string.sync_text_tab_sent;
   332     } else {
   333       textId = R.string.sync_text_tab_not_sent;
   334     }
   336     Toast.makeText(this, textId, Toast.LENGTH_LONG).show();
   337     finish();
   338   }
   340   public void enableSend(boolean shouldEnable) {
   341     View sendButton = findViewById(R.id.send_button);
   342     sendButton.setEnabled(shouldEnable);
   343     sendButton.setClickable(shouldEnable);
   344   }
   346   /**
   347    * @return a map from GUID to client record, including our own.
   348    */
   349   protected Map<String, ClientRecord> getAllClients() {
   350     ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(this.getApplicationContext());
   351     try {
   352       return db.fetchAllClients();
   353     } catch (NullCursorException e) {
   354       Logger.warn(LOG_TAG, "NullCursorException while populating device list.", e);
   355       return null;
   356     } finally {
   357       db.close();
   358     }
   359   }
   361   /**
   362    * @return a collection of client records, excluding our own.
   363    */
   364   protected Collection<ClientRecord> getOtherClients() {
   365     final Map<String, ClientRecord> all = getAllClients();
   366     if (all == null) {
   367       return new ArrayList<ClientRecord>(0);
   368     }
   370     if (this.tabSender == null) {
   371       Logger.warn(LOG_TAG, "No tab sender when fetching other client IDs.");
   372       return new ArrayList<ClientRecord>(0);
   373     }
   375     final String ourGUID = this.tabSender.getAccountGUID();
   376     if (ourGUID == null) {
   377       return all.values();
   378     }
   380     final ArrayList<ClientRecord> out = new ArrayList<ClientRecord>(all.size());
   381     for (Entry<String, ClientRecord> entry : all.entrySet()) {
   382       if (ourGUID.equals(entry.getKey())) {
   383         continue;
   384       }
   385       out.add(entry.getValue());
   386     }
   387     return out;
   388   }
   390   // Adapted from FxAccountAbstractActivity.
   391   protected void redirectToNewTask(Class<? extends Activity> activityClass, boolean success) {
   392     Intent intent = new Intent(this, activityClass);
   393     // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
   394     // the soft keyboard not being shown for the started activity. Why, Android, why?
   395     intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
   396     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   397     startActivity(intent);
   398     notifyAndFinish(success);
   399   }
   400 }

mercurial