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

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

Issue 19506004: Add the beginnings of a Chromoting Android app (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Minimize monitor use, concatenate using StringBuilder, tune host list spacing, make build target re… Created 7 years, 5 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
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 package org.chromium.chromoting;
6
7 import android.accounts.Account;
8 import android.accounts.AccountManager;
9 import android.accounts.AccountManagerCallback;
10 import android.accounts.AccountManagerFuture;
11 import android.accounts.AuthenticatorException;
12 import android.accounts.OperationCanceledException;
13 import android.app.Activity;
14 import android.content.Context;
15 import android.content.Intent;
16 import android.os.Bundle;
17 import android.os.Handler;
18 import android.os.HandlerThread;
19 import android.text.Html;
20 import android.util.Log;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.ArrayAdapter;
24 import android.widget.TextView;
25 import android.widget.ListView;
26 import android.widget.Toast;
27
28 import org.chromium.chromoting.jni.JniInterface;
29 import org.json.JSONArray;
30 import org.json.JSONException;
31 import org.json.JSONObject;
32
33 import java.io.IOException;
34 import java.net.URL;
35 import java.net.URLConnection;
36 import java.util.Scanner;
37
38 /**
39 * The user interface for querying and displaying a user's host list from the di rectory server. It
40 * also requests and renews authentication tokens using the system account manag er.
41 */
42 public class Chromoting extends Activity {
43 /** Only accounts of this type will be selectable for authentication. */
44 private static final String ACCOUNT_TYPE = "com.google";
45
46 /** Scopes at which the authentication token we request will be valid. */
47 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " +
48 "https://www.googleapis.com/auth/googletalk";
49
50 /** Path from which to download a user's host list JSON object. */
51 private static final String HOST_LIST_PATH =
52 "https://www.googleapis.com/chromoting/v1/@me/hosts?key=";
53
54 /** Color to use for hosts that are online. */
55 private static final String HOST_COLOR_ONLINE = "green";
56
57 /** Color to use for hosts that are offline. */
58 private static final String HOST_COLOR_OFFLINE = "red";
59
60 /** User's account details. */
61 Account mAccount;
62
63 /** Account auth token. */
64 String mToken;
65
66 /** List of hosts. */
67 JSONArray mHosts;
68
69 /** Greeting at the top of the displayed list. */
70 TextView mGreeting;
71
72 /** Host list as it appears to the user. */
73 ListView mList;
74
75 /** Callback handler to be used for network operations. */
76 Handler mNetwork;
77
78 /**
79 * Called when the activity is first created. Loads the native library and r equests an
80 * authentication token from the system.
81 */
82 @Override
83 public void onCreate(Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 setContentView(R.layout.main);
86
87 // Get ahold of our view widgets.
88 mGreeting = (TextView)findViewById(R.id.hostList_greeting);
89 mList = (ListView)findViewById(R.id.hostList_chooser);
90
91 // Bring native components online.
92 JniInterface.loadLibrary(this);
93
94 // Thread responsible for downloading/displaying host list.
95 HandlerThread thread = new HandlerThread("auth_callback");
96 thread.start();
97 mNetwork = new Handler(thread.getLooper());
98
99 // Request callback once user has chosen an account.
100 Log.i("auth", "Requesting auth token from system");
101 AccountManager.get(this).getAuthTokenByFeatures(
102 ACCOUNT_TYPE,
103 TOKEN_SCOPE,
104 null,
105 this,
106 null,
107 null,
108 new HostListDirectoryGrabber(this),
109 mNetwork
110 );
111 }
112
113 /** Called when the activity is finally finished. */
114 @Override
115 public void onDestroy() {
116 super.onDestroy();
117
118 JniInterface.disconnectFromHost();
119 }
120
121 /**
122 * Processes the authentication token once the system provides it. Once in p ossession of such a
123 * token, attempts to request a host list from the directory server. In case of a bad response,
124 * this is retried once in case the system's cached auth token had expired.
125 */
126 private class HostListDirectoryGrabber implements AccountManagerCallback<Bun dle> {
127 /** Whether authentication has already been attempted. */
128 private boolean mAlreadyTried;
129
130 /** Communication with the screen. */
131 private Activity mUi;
132
133 /** Constructor. */
134 public HostListDirectoryGrabber(Activity ui) {
135 mAlreadyTried = false;
136 mUi = ui;
137 }
138
139 /**
140 * Retrieves the host list from the directory server. This method perfor ms
141 * network operations and must be run an a non-UI thread.
142 */
143 @Override
144 public void run(AccountManagerFuture<Bundle> future) {
145 Log.i("auth", "User finished with auth dialogs");
146 mAlreadyTried = true;
147 try {
148 // Here comes our auth token from the Android system.
149 Bundle result = future.getResult();
150 Log.i("auth", "Received an auth token from system");
151 mAccount = new Account(result.getString(AccountManager.KEY_ACCOU NT_NAME),
152 result.getString(AccountManager.KEY_ACCOUNT_TYPE));
153 mToken = result.getString(AccountManager.KEY_AUTHTOKEN);
154
155 // Send our HTTP request to the directory server.
156 URLConnection link =
157 new URL(HOST_LIST_PATH + JniInterface.getApiKey()).openC onnection();
158 link.addRequestProperty("client_id", JniInterface.getClientId()) ;
159 link.addRequestProperty("client_secret", JniInterface.getClientS ecret());
160 link.setRequestProperty("Authorization", "OAuth " + mToken);
161
162 // Listen for the server to respond.
163 StringBuilder response = new StringBuilder();
164 Scanner incoming = new Scanner(link.getInputStream());
165 Log.i("auth", "Successfully authenticated to directory server");
166 while (incoming.hasNext()) {
167 response.append(incoming.nextLine());
168 }
169 incoming.close();
170
171 // Interpret what the directory server told us.
172 JSONObject data = new JSONObject(String.valueOf(response)).getJS ONObject("data");
173 mHosts = data.getJSONArray("items");
174 Log.i("hostlist", "Received host listing from directory server") ;
175
176 // Share our findings with the user.
177 runOnUiThread(new HostListDisplayer(mUi));
178 }
179 catch(Exception ex) {
180 // Assemble error message to display to the user.
181 String explanation = getString(R.string.error_unknown);
182 if (ex instanceof OperationCanceledException) {
183 explanation = getString(R.string.error_auth_canceled);
184 } else if (ex instanceof AuthenticatorException) {
185 explanation = getString(R.string.error_no_accounts);
186 } else if (ex instanceof IOException) {
187 if (!mAlreadyTried) { // This was our first connection atte mpt.
188 Log.w("auth", "Unable to authenticate with (expired?) to ken");
189
190 // Ask system to renew the auth token in case it expired .
191 AccountManager authenticator = AccountManager.get(mUi);
192 authenticator.invalidateAuthToken(mAccount.type, mToken) ;
193 Log.i("auth", "Requesting auth token renewal");
194 authenticator.getAuthToken(
195 mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork );
196
197 // We're not in an error state *yet.*
198 return;
199 } else { // Authentication truly failed.
200 Log.e("auth", "Fresh auth token was also rejected");
201 explanation = getString(R.string.error_auth_failed);
202 }
203 } else if (ex instanceof JSONException) {
204 explanation = getString(R.string.error_unexpected_response);
205 }
206
207 Log.w("auth", ex);
208 Toast.makeText(mUi, explanation, Toast.LENGTH_LONG).show();
209
210 // Close the application.
211 finish();
212 }
213 }
214 }
215
216 /** Formats the host list and offers it to the user. */
217 private class HostListDisplayer implements Runnable {
218 /** Communication with the screen. */
219 private Activity mUi;
220
221 /** Constructor. */
222 public HostListDisplayer(Activity ui) {
223 mUi = ui;
224 }
225
226 /**
227 * Updates the infotext and host list display.
228 * This method affects the UI and must be run on its same thread.
229 */
230 @Override
231 public void run() {
232 mGreeting.setText(getString(R.string.inst_host_list));
233
234 ArrayAdapter<JSONObject> displayer = new HostListAdapter(mUi, R.layo ut.host);
235 Log.i("hostlist", "About to populate host list display");
236 try {
237 int index = 0;
238 while (!mHosts.isNull(index)) {
239 displayer.add(mHosts.getJSONObject(index));
240 ++index;
241 }
242 mList.setAdapter(displayer);
243 }
244 catch(JSONException ex) {
245 Log.w("hostlist", ex);
246 Toast.makeText(
247 mUi, getString(R.string.error_cataloging_hosts), Toast.L ENGTH_LONG).show();
248
249 // Close the application.
250 finish();
251 }
252 }
253 }
254
255 /** Describes the appearance and behavior of each host list entry. */
256 private class HostListAdapter extends ArrayAdapter<JSONObject> {
257 /** Constructor. */
258 public HostListAdapter(Context context, int textViewResourceId) {
259 super(context, textViewResourceId);
260 }
261
262 /** Generates a View corresponding to this particular host. */
263 @Override
264 public View getView(int position, View convertView, ViewGroup parent) {
265 TextView target = (TextView)super.getView(position, convertView, par ent);
266
267 try {
268 final JSONObject host = getItem(position);
269 target.setText(Html.fromHtml(host.getString("hostName") + " (<fo nt color = \"" +
270 (host.getString("status").equals("ONLINE") ? HOST_COLOR_ ONLINE :
271 HOST_COLOR_OFFLINE) + "\">" + host.getString("status") + "</font>)"));
272
273 if (host.getString("status").equals("ONLINE")) { // Host is onl ine.
274 target.setOnClickListener(new View.OnClickListener() {
275 @Override
276 public void onClick(View v) {
277 try {
278 JniInterface.connectToHost(
279 mAccount.name, mToken, host.getStrin g("jabberId"),
280 host.getString("hostId"), host.getSt ring("publicKey"),
281 new Runnable() {
282 @Override
283 public void run() {
284 // TODO(solb) Start an Activity to d isplay the desktop.
285 }
286 });
287 }
288 catch(JSONException ex) {
289 Log.w("host", ex);
290 Toast.makeText(getContext(),
291 getString(R.string.error_reading_hos t),
292 Toast.LENGTH_LONG).show();
293
294 // Close the application.
295 finish();
296 }
297 }
298 });
299 } else { // Host is offline.
300 // Disallow interaction with this entry.
301 target.setEnabled(false);
302 }
303 }
304 catch(JSONException ex) {
305 Log.w("hostlist", ex);
306 Toast.makeText(getContext(),
307 getString(R.string.error_displaying_host),
308 Toast.LENGTH_LONG).show();
309
310 // Close the application.
311 finish();
312 }
313
314 return target;
315 }
316 }
317 }
OLDNEW
« no previous file with comments | « remoting/android/java/AndroidManifest.xml ('k') | remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698