OLD | NEW |
---|---|
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.ActionBar; | |
13 import android.app.Activity; | 14 import android.app.Activity; |
14 import android.app.ProgressDialog; | 15 import android.app.ProgressDialog; |
15 import android.content.DialogInterface; | 16 import android.content.DialogInterface; |
16 import android.content.Intent; | 17 import android.content.Intent; |
17 import android.content.SharedPreferences; | 18 import android.content.SharedPreferences; |
19 import android.content.res.Configuration; | |
18 import android.os.Bundle; | 20 import android.os.Bundle; |
19 import android.util.Log; | 21 import android.util.Log; |
20 import android.view.Menu; | 22 import android.view.Menu; |
21 import android.view.MenuItem; | 23 import android.view.MenuItem; |
22 import android.widget.ArrayAdapter; | 24 import android.widget.ArrayAdapter; |
23 import android.widget.ListView; | 25 import android.widget.ListView; |
24 import android.widget.TextView; | 26 import android.widget.TextView; |
25 import android.widget.Toast; | 27 import android.widget.Toast; |
26 | 28 |
27 import org.chromium.chromoting.jni.JniInterface; | 29 import org.chromium.chromoting.jni.JniInterface; |
28 | 30 |
29 import java.io.IOException; | 31 import java.io.IOException; |
32 import java.util.Arrays; | |
30 | 33 |
31 /** | 34 /** |
32 * The user interface for querying and displaying a user's host list from the di rectory server. It | 35 * The user interface for querying and displaying a user's host list from the di rectory server. It |
33 * also requests and renews authentication tokens using the system account manag er. | 36 * also requests and renews authentication tokens using the system account manag er. |
34 */ | 37 */ |
35 public class Chromoting extends Activity implements JniInterface.ConnectionListe ner, | 38 public class Chromoting extends Activity implements JniInterface.ConnectionListe ner, |
36 AccountManagerCallback<Bundle>, HostListLoader.Callback { | 39 AccountManagerCallback<Bundle>, ActionBar.OnNavigationListener, |
40 HostListLoader.Callback { | |
37 /** Only accounts of this type will be selectable for authentication. */ | 41 /** Only accounts of this type will be selectable for authentication. */ |
38 private static final String ACCOUNT_TYPE = "com.google"; | 42 private static final String ACCOUNT_TYPE = "com.google"; |
39 | 43 |
40 /** Scopes at which the authentication token we request will be valid. */ | 44 /** Scopes at which the authentication token we request will be valid. */ |
41 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " + | 45 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " + |
42 "https://www.googleapis.com/auth/googletalk"; | 46 "https://www.googleapis.com/auth/googletalk"; |
43 | 47 |
44 /** User's account details. */ | 48 /** User's account details. */ |
45 private Account mAccount; | 49 private Account mAccount; |
46 | 50 |
51 /** List of accounts on the system. */ | |
52 private Account[] mAccounts; | |
53 | |
47 /** Account auth token. */ | 54 /** Account auth token. */ |
48 private String mToken; | 55 private String mToken; |
49 | 56 |
50 /** Helper for fetching the host list. */ | 57 /** Helper for fetching the host list. */ |
51 private HostListLoader mHostListLoader; | 58 private HostListLoader mHostListLoader; |
52 | 59 |
53 /** List of hosts. */ | 60 /** List of hosts. */ |
54 private Host[] mHosts; | 61 private Host[] mHosts; |
55 | 62 |
56 /** Refresh button. */ | 63 /** Refresh button. */ |
57 private MenuItem mRefreshButton; | 64 private MenuItem mRefreshButton; |
58 | 65 |
59 /** Account switcher. */ | |
60 private MenuItem mAccountSwitcher; | |
61 | |
62 /** Greeting at the top of the displayed list. */ | 66 /** Greeting at the top of the displayed list. */ |
63 private TextView mGreeting; | 67 private TextView mGreeting; |
64 | 68 |
65 /** Host list as it appears to the user. */ | 69 /** Host list as it appears to the user. */ |
66 private ListView mList; | 70 private ListView mList; |
67 | 71 |
68 /** Dialog for reporting connection progress. */ | 72 /** Dialog for reporting connection progress. */ |
69 private ProgressDialog mProgressIndicator; | 73 private ProgressDialog mProgressIndicator; |
70 | 74 |
71 /** | 75 /** |
72 * This is set when receiving an authentication error from the HostListLoade r. If that occurs, | 76 * This is set when receiving an authentication error from the HostListLoade r. If that occurs, |
73 * this flag is set and a fresh authentication token is fetched from the Acc ountsService, and | 77 * this flag is set and a fresh authentication token is fetched from the Acc ountsService, and |
74 * used to request the host list a second time. | 78 * used to request the host list a second time. |
75 */ | 79 */ |
76 boolean mAlreadyTried; | 80 boolean mAlreadyTried; |
Sergey Ulanov
2014/02/18 21:58:43
maybe rename this to make it clear that this has t
Lambros
2014/02/19 23:51:39
Done.
| |
77 | 81 |
78 /** | 82 /** |
79 * Called when the activity is first created. Loads the native library and r equests an | 83 * Called when the activity is first created. Loads the native library and r equests an |
80 * authentication token from the system. | 84 * authentication token from the system. |
81 */ | 85 */ |
82 @Override | 86 @Override |
83 public void onCreate(Bundle savedInstanceState) { | 87 public void onCreate(Bundle savedInstanceState) { |
84 super.onCreate(savedInstanceState); | 88 super.onCreate(savedInstanceState); |
85 setContentView(R.layout.main); | 89 setContentView(R.layout.main); |
86 | 90 |
87 mAlreadyTried = false; | 91 mAlreadyTried = false; |
88 mHostListLoader = new HostListLoader(); | 92 mHostListLoader = new HostListLoader(); |
89 | 93 |
90 // Get ahold of our view widgets. | 94 // Get ahold of our view widgets. |
91 mGreeting = (TextView)findViewById(R.id.hostList_greeting); | 95 mGreeting = (TextView)findViewById(R.id.hostList_greeting); |
92 mList = (ListView)findViewById(R.id.hostList_chooser); | 96 mList = (ListView)findViewById(R.id.hostList_chooser); |
93 | 97 |
94 // Bring native components online. | 98 // Bring native components online. |
95 JniInterface.loadLibrary(this); | 99 JniInterface.loadLibrary(this); |
96 | 100 |
101 mAccounts = AccountManager.get(this).getAccountsByType(ACCOUNT_TYPE); | |
102 if (mAccounts.length == 0) { | |
103 // TODO(lambroslambrou): Show a dialog with message: "To use <App Na me>, you'll need | |
104 // to add a Google Account to your device." and two buttons: "Close" , "Add account". | |
105 // The "Add account" button should navigate to the system "Add a Goo gle Account" | |
106 // screen. | |
107 return; | |
108 } | |
109 | |
97 SharedPreferences prefs = getPreferences(MODE_PRIVATE); | 110 SharedPreferences prefs = getPreferences(MODE_PRIVATE); |
111 int index = -1; | |
98 if (prefs.contains("account_name") && prefs.contains("account_type")) { | 112 if (prefs.contains("account_name") && prefs.contains("account_type")) { |
99 // Perform authentication using saved account selection. | |
100 mAccount = new Account(prefs.getString("account_name", null), | 113 mAccount = new Account(prefs.getString("account_name", null), |
101 prefs.getString("account_type", null)); | 114 prefs.getString("account_type", null)); |
102 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, this, null); | 115 index = Arrays.asList(mAccounts).indexOf(mAccount); |
103 if (mAccountSwitcher != null) { | 116 } |
104 mAccountSwitcher.setTitle(mAccount.name); | 117 if (index == -1) { |
105 } | 118 // Preference not loaded, or does not correspond to a valid account, so just pick the |
119 // first account arbitrarily. | |
120 index = 0; | |
121 mAccount = mAccounts[0]; | |
122 } | |
123 | |
124 if (mAccounts.length == 1) { | |
125 getActionBar().setDisplayShowTitleEnabled(true); | |
126 getActionBar().setSubtitle(mAccount.name); | |
106 } else { | 127 } else { |
107 // Request auth callback once user has chosen an account. | 128 AccountsAdapter adapter = new AccountsAdapter(this, mAccounts); |
108 Log.i("auth", "Requesting auth token from system"); | 129 getActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); |
109 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_ SCOPE, null, this, | 130 getActionBar().setListNavigationCallbacks(adapter, this); |
110 null, null, this, null); | 131 getActionBar().setSelectedNavigationItem(index); |
111 } | 132 } |
133 | |
134 refreshHostList(); | |
112 } | 135 } |
113 | 136 |
114 /** Called when the activity is finally finished. */ | 137 /** Called when the activity is finally finished. */ |
115 @Override | 138 @Override |
116 public void onDestroy() { | 139 public void onDestroy() { |
117 super.onDestroy(); | 140 super.onDestroy(); |
118 JniInterface.disconnectFromHost(); | 141 JniInterface.disconnectFromHost(); |
119 } | 142 } |
120 | 143 |
144 /** Called when the display is rotated (as registered in the manifest). */ | |
145 @Override | |
146 public void onConfigurationChanged(Configuration newConfig) { | |
147 super.onConfigurationChanged(newConfig); | |
148 | |
149 // Reload the spinner resources, since the font sizes are dependent on t he screen | |
150 // orientation. | |
151 if (mAccounts.length != 1) { | |
152 AccountsAdapter adapter = new AccountsAdapter(this, mAccounts); | |
153 getActionBar().setListNavigationCallbacks(adapter, this); | |
154 } | |
155 } | |
156 | |
121 /** Called to initialize the action bar. */ | 157 /** Called to initialize the action bar. */ |
122 @Override | 158 @Override |
123 public boolean onCreateOptionsMenu(Menu menu) { | 159 public boolean onCreateOptionsMenu(Menu menu) { |
124 getMenuInflater().inflate(R.menu.chromoting_actionbar, menu); | 160 getMenuInflater().inflate(R.menu.chromoting_actionbar, menu); |
125 mRefreshButton = menu.findItem(R.id.actionbar_directoryrefresh); | 161 mRefreshButton = menu.findItem(R.id.actionbar_directoryrefresh); |
126 mAccountSwitcher = menu.findItem(R.id.actionbar_accountswitcher); | |
127 | |
128 Account[] usableAccounts = AccountManager.get(this).getAccountsByType(AC COUNT_TYPE); | |
129 if (usableAccounts.length == 1 && usableAccounts[0].equals(mAccount)) { | |
130 // If we're using the only available account, don't offer account sw itching. | |
131 // (If there are *no* accounts available, clicking this allows you t o add a new one.) | |
132 mAccountSwitcher.setEnabled(false); | |
133 } | |
134 | 162 |
135 if (mAccount == null) { | 163 if (mAccount == null) { |
136 // If no account has been chosen, don't allow the user to refresh th e listing. | 164 // If there is no account, don't allow the user to refresh the listi ng. |
137 mRefreshButton.setEnabled(false); | 165 mRefreshButton.setEnabled(false); |
138 } else { | |
139 // If the user has picked an account, show its name directly on the account switcher. | |
140 mAccountSwitcher.setTitle(mAccount.name); | |
141 } | 166 } |
142 | 167 |
143 return super.onCreateOptionsMenu(menu); | 168 return super.onCreateOptionsMenu(menu); |
144 } | 169 } |
145 | 170 |
146 /** Called whenever an action bar button is pressed. */ | 171 /** Called whenever an action bar button is pressed. */ |
147 @Override | 172 @Override |
148 public boolean onOptionsItemSelected(MenuItem item) { | 173 public boolean onOptionsItemSelected(MenuItem item) { |
149 mAlreadyTried = false; | 174 refreshHostList(); |
150 if (item == mAccountSwitcher) { | |
151 // The account switcher triggers a listing of all available accounts . | |
152 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_ SCOPE, null, this, | |
153 null, null, this, null); | |
154 } else { | |
155 // The refresh button simply makes use of the currently-chosen accou nt. | |
156 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, this, null); | |
157 } | |
158 | |
159 return true; | 175 return true; |
160 } | 176 } |
161 | 177 |
162 /** Called when the user taps on a host entry. */ | 178 /** Called when the user taps on a host entry. */ |
163 public void connectToHost(Host host) { | 179 public void connectToHost(Host host) { |
164 JniInterface.connectToHost(mAccount.name, mToken, host.jabberId, host.id , host.publicKey, | 180 JniInterface.connectToHost(mAccount.name, mToken, host.jabberId, host.id , host.publicKey, |
165 this); | 181 this); |
166 } | 182 } |
167 | 183 |
184 private void refreshHostList() { | |
185 mAlreadyTried = false; | |
186 | |
187 // The refresh button simply makes use of the currently-chosen account. | |
188 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, this, this, null); | |
189 } | |
190 | |
168 @Override | 191 @Override |
169 public void run(AccountManagerFuture<Bundle> future) { | 192 public void run(AccountManagerFuture<Bundle> future) { |
170 Log.i("auth", "User finished with auth dialogs"); | 193 Log.i("auth", "User finished with auth dialogs"); |
171 try { | 194 try { |
172 // Here comes our auth token from the Android system. | 195 // Here comes our auth token from the Android system. |
173 Bundle result = future.getResult(); | 196 Bundle result = future.getResult(); |
174 String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAM E); | |
175 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYP E); | |
176 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); | 197 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); |
177 Log.i("auth", "Received an auth token from system"); | 198 Log.i("auth", "Received an auth token from system"); |
178 | 199 |
179 mAccount = new Account(accountName, accountType); | |
180 mToken = authToken; | 200 mToken = authToken; |
181 getPreferences(MODE_PRIVATE).edit().putString("account_name", accoun tName). | |
182 putString("account_type", accountType).apply(); | |
183 | 201 |
184 mHostListLoader.retrieveHostList(authToken, this); | 202 mHostListLoader.retrieveHostList(authToken, this); |
185 } catch (OperationCanceledException ex) { | 203 } catch (OperationCanceledException ex) { |
186 String explanation = getString(R.string.error_auth_canceled); | 204 String explanation = getString(R.string.error_auth_canceled); |
187 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); | 205 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
188 } catch (AuthenticatorException ex) { | 206 } catch (AuthenticatorException ex) { |
189 String explanation = getString(R.string.error_no_accounts); | 207 String explanation = getString(R.string.error_no_accounts); |
190 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); | 208 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
191 } catch (IOException ex) { | 209 } catch (IOException ex) { |
192 String explanation = getString(R.string.error_bad_connection); | 210 String explanation = getString(R.string.error_bad_connection); |
193 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); | 211 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
194 } | 212 } |
195 } | 213 } |
196 | 214 |
197 @Override | 215 @Override |
216 public boolean onNavigationItemSelected(int itemPosition, long itemId) { | |
217 mAccount = mAccounts[itemPosition]; | |
218 | |
219 getPreferences(MODE_PRIVATE).edit().putString("account_name", mAccount.n ame). | |
220 putString("account_type", mAccount.type).apply(); | |
221 | |
222 refreshHostList(); | |
223 return true; | |
224 } | |
225 | |
226 @Override | |
198 public void onHostListReceived(Host[] hosts) { | 227 public void onHostListReceived(Host[] hosts) { |
199 mHosts = hosts; | 228 mHosts = hosts; |
200 updateUi(); | 229 updateUi(); |
201 } | 230 } |
202 | 231 |
203 @Override | 232 @Override |
204 public void onError(HostListLoader.Error error) { | 233 public void onError(HostListLoader.Error error) { |
205 String explanation = null; | 234 String explanation = null; |
206 switch (error) { | 235 switch (error) { |
207 case AUTH_FAILED: | 236 case AUTH_FAILED: |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
247 explanation = getString(R.string.error_auth_failed); | 276 explanation = getString(R.string.error_auth_failed); |
248 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); | 277 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
249 } | 278 } |
250 } | 279 } |
251 | 280 |
252 /** | 281 /** |
253 * Updates the infotext and host list display. | 282 * Updates the infotext and host list display. |
254 */ | 283 */ |
255 private void updateUi() { | 284 private void updateUi() { |
256 mRefreshButton.setEnabled(mAccount != null); | 285 mRefreshButton.setEnabled(mAccount != null); |
257 if (mAccount != null) { | |
258 mAccountSwitcher.setTitle(mAccount.name); | |
259 } | |
260 | 286 |
261 if (mHosts == null) { | 287 if (mHosts == null) { |
262 mGreeting.setText(getString(R.string.inst_empty_list)); | 288 mGreeting.setText(getString(R.string.inst_empty_list)); |
263 mList.setAdapter(null); | 289 mList.setAdapter(null); |
264 return; | 290 return; |
265 } | 291 } |
266 | 292 |
267 mGreeting.setText(getString(R.string.inst_host_list)); | 293 mGreeting.setText(getString(R.string.inst_host_list)); |
268 | 294 |
269 ArrayAdapter<Host> displayer = new HostListAdapter(this, R.layout.host, mHosts); | 295 ArrayAdapter<Host> displayer = new HostListAdapter(this, R.layout.host, mHosts); |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
325 // Unreachable, but required by Google Java style and findbugs. | 351 // Unreachable, but required by Google Java style and findbugs. |
326 assert false : "Unreached"; | 352 assert false : "Unreached"; |
327 } | 353 } |
328 | 354 |
329 if (dismissProgress && mProgressIndicator != null) { | 355 if (dismissProgress && mProgressIndicator != null) { |
330 mProgressIndicator.dismiss(); | 356 mProgressIndicator.dismiss(); |
331 mProgressIndicator = null; | 357 mProgressIndicator = null; |
332 } | 358 } |
333 } | 359 } |
334 } | 360 } |
OLD | NEW |