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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/sync/setup/activities/SetupSyncActivity.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,623 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.sync.setup.activities;
     1.9 +
    1.10 +import java.io.UnsupportedEncodingException;
    1.11 +import java.util.HashMap;
    1.12 +
    1.13 +import org.json.simple.JSONObject;
    1.14 +import org.mozilla.gecko.R;
    1.15 +import org.mozilla.gecko.background.common.log.Logger;
    1.16 +import org.mozilla.gecko.sync.SyncConstants;
    1.17 +import org.mozilla.gecko.sync.ThreadPool;
    1.18 +import org.mozilla.gecko.sync.Utils;
    1.19 +import org.mozilla.gecko.sync.jpake.JPakeClient;
    1.20 +import org.mozilla.gecko.sync.jpake.JPakeNoActivePairingException;
    1.21 +import org.mozilla.gecko.sync.setup.Constants;
    1.22 +import org.mozilla.gecko.sync.setup.SyncAccounts;
    1.23 +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
    1.24 +
    1.25 +import android.accounts.Account;
    1.26 +import android.accounts.AccountAuthenticatorActivity;
    1.27 +import android.accounts.AccountManager;
    1.28 +import android.app.Activity;
    1.29 +import android.content.Context;
    1.30 +import android.content.Intent;
    1.31 +import android.net.ConnectivityManager;
    1.32 +import android.net.NetworkInfo;
    1.33 +import android.net.Uri;
    1.34 +import android.os.Bundle;
    1.35 +import android.text.Editable;
    1.36 +import android.text.TextWatcher;
    1.37 +import android.view.View;
    1.38 +import android.view.Window;
    1.39 +import android.view.WindowManager;
    1.40 +import android.widget.Button;
    1.41 +import android.widget.EditText;
    1.42 +import android.widget.LinearLayout;
    1.43 +import android.widget.TextView;
    1.44 +import android.widget.Toast;
    1.45 +
    1.46 +public class SetupSyncActivity extends AccountAuthenticatorActivity {
    1.47 +  private final static String LOG_TAG = "SetupSync";
    1.48 +
    1.49 +  private boolean pairWithPin = false;
    1.50 +
    1.51 +  // UI elements for pairing through PIN entry.
    1.52 +  private EditText            row1;
    1.53 +  private EditText            row2;
    1.54 +  private EditText            row3;
    1.55 +  private Button              connectButton;
    1.56 +  private LinearLayout        pinError;
    1.57 +
    1.58 +  // UI elements for pairing through PIN generation.
    1.59 +  private TextView            pinTextView1;
    1.60 +  private TextView            pinTextView2;
    1.61 +  private TextView            pinTextView3;
    1.62 +  private JPakeClient         jClient;
    1.63 +
    1.64 +  // Android context.
    1.65 +  private AccountManager      mAccountManager;
    1.66 +  private Context             mContext;
    1.67 +
    1.68 +  public SetupSyncActivity() {
    1.69 +    super();
    1.70 +  }
    1.71 +
    1.72 +  /** Called when the activity is first created. */
    1.73 +  @Override
    1.74 +  public void onCreate(Bundle savedInstanceState) {
    1.75 +    ActivityUtils.prepareLogging();
    1.76 +    Logger.info(LOG_TAG, "Called SetupSyncActivity.onCreate.");
    1.77 +    super.onCreate(savedInstanceState);
    1.78 +
    1.79 +    // Set Activity variables.
    1.80 +    mContext = getApplicationContext();
    1.81 +    Logger.debug(LOG_TAG, "AccountManager.get(" + mContext + ")");
    1.82 +    mAccountManager = AccountManager.get(mContext);
    1.83 +
    1.84 +    // Set "screen on" flag for this activity. Screen will not automatically dim as long as this
    1.85 +    // activity is at the top of the stack.
    1.86 +    // Attempting to set this flag more than once causes hanging, so we set it here, not in onResume().
    1.87 +    Window w = getWindow();
    1.88 +    w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    1.89 +    Logger.debug(LOG_TAG, "Successfully set screen-on flag.");
    1.90 +  }
    1.91 +
    1.92 +  @Override
    1.93 +  public void onResume() {
    1.94 +    ActivityUtils.prepareLogging();
    1.95 +    Logger.info(LOG_TAG, "Called SetupSyncActivity.onResume.");
    1.96 +    super.onResume();
    1.97 +
    1.98 +    if (!hasInternet()) {
    1.99 +      runOnUiThread(new Runnable() {
   1.100 +        @Override
   1.101 +        public void run() {
   1.102 +          setContentView(R.layout.sync_setup_nointernet);
   1.103 +        }
   1.104 +      });
   1.105 +      return;
   1.106 +    }
   1.107 +
   1.108 +    // Check whether Sync accounts exist; if not, display J-PAKE PIN.
   1.109 +    // Run this on a separate thread to comply with Strict Mode thread policies.
   1.110 +    ThreadPool.run(new Runnable() {
   1.111 +      @Override
   1.112 +      public void run() {
   1.113 +        ActivityUtils.prepareLogging();
   1.114 +        Account[] accts = mAccountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
   1.115 +        finishResume(accts);
   1.116 +      }
   1.117 +    });
   1.118 +  }
   1.119 +
   1.120 +  public void finishResume(Account[] accts) {
   1.121 +    Logger.debug(LOG_TAG, "Finishing Resume after fetching accounts.");
   1.122 +
   1.123 +    if (accts.length == 0) { // Start J-PAKE for pairing if no accounts present.
   1.124 +      Logger.debug(LOG_TAG, "No accounts; starting J-PAKE receiver.");
   1.125 +      displayReceiveNoPin();
   1.126 +      if (jClient != null) {
   1.127 +        // Mark previous J-PAKE as finished. Don't bother propagating back up to this Activity.
   1.128 +        jClient.finished = true;
   1.129 +      }
   1.130 +      jClient = new JPakeClient(this);
   1.131 +      jClient.receiveNoPin();
   1.132 +      return;
   1.133 +    }
   1.134 +
   1.135 +    // Set layout based on starting Intent.
   1.136 +    Bundle extras = this.getIntent().getExtras();
   1.137 +    if (extras != null) {
   1.138 +      Logger.debug(LOG_TAG, "SetupSync with extras.");
   1.139 +      boolean isSetup = extras.getBoolean(Constants.INTENT_EXTRA_IS_SETUP);
   1.140 +      if (!isSetup) {
   1.141 +        Logger.debug(LOG_TAG, "Account exists; Pair a Device started.");
   1.142 +        pairWithPin = true;
   1.143 +        displayPairWithPin();
   1.144 +        return;
   1.145 +      }
   1.146 +    }
   1.147 +
   1.148 +    runOnUiThread(new Runnable() {
   1.149 +      @Override
   1.150 +      public void run() {
   1.151 +        Logger.debug(LOG_TAG, "Only one account supported. Redirecting.");
   1.152 +        // Display toast for "Only one account supported."
   1.153 +        // Redirect to account management.
   1.154 +        Toast toast = Toast.makeText(mContext,
   1.155 +            R.string.sync_notification_oneaccount, Toast.LENGTH_LONG);
   1.156 +        toast.show();
   1.157 +
   1.158 +        // Setting up Sync when an existing account exists only happens from Settings,
   1.159 +        // so we can safely finish() the activity to return to Settings.
   1.160 +        finish();
   1.161 +      }
   1.162 +    });
   1.163 +  }
   1.164 +
   1.165 +
   1.166 +  @Override
   1.167 +  public void onPause() {
   1.168 +    super.onPause();
   1.169 +
   1.170 +    if (jClient != null) {
   1.171 +      jClient.abort(Constants.JPAKE_ERROR_USERABORT);
   1.172 +    }
   1.173 +    if (pairWithPin) {
   1.174 +      finish();
   1.175 +    }
   1.176 +  }
   1.177 +
   1.178 +  @Override
   1.179 +  public void onNewIntent(Intent intent) {
   1.180 +    Logger.debug(LOG_TAG, "Started SetupSyncActivity with new intent.");
   1.181 +    setIntent(intent);
   1.182 +  }
   1.183 +
   1.184 +  @Override
   1.185 +  public void onDestroy() {
   1.186 +    Logger.debug(LOG_TAG, "onDestroy() called.");
   1.187 +    super.onDestroy();
   1.188 +  }
   1.189 +
   1.190 +  /* Click Handlers */
   1.191 +  public void manualClickHandler(View target) {
   1.192 +    Intent accountIntent = new Intent(this, AccountActivity.class);
   1.193 +    accountIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
   1.194 +    startActivityForResult(accountIntent, 0);
   1.195 +    overridePendingTransition(0, 0);
   1.196 +  }
   1.197 +
   1.198 +  public void cancelClickHandler(View target) {
   1.199 +    finish();
   1.200 +  }
   1.201 +
   1.202 +  public void connectClickHandler(View target) {
   1.203 +    Logger.debug(LOG_TAG, "Connect clicked.");
   1.204 +    // Set UI feedback.
   1.205 +    pinError.setVisibility(View.INVISIBLE);
   1.206 +    enablePinEntry(false);
   1.207 +    connectButton.requestFocus();
   1.208 +    activateButton(connectButton, false);
   1.209 +
   1.210 +    // Extract PIN.
   1.211 +    String pin = row1.getText().toString();
   1.212 +    pin += row2.getText().toString() + row3.getText().toString();
   1.213 +
   1.214 +    // Start J-PAKE.
   1.215 +    if (jClient != null) {
   1.216 +      // Cancel previous J-PAKE exchange.
   1.217 +      jClient.finished = true;
   1.218 +    }
   1.219 +    jClient = new JPakeClient(this);
   1.220 +    jClient.pairWithPin(pin);
   1.221 +  }
   1.222 +
   1.223 +  /**
   1.224 +   * Handler when "Show me how" link is clicked.
   1.225 +   * @param target
   1.226 +   *          View that received the click.
   1.227 +   */
   1.228 +  public void showClickHandler(View target) {
   1.229 +    Uri uri = null;
   1.230 +    // TODO: fetch these from fennec
   1.231 +    if (pairWithPin) {
   1.232 +      uri = Uri.parse(Constants.LINK_FIND_CODE);
   1.233 +    } else {
   1.234 +      uri = Uri.parse(Constants.LINK_FIND_ADD_DEVICE);
   1.235 +    }
   1.236 +    Intent intent = new Intent(this, WebViewActivity.class);
   1.237 +    intent.setData(uri);
   1.238 +    startActivity(intent);
   1.239 +  }
   1.240 +
   1.241 +  /* Controller methods */
   1.242 +
   1.243 +  /**
   1.244 +   * Display generated PIN to user.
   1.245 +   * @param pin
   1.246 +   *          12-character string generated for J-PAKE.
   1.247 +   */
   1.248 +  public void displayPin(String pin) {
   1.249 +    if (pin == null) {
   1.250 +      Logger.warn(LOG_TAG, "Asked to display null pin.");
   1.251 +      return;
   1.252 +    }
   1.253 +    // Format PIN for display.
   1.254 +    int charPerLine = pin.length() / 3;
   1.255 +    final String pin1 = pin.substring(0, charPerLine);
   1.256 +    final String pin2 = pin.substring(charPerLine, 2 * charPerLine);
   1.257 +    final String pin3 = pin.substring(2 * charPerLine, pin.length());
   1.258 +
   1.259 +    runOnUiThread(new Runnable() {
   1.260 +      @Override
   1.261 +      public void run() {
   1.262 +        TextView view1 = pinTextView1;
   1.263 +        TextView view2 = pinTextView2;
   1.264 +        TextView view3 = pinTextView3;
   1.265 +        if (view1 == null || view2 == null || view3 == null) {
   1.266 +          Logger.warn(LOG_TAG, "Couldn't find view to display PIN.");
   1.267 +          return;
   1.268 +        }
   1.269 +        view1.setText(pin1);
   1.270 +        view1.setContentDescription(pin1.replaceAll("\\B", ", "));
   1.271 +
   1.272 +        view2.setText(pin2);
   1.273 +        view2.setContentDescription(pin2.replaceAll("\\B", ", "));
   1.274 +
   1.275 +        view3.setText(pin3);
   1.276 +        view3.setContentDescription(pin3.replaceAll("\\B", ", "));
   1.277 +      }
   1.278 +    });
   1.279 +  }
   1.280 +
   1.281 +  /**
   1.282 +   * Abort current J-PAKE pairing. Clear forms/restart pairing.
   1.283 +   * @param error
   1.284 +   */
   1.285 +  public void displayAbort(String error) {
   1.286 +    if (!Constants.JPAKE_ERROR_USERABORT.equals(error) && !hasInternet()) {
   1.287 +      runOnUiThread(new Runnable() {
   1.288 +        @Override
   1.289 +        public void run() {
   1.290 +          setContentView(R.layout.sync_setup_nointernet);
   1.291 +        }
   1.292 +      });
   1.293 +      return;
   1.294 +    }
   1.295 +    if (pairWithPin) {
   1.296 +      // Clear PIN entries and display error.
   1.297 +      runOnUiThread(new Runnable() {
   1.298 +        @Override
   1.299 +        public void run() {
   1.300 +          enablePinEntry(true);
   1.301 +          row1.setText("");
   1.302 +          row2.setText("");
   1.303 +          row3.setText("");
   1.304 +          row1.requestFocus();
   1.305 +
   1.306 +          // Display error.
   1.307 +          pinError.setVisibility(View.VISIBLE);
   1.308 +        }
   1.309 +      });
   1.310 +      return;
   1.311 +    }
   1.312 +
   1.313 +    // Start new JPakeClient for restarting J-PAKE.
   1.314 +    Logger.debug(LOG_TAG, "abort reason: " + error);
   1.315 +    if (!Constants.JPAKE_ERROR_USERABORT.equals(error)) {
   1.316 +      jClient = new JPakeClient(this);
   1.317 +      runOnUiThread(new Runnable() {
   1.318 +        @Override
   1.319 +        public void run() {
   1.320 +          displayReceiveNoPin();
   1.321 +          jClient.receiveNoPin();
   1.322 +        }
   1.323 +      });
   1.324 +    }
   1.325 +  }
   1.326 +
   1.327 +  @SuppressWarnings({ "unchecked", "static-method" })
   1.328 +  protected JSONObject makeAccountJSON(String username, String password,
   1.329 +                                       String syncKey, String serverURL) {
   1.330 +
   1.331 +    JSONObject jAccount = new JSONObject();
   1.332 +
   1.333 +    // Hack to try to keep Java 1.7 from complaining about unchecked types,
   1.334 +    // despite the presence of SuppressWarnings.
   1.335 +    HashMap<String, String> fields = (HashMap<String, String>) jAccount;
   1.336 +
   1.337 +    fields.put(Constants.JSON_KEY_SYNCKEY,  syncKey);
   1.338 +    fields.put(Constants.JSON_KEY_ACCOUNT,  username);
   1.339 +    fields.put(Constants.JSON_KEY_PASSWORD, password);
   1.340 +    fields.put(Constants.JSON_KEY_SERVER,   serverURL);
   1.341 +
   1.342 +    if (Logger.LOG_PERSONAL_INFORMATION) {
   1.343 +      Logger.pii(LOG_TAG, "Extracted account data: " + jAccount.toJSONString());
   1.344 +    }
   1.345 +    return jAccount;
   1.346 +  }
   1.347 +
   1.348 +  /**
   1.349 +   * Device has finished key exchange, waiting for remote device to set up or
   1.350 +   * link to a Sync account. Display "waiting for other device" dialog.
   1.351 +   */
   1.352 +  public void onPaired() {
   1.353 +    // Extract Sync account data.
   1.354 +    Account[] accts = mAccountManager.getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
   1.355 +    if (accts.length == 0) {
   1.356 +      // Error, no account present.
   1.357 +      Logger.error(LOG_TAG, "No accounts present.");
   1.358 +      displayAbort(Constants.JPAKE_ERROR_INVALID);
   1.359 +      return;
   1.360 +    }
   1.361 +
   1.362 +    // TODO: Single account supported. Create account selection if spec changes.
   1.363 +    Account account = accts[0];
   1.364 +    String username  = account.name;
   1.365 +    String password  = mAccountManager.getPassword(account);
   1.366 +    String syncKey   = mAccountManager.getUserData(account, Constants.OPTION_SYNCKEY);
   1.367 +    String serverURL = mAccountManager.getUserData(account, Constants.OPTION_SERVER);
   1.368 +
   1.369 +    JSONObject jAccount = makeAccountJSON(username, password, syncKey, serverURL);
   1.370 +    try {
   1.371 +      jClient.sendAndComplete(jAccount);
   1.372 +    } catch (JPakeNoActivePairingException e) {
   1.373 +      Logger.error(LOG_TAG, "No active J-PAKE pairing.", e);
   1.374 +      displayAbort(Constants.JPAKE_ERROR_INVALID);
   1.375 +    }
   1.376 +  }
   1.377 +
   1.378 +  /**
   1.379 +   * J-PAKE pairing has started, but when this device has generated the PIN for
   1.380 +   * pairing, does not require UI feedback to user.
   1.381 +   */
   1.382 +  public void onPairingStart() {
   1.383 +    if (!pairWithPin) {
   1.384 +      runOnUiThread(new Runnable() {
   1.385 +        @Override
   1.386 +        public void run() {
   1.387 +          setContentView(R.layout.sync_setup_jpake_waiting);
   1.388 +        }
   1.389 +      });
   1.390 +      return;
   1.391 +    }
   1.392 +  }
   1.393 +
   1.394 +  /**
   1.395 +   * On J-PAKE completion, store the Sync Account credentials sent by other
   1.396 +   * device. Display progress to user.
   1.397 +   *
   1.398 +   * @param jCreds
   1.399 +   */
   1.400 +  public void onComplete(JSONObject jCreds) {
   1.401 +    if (!pairWithPin) {
   1.402 +      // Create account from received credentials.
   1.403 +      String accountName  = (String) jCreds.get(Constants.JSON_KEY_ACCOUNT);
   1.404 +      String password     = (String) jCreds.get(Constants.JSON_KEY_PASSWORD);
   1.405 +      String syncKey      = (String) jCreds.get(Constants.JSON_KEY_SYNCKEY);
   1.406 +      String serverURL    = (String) jCreds.get(Constants.JSON_KEY_SERVER);
   1.407 +
   1.408 +      // The password we get is double-encoded.
   1.409 +      try {
   1.410 +        password = Utils.decodeUTF8(password);
   1.411 +      } catch (UnsupportedEncodingException e) {
   1.412 +        Logger.warn(LOG_TAG, "Unsupported encoding when decoding UTF-8 ASCII J-PAKE message. Ignoring.");
   1.413 +      }
   1.414 +
   1.415 +      final SyncAccountParameters syncAccount = new SyncAccountParameters(mContext, mAccountManager, accountName,
   1.416 +                                                                          syncKey, password, serverURL);
   1.417 +      createAccountOnThread(syncAccount);
   1.418 +    } else {
   1.419 +      // No need to create an account; just clean up.
   1.420 +      displayResultAndFinish(true);
   1.421 +    }
   1.422 +  }
   1.423 +
   1.424 +  private void displayResultAndFinish(final boolean isSuccess) {
   1.425 +    jClient = null;
   1.426 +    runOnUiThread(new Runnable() {
   1.427 +      @Override
   1.428 +      public void run() {
   1.429 +        int result = isSuccess ? RESULT_OK : RESULT_CANCELED;
   1.430 +        setResult(result);
   1.431 +        displayResult(isSuccess);
   1.432 +      }
   1.433 +    });
   1.434 +  }
   1.435 +
   1.436 +  private void createAccountOnThread(final SyncAccountParameters syncAccount) {
   1.437 +    ThreadPool.run(new Runnable() {
   1.438 +      @Override
   1.439 +      public void run() {
   1.440 +        Account account = SyncAccounts.createSyncAccount(syncAccount);
   1.441 +        boolean isSuccess = (account != null);
   1.442 +        if (isSuccess) {
   1.443 +          Bundle resultBundle = new Bundle();
   1.444 +          resultBundle.putString(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username);
   1.445 +          resultBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, SyncConstants.ACCOUNTTYPE_SYNC);
   1.446 +          resultBundle.putString(AccountManager.KEY_AUTHTOKEN, SyncConstants.ACCOUNTTYPE_SYNC);
   1.447 +          setAccountAuthenticatorResult(resultBundle);
   1.448 +        }
   1.449 +        displayResultAndFinish(isSuccess);
   1.450 +      }
   1.451 +    });
   1.452 +  }
   1.453 +
   1.454 +  /*
   1.455 +   * Helper functions
   1.456 +   */
   1.457 +  private void activateButton(Button button, boolean toActivate) {
   1.458 +    button.setEnabled(toActivate);
   1.459 +    button.setClickable(toActivate);
   1.460 +  }
   1.461 +
   1.462 +  private void enablePinEntry(boolean toEnable) {
   1.463 +    row1.setEnabled(toEnable);
   1.464 +    row2.setEnabled(toEnable);
   1.465 +    row3.setEnabled(toEnable);
   1.466 +  }
   1.467 +
   1.468 +  /**
   1.469 +   * Displays Sync account setup result to user.
   1.470 +   *
   1.471 +   * @param isSetup
   1.472 +   *          true if account was set up successfully, false otherwise.
   1.473 +   */
   1.474 +  private void displayResult(boolean isSuccess) {
   1.475 +    Intent intent = null;
   1.476 +    if (isSuccess) {
   1.477 +      intent = new Intent(mContext, SetupSuccessActivity.class);
   1.478 +      intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
   1.479 +      intent.putExtra(Constants.INTENT_EXTRA_IS_SETUP, !pairWithPin);
   1.480 +      startActivity(intent);
   1.481 +      finish();
   1.482 +    } else {
   1.483 +      intent = new Intent(mContext, SetupFailureActivity.class);
   1.484 +      intent.putExtra(Constants.INTENT_EXTRA_IS_ACCOUNTERROR, true);
   1.485 +      intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
   1.486 +      intent.putExtra(Constants.INTENT_EXTRA_IS_SETUP, !pairWithPin);
   1.487 +      startActivity(intent);
   1.488 +      // Do not finish, so user can retry setup by hitting "back."
   1.489 +    }
   1.490 +  }
   1.491 +
   1.492 +  /**
   1.493 +   * Validate PIN entry fields to check if the three PIN entry fields are all
   1.494 +   * filled in.
   1.495 +   *
   1.496 +   * @return true, if all PIN fields have 4 characters, false otherwise
   1.497 +   */
   1.498 +  private boolean pinEntryCompleted() {
   1.499 +    if (row1.length() == 4 &&
   1.500 +        row2.length() == 4 &&
   1.501 +        row3.length() == 4) {
   1.502 +      return true;
   1.503 +    }
   1.504 +    return false;
   1.505 +  }
   1.506 +
   1.507 +  private boolean hasInternet() {
   1.508 +    Logger.debug(LOG_TAG, "Checking internet connectivity.");
   1.509 +    ConnectivityManager connManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
   1.510 +    NetworkInfo network = connManager.getActiveNetworkInfo();
   1.511 +
   1.512 +    if (network != null && network.isConnected()) {
   1.513 +      Logger.debug(LOG_TAG, network + " is connected.");
   1.514 +      return true;
   1.515 +    }
   1.516 +    Logger.debug(LOG_TAG, "No connected networks.");
   1.517 +    return false;
   1.518 +  }
   1.519 +
   1.520 +  /**
   1.521 +   * Displays layout for entering a PIN from another device.
   1.522 +   * A Sync Account has already been set up.
   1.523 +   */
   1.524 +  private void displayPairWithPin() {
   1.525 +    Logger.debug(LOG_TAG, "PairWithPin initiated.");
   1.526 +    runOnUiThread(new Runnable() {
   1.527 +
   1.528 +      @Override
   1.529 +      public void run() {
   1.530 +        setContentView(R.layout.sync_setup_pair);
   1.531 +        connectButton = (Button) findViewById(R.id.pair_button_connect);
   1.532 +        pinError = (LinearLayout) findViewById(R.id.pair_error);
   1.533 +
   1.534 +        row1 = (EditText) findViewById(R.id.pair_row1);
   1.535 +        row2 = (EditText) findViewById(R.id.pair_row2);
   1.536 +        row3 = (EditText) findViewById(R.id.pair_row3);
   1.537 +
   1.538 +        row1.addTextChangedListener(new TextWatcher() {
   1.539 +          @Override
   1.540 +          public void afterTextChanged(Editable s) {
   1.541 +             activateButton(connectButton, pinEntryCompleted());
   1.542 +             if (s.length() == 4) {
   1.543 +               row2.requestFocus();
   1.544 +             }
   1.545 +          }
   1.546 +
   1.547 +          @Override
   1.548 +          public void beforeTextChanged(CharSequence s, int start, int count,
   1.549 +              int after) {
   1.550 +          }
   1.551 +
   1.552 +          @Override
   1.553 +          public void onTextChanged(CharSequence s, int start, int before, int count) {
   1.554 +          }
   1.555 +
   1.556 +        });
   1.557 +        row2.addTextChangedListener(new TextWatcher() {
   1.558 +          @Override
   1.559 +          public void afterTextChanged(Editable s) {
   1.560 +            activateButton(connectButton, pinEntryCompleted());
   1.561 +            if (s.length() == 4) {
   1.562 +              row3.requestFocus();
   1.563 +            }
   1.564 +          }
   1.565 +
   1.566 +          @Override
   1.567 +          public void beforeTextChanged(CharSequence s, int start, int count,
   1.568 +              int after) {
   1.569 +          }
   1.570 +
   1.571 +          @Override
   1.572 +          public void onTextChanged(CharSequence s, int start, int before, int count) {
   1.573 +          }
   1.574 +
   1.575 +        });
   1.576 +
   1.577 +        row3.addTextChangedListener(new TextWatcher() {
   1.578 +          @Override
   1.579 +          public void afterTextChanged(Editable s) {
   1.580 +            activateButton(connectButton, pinEntryCompleted());
   1.581 +          }
   1.582 +
   1.583 +          @Override
   1.584 +          public void beforeTextChanged(CharSequence s, int start, int count,
   1.585 +              int after) {
   1.586 +          }
   1.587 +
   1.588 +          @Override
   1.589 +          public void onTextChanged(CharSequence s, int start, int before, int count) {
   1.590 +          }
   1.591 +        });
   1.592 +
   1.593 +        row1.requestFocus();
   1.594 +      }
   1.595 +    });
   1.596 +  }
   1.597 +
   1.598 +  /**
   1.599 +   * Displays layout with PIN for pairing with another device.
   1.600 +   * No Sync Account has been set up yet.
   1.601 +   */
   1.602 +  private void displayReceiveNoPin() {
   1.603 +    Logger.debug(LOG_TAG, "ReceiveNoPin initiated");
   1.604 +    runOnUiThread(new Runnable(){
   1.605 +
   1.606 +      @Override
   1.607 +      public void run() {
   1.608 +        setContentView(R.layout.sync_setup);
   1.609 +
   1.610 +        // Set up UI.
   1.611 +        pinTextView1 = ((TextView) findViewById(R.id.text_pin1));
   1.612 +        pinTextView2 = ((TextView) findViewById(R.id.text_pin2));
   1.613 +        pinTextView3 = ((TextView) findViewById(R.id.text_pin3));
   1.614 +      }
   1.615 +    });
   1.616 +  }
   1.617 +
   1.618 +  @Override
   1.619 +  public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1.620 +    switch (resultCode) {
   1.621 +    case Activity.RESULT_OK:
   1.622 +      // Setup completed in manual setup.
   1.623 +      finish();
   1.624 +    }
   1.625 +  }
   1.626 +}

mercurial