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

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:70ecc6e4d35c
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/. */
4
5 package org.mozilla.gecko.sync.setup.activities;
6
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;
12
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;
33
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;
46
47 public class SendTabActivity extends LocaleAwareActivity {
48 private interface TabSender {
49 static final String[] CLIENTS_STAGE = new String[] { SyncClientsEngineStage.COLLECTION_NAME };
50
51 /**
52 * @return Return null if the account isn't correctly initialized. Return
53 * the account GUID otherwise.
54 */
55 String getAccountGUID();
56
57 /**
58 * Sync this account, specifying only clients as the engine to sync.
59 */
60 void syncClientsStage();
61 }
62
63 private static class FxAccountTabSender implements TabSender {
64 private final AndroidFxAccount fxAccount;
65
66 public FxAccountTabSender(Context context, AndroidFxAccount fxAccount) {
67 this.fxAccount = fxAccount;
68 }
69
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 }
80
81 @Override
82 public void syncClientsStage() {
83 fxAccount.requestSync(FirefoxAccounts.FORCE, CLIENTS_STAGE, null);
84 }
85 }
86
87 private static class Sync11TabSender implements TabSender {
88 private final Account account;
89 private final AccountManager accountManager;
90 private final Context context;
91
92 private Sync11TabSender(Context context, Account syncAccount, AccountManager accountManager) {
93 this.context = context;
94 this.account = syncAccount;
95 this.accountManager = accountManager;
96 }
97
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 }
108
109 @Override
110 public void syncClientsStage() {
111 SyncAdapter.requestImmediateSync(this.account, CLIENTS_STAGE);
112 }
113 }
114
115 public static final String LOG_TAG = "SendTabActivity";
116 private ClientRecordArrayAdapter arrayAdapter;
117
118 private TabSender tabSender;
119 private SendTabData sendTabData;
120
121 @Override
122 public void onCreate(Bundle savedInstanceState) {
123 super.onCreate(savedInstanceState);
124
125 try {
126 sendTabData = getSendTabData(getIntent());
127 } catch (IllegalArgumentException e) {
128 notifyAndFinish(false);
129 return;
130 }
131
132 setContentView(R.layout.sync_send_tab);
133
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);
138
139 arrayAdapter = new ClientRecordArrayAdapter(this, R.layout.sync_list_item);
140 listview.setAdapter(arrayAdapter);
141
142 TextView textView = (TextView) findViewById(R.id.title);
143 textView.setText(sendTabData.title);
144
145 textView = (TextView) findViewById(R.id.uri);
146 textView.setText(sendTabData.uri);
147
148 enableSend(false);
149
150 // will enableSend if appropriate.
151 updateClientList();
152 }
153
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 }
159
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 }
165
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 }
171
172 if (sendTabData.uri == null) {
173 Logger.warn(LOG_TAG, "uri was null; aborting without sending tab.");
174 throw new IllegalArgumentException();
175 }
176
177 if (sendTabData.title == null) {
178 Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway.");
179 }
180
181 return sendTabData;
182 }
183
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>>() {
192
193 @Override
194 protected Collection<ClientRecord> doInBackground(Void... params) {
195 return getOtherClients();
196 }
197
198 @Override
199 protected void onPostExecute(final Collection<ClientRecord> clientArray) {
200 // We're allowed to update the UI from here.
201
202 Logger.debug(LOG_TAG, "Got " + clientArray.size() + " clients.");
203 arrayAdapter.setClientRecordList(clientArray);
204 if (clientArray.size() == 1) {
205 arrayAdapter.checkItem(0, true);
206 }
207
208 enableSend(arrayAdapter.getNumCheckedGUIDs() > 0);
209 }
210 }.execute();
211 }
212
213 @Override
214 public void onResume() {
215 ActivityUtils.prepareLogging();
216 Logger.info(LOG_TAG, "Called SendTabActivity.onResume.");
217 super.onResume();
218
219 /*
220 * First, decide if we are able to send anything.
221 */
222 final Context applicationContext = getApplicationContext();
223 final AccountManager accountManager = AccountManager.get(applicationContext);
224
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 }
236
237 this.tabSender = new FxAccountTabSender(applicationContext, fxAccount);
238
239 Logger.info(LOG_TAG, "Allowing tab send for Firefox Account.");
240 registerDisplayURICommand();
241 return;
242 }
243
244 final Account[] syncAccounts = accountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
245 if (syncAccounts.length > 0) {
246 this.tabSender = new Sync11TabSender(applicationContext, syncAccounts[0], accountManager);
247
248 Logger.info(LOG_TAG, "Allowing tab send for Sync account.");
249 registerDisplayURICommand();
250 return;
251 }
252
253 // Offer to set up a Firefox Account, and finish this activity.
254 redirectToNewTask(FxAccountGetStartedActivity.class, false);
255 }
256
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 }
266
267 public void sendClickHandler(View view) {
268 Logger.info(LOG_TAG, "Send was clicked.");
269 final List<String> remoteClientGuids = arrayAdapter.getCheckedGUIDs();
270
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 }
277
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 }
285
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>() {
289
290 @Override
291 protected Boolean doInBackground(Void... params) {
292 final CommandProcessor processor = CommandProcessor.getProcessor();
293
294 final String accountGUID = sender.getAccountGUID();
295 Logger.debug(LOG_TAG, "Retrieved local account GUID '" + accountGUID + "'.");
296 if (accountGUID == null) {
297 return false;
298 }
299
300 for (String remoteClientGuid : remoteClientGuids) {
301 processor.sendURIToClientForDisplay(sendTabData.uri, remoteClientGuid, sendTabData.title, accountGUID, getApplicationContext());
302 }
303
304 Logger.info(LOG_TAG, "Requesting immediate clients stage sync.");
305 sender.syncClientsStage();
306
307 return true;
308 }
309
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 }
317
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 }
335
336 Toast.makeText(this, textId, Toast.LENGTH_LONG).show();
337 finish();
338 }
339
340 public void enableSend(boolean shouldEnable) {
341 View sendButton = findViewById(R.id.send_button);
342 sendButton.setEnabled(shouldEnable);
343 sendButton.setClickable(shouldEnable);
344 }
345
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 }
360
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 }
369
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 }
374
375 final String ourGUID = this.tabSender.getAccountGUID();
376 if (ourGUID == null) {
377 return all.values();
378 }
379
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 }
389
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