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 +}