Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(356)

Side by Side Diff: remoting/android/java/src/org/chromium/chromoting/Chromoting.java

Issue 157013002: Pull HostListDirectoryGrabber out to a separate class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix FindBugs warnings Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | remoting/android/java/src/org/chromium/chromoting/HostInfo.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package org.chromium.chromoting; 5 package org.chromium.chromoting;
6 6
7 import android.accounts.Account; 7 import android.accounts.Account;
8 import android.accounts.AccountManager; 8 import android.accounts.AccountManager;
9 import android.accounts.AccountManagerCallback; 9 import android.accounts.AccountManagerCallback;
10 import android.accounts.AccountManagerFuture; 10 import android.accounts.AccountManagerFuture;
11 import android.accounts.AuthenticatorException; 11 import android.accounts.AuthenticatorException;
12 import android.accounts.OperationCanceledException; 12 import android.accounts.OperationCanceledException;
13 import android.app.Activity; 13 import android.app.Activity;
14 import android.app.ProgressDialog; 14 import android.app.ProgressDialog;
15 import android.content.DialogInterface; 15 import android.content.DialogInterface;
16 import android.content.Intent; 16 import android.content.Intent;
17 import android.content.SharedPreferences; 17 import android.content.SharedPreferences;
18 import android.os.Bundle; 18 import android.os.Bundle;
19 import android.os.Handler;
20 import android.os.HandlerThread;
21 import android.util.Log; 19 import android.util.Log;
22 import android.view.Menu; 20 import android.view.Menu;
23 import android.view.MenuItem; 21 import android.view.MenuItem;
24 import android.widget.ArrayAdapter; 22 import android.widget.ArrayAdapter;
25 import android.widget.ListView; 23 import android.widget.ListView;
26 import android.widget.TextView; 24 import android.widget.TextView;
27 import android.widget.Toast; 25 import android.widget.Toast;
28 26
29 import org.chromium.chromoting.jni.JniInterface; 27 import org.chromium.chromoting.jni.JniInterface;
30 import org.json.JSONArray;
31 import org.json.JSONException;
32 import org.json.JSONObject;
33 28
34 import java.io.IOException; 29 import java.io.IOException;
35 import java.net.URL; 30 import java.util.Arrays;
36 import java.net.URLConnection;
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.Comparator;
40 import java.util.List;
41 import java.util.Scanner;
42 31
43 /** 32 /**
44 * The user interface for querying and displaying a user's host list from the di rectory server. It 33 * The user interface for querying and displaying a user's host list from the di rectory server. It
45 * also requests and renews authentication tokens using the system account manag er. 34 * also requests and renews authentication tokens using the system account manag er.
46 */ 35 */
47 public class Chromoting extends Activity implements JniInterface.ConnectionListe ner { 36 public class Chromoting extends Activity implements JniInterface.ConnectionListe ner,
37 AccountManagerCallback<Bundle>, HostListLoader.Callback {
48 /** Only accounts of this type will be selectable for authentication. */ 38 /** Only accounts of this type will be selectable for authentication. */
49 private static final String ACCOUNT_TYPE = "com.google"; 39 private static final String ACCOUNT_TYPE = "com.google";
50 40
51 /** Scopes at which the authentication token we request will be valid. */ 41 /** Scopes at which the authentication token we request will be valid. */
52 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " + 42 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " +
53 "https://www.googleapis.com/auth/googletalk"; 43 "https://www.googleapis.com/auth/googletalk";
54 44
55 /** Path from which to download a user's host list JSON object. */
56 private static final String HOST_LIST_PATH =
57 "https://www.googleapis.com/chromoting/v1/@me/hosts?key=";
58
59 /** Lock to protect |mAccount| and |mToken|. */
60 // TODO(lambroslambrou): |mHosts| needs to be protected as well.
61 private Object mLock = new Object();
62
63 /** User's account details. */ 45 /** User's account details. */
64 private Account mAccount; 46 private Account mAccount;
65 47
66 /** Account auth token. */ 48 /** Account auth token. */
67 private String mToken; 49 private String mToken;
68 50
51 /** Helper for fetching the host list. */
52 private HostListLoader mHostListLoader;
53
69 /** List of hosts. */ 54 /** List of hosts. */
70 private JSONArray mHosts; 55 private HostInfo[] mHosts;
71 56
72 /** Refresh button. */ 57 /** Refresh button. */
73 private MenuItem mRefreshButton; 58 private MenuItem mRefreshButton;
74 59
75 /** Account switcher. */ 60 /** Account switcher. */
76 private MenuItem mAccountSwitcher; 61 private MenuItem mAccountSwitcher;
77 62
78 /** Greeting at the top of the displayed list. */ 63 /** Greeting at the top of the displayed list. */
79 private TextView mGreeting; 64 private TextView mGreeting;
80 65
81 /** Host list as it appears to the user. */ 66 /** Host list as it appears to the user. */
82 private ListView mList; 67 private ListView mList;
83 68
84 /** Callback handler to be used for network operations. */
85 private Handler mNetwork;
86
87 /** Dialog for reporting connection progress. */ 69 /** Dialog for reporting connection progress. */
88 private ProgressDialog mProgressIndicator; 70 private ProgressDialog mProgressIndicator;
89 71
90 /** 72 /**
73 * This is set when receiving an authentication error from the HostListLoade r. If that occurs,
74 * this flag is set and a fresh authentication token is fetched from the Acc ountsService, and
75 * used to request the host list a second time.
76 */
77 boolean mAlreadyTried;
78
79 /**
91 * Called when the activity is first created. Loads the native library and r equests an 80 * Called when the activity is first created. Loads the native library and r equests an
92 * authentication token from the system. 81 * authentication token from the system.
93 */ 82 */
94 @Override 83 @Override
95 public void onCreate(Bundle savedInstanceState) { 84 public void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState); 85 super.onCreate(savedInstanceState);
97 setContentView(R.layout.main); 86 setContentView(R.layout.main);
98 87
88 mAlreadyTried = false;
89 mHostListLoader = new HostListLoader();
90
99 // Get ahold of our view widgets. 91 // Get ahold of our view widgets.
100 mGreeting = (TextView)findViewById(R.id.hostList_greeting); 92 mGreeting = (TextView)findViewById(R.id.hostList_greeting);
101 mList = (ListView)findViewById(R.id.hostList_chooser); 93 mList = (ListView)findViewById(R.id.hostList_chooser);
102 94
103 // Bring native components online. 95 // Bring native components online.
104 JniInterface.loadLibrary(this); 96 JniInterface.loadLibrary(this);
105 97
106 // Thread responsible for downloading/displaying host list.
107 HandlerThread thread = new HandlerThread("auth_callback");
108 thread.start();
109 mNetwork = new Handler(thread.getLooper());
110
111 SharedPreferences prefs = getPreferences(MODE_PRIVATE); 98 SharedPreferences prefs = getPreferences(MODE_PRIVATE);
112 if (prefs.contains("account_name") && prefs.contains("account_type")) { 99 if (prefs.contains("account_name") && prefs.contains("account_type")) {
113 // Perform authentication using saved account selection. 100 // Perform authentication using saved account selection.
114 mAccount = new Account(prefs.getString("account_name", null), 101 mAccount = new Account(prefs.getString("account_name", null),
115 prefs.getString("account_type", null)); 102 prefs.getString("account_type", null));
116 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, 103 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, this, null);
117 new HostListDirectoryGrabber(this), mNetwork);
118 if (mAccountSwitcher != null) { 104 if (mAccountSwitcher != null) {
119 mAccountSwitcher.setTitle(mAccount.name); 105 mAccountSwitcher.setTitle(mAccount.name);
120 } 106 }
121 } else { 107 } else {
122 // Request auth callback once user has chosen an account. 108 // Request auth callback once user has chosen an account.
123 Log.i("auth", "Requesting auth token from system"); 109 Log.i("auth", "Requesting auth token from system");
124 AccountManager.get(this).getAuthTokenByFeatures( 110 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_ SCOPE, null, this,
125 ACCOUNT_TYPE, 111 null, null, this, null);
126 TOKEN_SCOPE,
127 null,
128 this,
129 null,
130 null,
131 new HostListDirectoryGrabber(this),
132 mNetwork
133 );
134 } 112 }
135 } 113 }
136 114
137 /** Called when the activity is finally finished. */ 115 /** Called when the activity is finally finished. */
138 @Override 116 @Override
139 public void onDestroy() { 117 public void onDestroy() {
140 super.onDestroy(); 118 super.onDestroy();
141 JniInterface.disconnectFromHost(); 119 JniInterface.disconnectFromHost();
142 } 120 }
143 121
(...skipping 18 matching lines...) Expand all
162 // If the user has picked an account, show its name directly on the account switcher. 140 // If the user has picked an account, show its name directly on the account switcher.
163 mAccountSwitcher.setTitle(mAccount.name); 141 mAccountSwitcher.setTitle(mAccount.name);
164 } 142 }
165 143
166 return super.onCreateOptionsMenu(menu); 144 return super.onCreateOptionsMenu(menu);
167 } 145 }
168 146
169 /** Called whenever an action bar button is pressed. */ 147 /** Called whenever an action bar button is pressed. */
170 @Override 148 @Override
171 public boolean onOptionsItemSelected(MenuItem item) { 149 public boolean onOptionsItemSelected(MenuItem item) {
150 mAlreadyTried = false;
172 if (item == mAccountSwitcher) { 151 if (item == mAccountSwitcher) {
173 // The account switcher triggers a listing of all available accounts . 152 // The account switcher triggers a listing of all available accounts .
174 AccountManager.get(this).getAuthTokenByFeatures( 153 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_ SCOPE, null, this,
175 ACCOUNT_TYPE, 154 null, null, this, null);
176 TOKEN_SCOPE, 155 } else {
177 null,
178 this,
179 null,
180 null,
181 new HostListDirectoryGrabber(this),
182 mNetwork
183 );
184 }
185 else {
186 // The refresh button simply makes use of the currently-chosen accou nt. 156 // The refresh button simply makes use of the currently-chosen accou nt.
187 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, 157 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, this, null);
188 new HostListDirectoryGrabber(this), mNetwork);
189 } 158 }
190 159
191 return true; 160 return true;
192 } 161 }
193 162
194 /** Called when the user taps on a host entry. */ 163 /** Called when the user taps on a host entry. */
195 public void connectToHost(JSONObject host) { 164 public void connectToHost(HostInfo host) {
196 try { 165 if (host.jabberId.isEmpty() || host.publicKey.isEmpty()) {
197 synchronized (mLock) { 166 // TODO(lambroslambrou): If these keys are not present, treat this a s a connection
198 JniInterface.connectToHost(mAccount.name, mToken, host.getString ("jabberId"), 167 // failure and reload the host list (see crbug.com/304719).
199 host.getString("hostId"), host.getString("publicKey"), t his);
200 }
201 } catch (JSONException ex) {
202 Log.w("host", ex);
203 Toast.makeText(this, getString(R.string.error_reading_host), 168 Toast.makeText(this, getString(R.string.error_reading_host),
204 Toast.LENGTH_LONG).show(); 169 Toast.LENGTH_LONG).show();
205 // Close the application. 170 return;
206 finish();
207 } 171 }
172
173 JniInterface.connectToHost(mAccount.name, mToken, host.jabberId, host.id , host.publicKey,
174 this);
208 } 175 }
209 176
210 /** 177 @Override
211 * Processes the authentication token once the system provides it. Once in p ossession of such a 178 public void run(AccountManagerFuture<Bundle> future) {
212 * token, attempts to request a host list from the directory server. In case of a bad response, 179 Log.i("auth", "User finished with auth dialogs");
213 * this is retried once in case the system's cached auth token had expired. 180 Bundle result = null;
214 */ 181 String explanation = null;
215 private class HostListDirectoryGrabber implements AccountManagerCallback<Bun dle> { 182 try {
216 // TODO(lambroslambrou): Refactor this class to provide async interface usable on the UI 183 // Here comes our auth token from the Android system.
217 // thread. 184 result = future.getResult();
218 185 } catch (OperationCanceledException ex) {
219 /** Whether authentication has already been attempted. */ 186 explanation = getString(R.string.error_auth_canceled);
220 private boolean mAlreadyTried; 187 } catch (AuthenticatorException ex) {
221 188 explanation = getString(R.string.error_no_accounts);
222 /** Communication with the screen. */ 189 } catch (IOException ex) {
223 private Activity mUi; 190 explanation = getString(R.string.error_bad_connection);
224
225 /** Constructor. */
226 public HostListDirectoryGrabber(Activity ui) {
227 mAlreadyTried = false;
228 mUi = ui;
229 } 191 }
230 192
231 /** 193 if (result == null) {
232 * Retrieves the host list from the directory server. This method perfor ms 194 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show();
233 * network operations and must be run an a non-UI thread. 195 return;
234 */
235 @Override
236 public void run(AccountManagerFuture<Bundle> future) {
237 Log.i("auth", "User finished with auth dialogs");
238 try {
239 // Here comes our auth token from the Android system.
240 Bundle result = future.getResult();
241 String accountName = result.getString(AccountManager.KEY_ACCOUNT _NAME);
242 String accountType = result.getString(AccountManager.KEY_ACCOUNT _TYPE);
243 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN );
244 Log.i("auth", "Received an auth token from system");
245
246 synchronized (mLock) {
247 mAccount = new Account(accountName, accountType);
248 mToken = authToken;
249 getPreferences(MODE_PRIVATE).edit().putString("account_name" , accountName).
250 putString("account_type", accountType).apply();
251 }
252
253 // Send our HTTP request to the directory server.
254 URLConnection link =
255 new URL(HOST_LIST_PATH + JniInterface.nativeGetApiKey()) .openConnection();
256 link.addRequestProperty("client_id", JniInterface.nativeGetClien tId());
257 link.addRequestProperty("client_secret", JniInterface.nativeGetC lientSecret());
258 link.setRequestProperty("Authorization", "OAuth " + authToken);
259
260 // Listen for the server to respond.
261 StringBuilder response = new StringBuilder();
262 Scanner incoming = new Scanner(link.getInputStream());
263 Log.i("auth", "Successfully authenticated to directory server");
264 while (incoming.hasNext()) {
265 response.append(incoming.nextLine());
266 }
267 incoming.close();
268
269 // Interpret what the directory server told us.
270 JSONObject data = new JSONObject(String.valueOf(response)).getJS ONObject("data");
271 mHosts = sortHosts(data.getJSONArray("items"));
272 Log.i("hostlist", "Received host listing from directory server") ;
273 } catch (RuntimeException ex) {
274 // Make sure any other failure is reported to the user (as an un known error).
275 throw ex;
276 } catch (Exception ex) {
277 // Assemble error message to display to the user.
278 String explanation = getString(R.string.error_unknown);
279 if (ex instanceof OperationCanceledException) {
280 explanation = getString(R.string.error_auth_canceled);
281 } else if (ex instanceof AuthenticatorException) {
282 explanation = getString(R.string.error_no_accounts);
283 } else if (ex instanceof IOException) {
284 if (!mAlreadyTried) {
285 // This was our first connection attempt.
286
287 synchronized (mLock) {
288 if (mAccount != null) {
289 // We got an account, but couldn't log into it. We'll retry in case
290 // the system's cached authentication token had already expired.
291 AccountManager authenticator = AccountManager.ge t(mUi);
292 mAlreadyTried = true;
293
294 Log.w("auth", "Requesting renewal of rejected au th token");
295 authenticator.invalidateAuthToken(mAccount.type, mToken);
296 mToken = null;
297 authenticator.getAuthToken(
298 mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork);
299
300 // We're not in an error state *yet*.
301 return;
302 }
303 }
304
305 // We didn't even get an account, so the auth server is likely unreachable.
306 explanation = getString(R.string.error_bad_connection);
307 } else {
308 // Authentication truly failed.
309 Log.e("auth", "Fresh auth token was also rejected");
310 explanation = getString(R.string.error_auth_failed);
311 }
312 } else if (ex instanceof JSONException) {
313 explanation = getString(R.string.error_unexpected_response);
314 }
315
316 mHosts = null;
317 Log.w("auth", ex);
318 Toast.makeText(mUi, explanation, Toast.LENGTH_LONG).show();
319 }
320
321 // Share our findings with the user.
322 runOnUiThread(new Runnable() {
323 @Override
324 public void run() {
325 updateUi();
326 }
327 });
328 } 196 }
329 197
330 private JSONArray sortHosts(JSONArray hosts) { 198 String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
331 List<JSONObject> hostList = new ArrayList<JSONObject>(); 199 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
332 for (int i = 0; i < hosts.length(); i++) { 200 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
333 try { 201 Log.i("auth", "Received an auth token from system");
334 hostList.add(hosts.getJSONObject(i));
335 } catch (JSONException ex) {
336 // Ignore non-object entries.
337 }
338 }
339 202
340 Comparator<JSONObject> compareHosts = new Comparator<JSONObject>() { 203 mAccount = new Account(accountName, accountType);
341 public int compare(JSONObject a, JSONObject b) { 204 mToken = authToken;
342 try { 205 getPreferences(MODE_PRIVATE).edit().putString("account_name", accountNam e).
343 boolean aOnline = a.getString("status").equals("ONLINE") ; 206 putString("account_type", accountType).apply();
344 boolean bOnline = b.getString("status").equals("ONLINE") ;
345 if (aOnline && !bOnline) {
346 return -1;
347 }
348 if (bOnline && !aOnline) {
349 return 1;
350 }
351 String aName = a.getString("hostName").toUpperCase();
352 String bName = b.getString("hostName").toUpperCase();
353 return aName.compareTo(bName);
354 } catch (JSONException ex) {
355 return 0;
356 }
357 }
358 };
359 Collections.sort(hostList, compareHosts);
360 207
361 JSONArray result = new JSONArray(hostList); 208 mHostListLoader.retrieveHostList(authToken, this);
362 return result; 209 }
210
211 @Override
212 public void onHostListReceived(HostInfo[] hosts) {
213 // Store a copy of the array, so that it can't be mutated by the HostLis tLoader. HostInfo
214 // is an immutable type, so a shallow copy of the array is sufficient he re.
215 mHosts = Arrays.copyOf(hosts, hosts.length);
216 updateUi();
217 }
218
219 @Override
220 public void onError(HostListLoader.Error error) {
221 String explanation = null;
222 switch (error) {
223 case AUTH_FAILED:
224 break;
225 case NETWORK_ERROR:
226 explanation = getString(R.string.error_bad_connection);
227 break;
228 case SERVICE_UNAVAILABLE:
229 case UNEXPECTED_RESPONSE:
230 explanation = getString(R.string.error_unexpected_response);
231 break;
232 case UNKNOWN:
233 explanation = getString(R.string.error_unknown);
234 break;
235 default:
236 // Unreachable.
237 return;
238 }
239
240 if (explanation != null) {
241 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show();
242 return;
243 }
244
245 // This is the AUTH_FAILED case.
246
247 if (!mAlreadyTried) {
248 // This was our first connection attempt.
249
250 AccountManager authenticator = AccountManager.get(this);
251 mAlreadyTried = true;
252
253 Log.w("auth", "Requesting renewal of rejected auth token");
254 authenticator.invalidateAuthToken(mAccount.type, mToken);
255 mToken = null;
256 authenticator.getAuthToken(mAccount, TOKEN_SCOPE, null, this, this, null);
257
258 // We're not in an error state *yet*.
259 return;
260 } else {
261 // Authentication truly failed.
262 Log.e("auth", "Fresh auth token was also rejected");
263 explanation = getString(R.string.error_auth_failed);
264 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show();
363 } 265 }
364 } 266 }
365 267
366 /** 268 /**
367 * Updates the infotext and host list display. 269 * Updates the infotext and host list display.
368 * This method affects the UI and must be run on the main thread.
369 */ 270 */
370 private void updateUi() { 271 private void updateUi() {
371 synchronized (mLock) { 272 mRefreshButton.setEnabled(mAccount != null);
372 mRefreshButton.setEnabled(mAccount != null); 273 if (mAccount != null) {
373 if (mAccount != null) { 274 mAccountSwitcher.setTitle(mAccount.name);
374 mAccountSwitcher.setTitle(mAccount.name);
375 }
376 } 275 }
377 276
378 if (mHosts == null) { 277 if (mHosts == null) {
379 mGreeting.setText(getString(R.string.inst_empty_list)); 278 mGreeting.setText(getString(R.string.inst_empty_list));
380 mList.setAdapter(null); 279 mList.setAdapter(null);
381 return; 280 return;
382 } 281 }
383 282
384 mGreeting.setText(getString(R.string.inst_host_list)); 283 mGreeting.setText(getString(R.string.inst_host_list));
385 284
386 ArrayAdapter<JSONObject> displayer = new HostListAdapter(this, R.layout. host); 285 ArrayAdapter<HostInfo> displayer = new HostListAdapter(this, R.layout.ho st, mHosts);
387 Log.i("hostlist", "About to populate host list display"); 286 Log.i("hostlist", "About to populate host list display");
388 try { 287 mList.setAdapter(displayer);
389 int index = 0;
390 while (!mHosts.isNull(index)) {
391 displayer.add(mHosts.getJSONObject(index));
392 ++index;
393 }
394 mList.setAdapter(displayer);
395 } catch (JSONException ex) {
396 Log.w("hostlist", ex);
397 Toast.makeText(this, getString(R.string.error_cataloging_hosts),
398 Toast.LENGTH_LONG).show();
399
400 // Close the application.
401 finish();
402 }
403 } 288 }
404 289
405 @Override 290 @Override
406 public void onConnectionState(JniInterface.ConnectionListener.State state, 291 public void onConnectionState(JniInterface.ConnectionListener.State state,
407 JniInterface.ConnectionListener.Error error) { 292 JniInterface.ConnectionListener.Error error) {
408 String stateText = getResources().getStringArray(R.array.protoc_states)[ state.value()]; 293 String stateText = getResources().getStringArray(R.array.protoc_states)[ state.value()];
409 boolean dismissProgress = false; 294 boolean dismissProgress = false;
410 switch (state) { 295 switch (state) {
411 case INITIALIZING: 296 case INITIALIZING:
412 case CONNECTING: 297 case CONNECTING:
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
456 // Unreachable, but required by Google Java style and findbugs. 341 // Unreachable, but required by Google Java style and findbugs.
457 assert false : "Unreached"; 342 assert false : "Unreached";
458 } 343 }
459 344
460 if (dismissProgress && mProgressIndicator != null) { 345 if (dismissProgress && mProgressIndicator != null) {
461 mProgressIndicator.dismiss(); 346 mProgressIndicator.dismiss();
462 mProgressIndicator = null; 347 mProgressIndicator = null;
463 } 348 }
464 } 349 }
465 } 350 }
OLDNEW
« no previous file with comments | « no previous file | remoting/android/java/src/org/chromium/chromoting/HostInfo.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698