| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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.blimp; | |
| 6 | |
| 7 import android.annotation.SuppressLint; | |
| 8 import android.app.Activity; | |
| 9 import android.content.Intent; | |
| 10 import android.os.Bundle; | |
| 11 import android.os.Handler; | |
| 12 import android.text.TextUtils; | |
| 13 import android.view.View; | |
| 14 import android.widget.TextView; | |
| 15 | |
| 16 import org.chromium.base.CommandLine; | |
| 17 import org.chromium.base.Log; | |
| 18 import org.chromium.base.annotations.SuppressFBWarnings; | |
| 19 import org.chromium.base.library_loader.ProcessInitException; | |
| 20 import org.chromium.blimp.auth.RetryingTokenSource; | |
| 21 import org.chromium.blimp.auth.TokenSource; | |
| 22 import org.chromium.blimp.auth.TokenSourceImpl; | |
| 23 import org.chromium.blimp.core.BlimpClientSwitches; | |
| 24 import org.chromium.blimp.preferences.PreferencesUtil; | |
| 25 import org.chromium.blimp.session.BlimpClientSession; | |
| 26 import org.chromium.blimp.session.EngineInfo; | |
| 27 import org.chromium.blimp.session.TabControlFeature; | |
| 28 import org.chromium.blimp.toolbar.Toolbar; | |
| 29 import org.chromium.blimp.toolbar.ToolbarMenu; | |
| 30 import org.chromium.ui.base.WindowAndroid; | |
| 31 import org.chromium.ui.widget.Toast; | |
| 32 | |
| 33 /** | |
| 34 * The {@link Activity} for rendering the main Blimp client. This loads the Bli
mp rendering stack | |
| 35 * and displays it. | |
| 36 */ | |
| 37 public class BlimpRendererActivity | |
| 38 extends Activity implements BlimpLibraryLoader.Callback, TokenSource.Cal
lback, | |
| 39 BlimpClientSession.ConnectionObserver, | |
| 40 ToolbarMenu.ToolbarMenuDelegate, Toolbar.Too
lbarDelegate { | |
| 41 private static final int ACCOUNT_CHOOSER_INTENT_REQUEST_CODE = 100; | |
| 42 private static final String TAG = "BlimpRendererActivity"; | |
| 43 | |
| 44 // Refresh interval for the debug view in milliseconds. | |
| 45 private static final int DEBUG_VIEW_REFRESH_INTERVAL = 1000; | |
| 46 private static final int BYTES_PER_KILO = 1024; | |
| 47 | |
| 48 /** Provides user authentication tokens that can be used to query for engine
assignments. This | |
| 49 * can potentially query GoogleAuthUtil for an OAuth2 authentication token
with userinfo.email | |
| 50 * privileges for a chosen Android account. */ | |
| 51 private TokenSource mTokenSource; | |
| 52 | |
| 53 private BlimpView mBlimpView; | |
| 54 private Toolbar mToolbar; | |
| 55 private BlimpClientSession mBlimpClientSession; | |
| 56 private TabControlFeature mTabControlFeature; | |
| 57 private WindowAndroid mWindowAndroid; | |
| 58 | |
| 59 private Handler mHandler = new Handler(); | |
| 60 | |
| 61 private boolean mFirstUrlLoadDone = false; | |
| 62 | |
| 63 // Flag to record the base value of the metrics when the debug view is turne
d on. | |
| 64 private boolean mStatsBaseRecorded = false; | |
| 65 private int mSentBase; | |
| 66 private int mReceivedBase; | |
| 67 private int mCommitsBase; | |
| 68 private int mSent; | |
| 69 private int mReceived; | |
| 70 private int mCommits; | |
| 71 private String mToken = null; | |
| 72 | |
| 73 @Override | |
| 74 @SuppressFBWarnings("DM_EXIT") // FindBugs doesn't like System.exit(). | |
| 75 protected void onCreate(Bundle savedInstanceState) { | |
| 76 super.onCreate(savedInstanceState); | |
| 77 buildAndTriggerTokenSourceIfNeeded(); | |
| 78 try { | |
| 79 BlimpLibraryLoader.startAsync(this); | |
| 80 } catch (ProcessInitException e) { | |
| 81 Log.e(TAG, "Native startup exception", e); | |
| 82 System.exit(-1); | |
| 83 return; | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 @Override | |
| 88 protected void onDestroy() { | |
| 89 if (mTabControlFeature != null) { | |
| 90 mTabControlFeature.destroy(); | |
| 91 mTabControlFeature = null; | |
| 92 } | |
| 93 | |
| 94 if (mBlimpView != null) { | |
| 95 mBlimpView.destroyRenderer(); | |
| 96 mBlimpView = null; | |
| 97 } | |
| 98 | |
| 99 if (mToolbar != null) { | |
| 100 mToolbar.destroy(); | |
| 101 mToolbar = null; | |
| 102 } | |
| 103 | |
| 104 if (mTokenSource != null) { | |
| 105 mTokenSource.destroy(); | |
| 106 mTokenSource = null; | |
| 107 } | |
| 108 | |
| 109 // Destroy the BlimpClientSession last, as all other features may rely o
n it. | |
| 110 if (mBlimpClientSession != null) { | |
| 111 mBlimpClientSession.removeObserver(this); | |
| 112 mBlimpClientSession.destroy(); | |
| 113 mBlimpClientSession = null; | |
| 114 } | |
| 115 | |
| 116 super.onDestroy(); | |
| 117 } | |
| 118 | |
| 119 @Override | |
| 120 protected void onActivityResult(int requestCode, int resultCode, Intent data
) { | |
| 121 switch (requestCode) { | |
| 122 case ACCOUNT_CHOOSER_INTENT_REQUEST_CODE: | |
| 123 if (resultCode == RESULT_OK) { | |
| 124 mTokenSource.onAccountSelected(data); | |
| 125 mTokenSource.getToken(); | |
| 126 } else { | |
| 127 onTokenUnavailable(false); | |
| 128 } | |
| 129 break; | |
| 130 default: | |
| 131 break; | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 @Override | |
| 136 public void onBackPressed() { | |
| 137 // Check if the toolbar can handle the back navigation. | |
| 138 if (mToolbar != null && mToolbar.onBackPressed()) return; | |
| 139 | |
| 140 // If not, use the default Activity behavior. | |
| 141 super.onBackPressed(); | |
| 142 } | |
| 143 | |
| 144 // BlimpLibraryLoader.Callback implementation. | |
| 145 @Override | |
| 146 public void onStartupComplete(boolean success) { | |
| 147 if (!success) { | |
| 148 Log.e(TAG, "Native startup failed"); | |
| 149 finish(); | |
| 150 return; | |
| 151 } | |
| 152 | |
| 153 setContentView(R.layout.blimp_main); | |
| 154 | |
| 155 mWindowAndroid = new WindowAndroid(BlimpRendererActivity.this); | |
| 156 mBlimpClientSession = | |
| 157 new BlimpClientSession(PreferencesUtil.findAssignerUrl(this), mW
indowAndroid); | |
| 158 mBlimpClientSession.addObserver(this); | |
| 159 | |
| 160 mBlimpView = (BlimpView) findViewById(R.id.renderer); | |
| 161 mBlimpView.initializeRenderer(mBlimpClientSession); | |
| 162 | |
| 163 mToolbar = (Toolbar) findViewById(R.id.toolbar); | |
| 164 mToolbar.initialize(mBlimpClientSession, this); | |
| 165 | |
| 166 mTabControlFeature = new TabControlFeature(mBlimpClientSession, mBlimpVi
ew); | |
| 167 | |
| 168 handleUrlFromIntent(getIntent()); | |
| 169 | |
| 170 // If Blimp client has command line flag "engine-ip", client will use th
e command line token | |
| 171 // to connect. See GetAssignmentFromCommandLine() in | |
| 172 // blimp/client/session/assignment_source.cc | |
| 173 // In normal cases, where client uses the engine ip given by the Assigne
r, | |
| 174 // connection to the engine is triggered by the successful retrieval of
a token as | |
| 175 // TokenSource.Callback. | |
| 176 if (CommandLine.getInstance().hasSwitch(BlimpClientSwitches.ENGINE_IP))
{ | |
| 177 mBlimpClientSession.connect(null); | |
| 178 } else { | |
| 179 if (mToken != null) { | |
| 180 mBlimpClientSession.connect(mToken); | |
| 181 mToken = null; | |
| 182 } | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 // ToolbarMenu.ToolbarMenuDelegate implementation. | |
| 187 @Override | |
| 188 public void showDebugView(boolean show) { | |
| 189 View debugView = findViewById(R.id.debug_stats); | |
| 190 debugView.setVisibility(show ? View.VISIBLE : View.INVISIBLE); | |
| 191 if (show) { | |
| 192 Runnable debugStatsRunnable = new Runnable() { | |
| 193 @Override | |
| 194 public void run() { | |
| 195 if (mToolbar.getToolbarMenu().isDebugInfoEnabled()) { | |
| 196 int[] metrics = mBlimpClientSession.getDebugStats(); | |
| 197 updateDebugStats(metrics); | |
| 198 mHandler.postDelayed(this, DEBUG_VIEW_REFRESH_INTERVAL); | |
| 199 } | |
| 200 } | |
| 201 }; | |
| 202 debugStatsRunnable.run(); | |
| 203 } else { | |
| 204 mStatsBaseRecorded = false; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 @Override | |
| 209 protected void onNewIntent(Intent intent) { | |
| 210 super.onNewIntent(intent); | |
| 211 handleUrlFromIntent(intent); | |
| 212 } | |
| 213 | |
| 214 /** | |
| 215 * Retrieve the URL from the Intent. | |
| 216 * @param intent Intent to examine. | |
| 217 * @return URL from the Intent, or null if a valid URL couldn't be found. | |
| 218 */ | |
| 219 private String getUrlFromIntent(Intent intent) { | |
| 220 if (intent == null) return null; | |
| 221 | |
| 222 String url = intent.getDataString(); | |
| 223 if (url == null) return null; | |
| 224 | |
| 225 url = url.trim(); | |
| 226 return TextUtils.isEmpty(url) ? null : url; | |
| 227 } | |
| 228 | |
| 229 /** | |
| 230 * Retrieves an URL from an Intent and loads it in the browser. | |
| 231 * If the toolbar already has an URL and the new intent doesn't have an URL
(e.g. bringing back | |
| 232 * from background), the intent gets ignored. | |
| 233 * @param intent Intent that contains the URL. | |
| 234 */ | |
| 235 private void handleUrlFromIntent(Intent intent) { | |
| 236 // TODO(shaktisahu): On a slow device, this might happen. Load the corre
ct URL once the | |
| 237 // toolbar loading is complete (crbug/601226) | |
| 238 if (mToolbar == null) return; | |
| 239 | |
| 240 String url = getUrlFromIntent(intent); | |
| 241 if (mFirstUrlLoadDone && url == null) return; | |
| 242 mFirstUrlLoadDone = true; | |
| 243 | |
| 244 mToolbar.loadUrl(url == null ? "http://www.google.com/" : url); | |
| 245 } | |
| 246 | |
| 247 // TokenSource.Callback implementation. | |
| 248 @Override | |
| 249 public void onTokenReceived(String token) { | |
| 250 if (mBlimpClientSession != null) { | |
| 251 mBlimpClientSession.connect(token); | |
| 252 } else { | |
| 253 mToken = token; | |
| 254 } | |
| 255 } | |
| 256 | |
| 257 @Override | |
| 258 public void onTokenUnavailable(boolean isTransient) { | |
| 259 // Ignore isTransient here because we're relying on the auto-retry Token
Source. | |
| 260 // TODO(dtrainor): Show a better error dialog/message. | |
| 261 Toast.makeText(this, R.string.signin_get_token_failed, Toast.LENGTH_LONG
).show(); | |
| 262 } | |
| 263 | |
| 264 @Override | |
| 265 public void onNeedsAccountToBeSelected(Intent suggestedIntent) { | |
| 266 startActivityForResult(suggestedIntent, ACCOUNT_CHOOSER_INTENT_REQUEST_C
ODE); | |
| 267 } | |
| 268 | |
| 269 // BlimpClientSession.ConnectionObserver interface. | |
| 270 @Override | |
| 271 public void onAssignmentReceived( | |
| 272 int result, int suggestedMessageResourceId, EngineInfo engineInfo) { | |
| 273 Toast.makeText(this, suggestedMessageResourceId, Toast.LENGTH_LONG).show
(); | |
| 274 } | |
| 275 | |
| 276 @Override | |
| 277 public void onConnected() { | |
| 278 Toast.makeText(this, R.string.network_connected, Toast.LENGTH_SHORT).sho
w(); | |
| 279 } | |
| 280 | |
| 281 /** | |
| 282 * Displays debug metrics up to one decimal place. | |
| 283 */ | |
| 284 @Override | |
| 285 @SuppressLint("DefaultLocale") | |
| 286 public void updateDebugStatsUI(int received, int sent, int commits) { | |
| 287 TextView tv = (TextView) findViewById(R.id.bytes_received_client); | |
| 288 tv.setText(String.format("%.1f", (float) received / BYTES_PER_KILO)); | |
| 289 tv = (TextView) findViewById(R.id.bytes_sent_client); | |
| 290 tv.setText(String.format("%.1f", (float) sent / BYTES_PER_KILO)); | |
| 291 tv = (TextView) findViewById(R.id.commit_count); | |
| 292 tv.setText(String.valueOf(commits)); | |
| 293 } | |
| 294 | |
| 295 private void updateDebugStats(int[] metrics) { | |
| 296 assert metrics.length == 3; | |
| 297 mReceived = metrics[0]; | |
| 298 mSent = metrics[1]; | |
| 299 mCommits = metrics[2]; | |
| 300 if (!mStatsBaseRecorded) { | |
| 301 mReceivedBase = mReceived; | |
| 302 mSentBase = mSent; | |
| 303 mCommitsBase = mCommits; | |
| 304 mStatsBaseRecorded = true; | |
| 305 } | |
| 306 updateDebugStatsUI(mReceived - mReceivedBase, mSent - mSentBase, mCommit
s - mCommitsBase); | |
| 307 } | |
| 308 | |
| 309 // Toolbar.ToolbarDelegate interface. | |
| 310 @Override | |
| 311 public void resetDebugStats() { | |
| 312 mReceivedBase = mReceived; | |
| 313 mSentBase = mSent; | |
| 314 mCommitsBase = mCommits; | |
| 315 } | |
| 316 | |
| 317 @Override | |
| 318 public void onDisconnected(String reason) { | |
| 319 Toast.makeText(this, | |
| 320 String.format(getResources().getString(R.string.network_dis
connected), reason), | |
| 321 Toast.LENGTH_LONG) | |
| 322 .show(); | |
| 323 } | |
| 324 | |
| 325 private void buildAndTriggerTokenSourceIfNeeded() { | |
| 326 // If Blimp client is given the engine ip by the command line, then ther
e is no need to | |
| 327 // build a TokenSource, because token, engine ip, engine port, and trans
port protocol are | |
| 328 // all given by command line. | |
| 329 if (CommandLine.getInstance().hasSwitch(BlimpClientSwitches.ENGINE_IP))
return; | |
| 330 | |
| 331 // Build a TokenSource that will internally retry accessing the underlyi
ng | |
| 332 // TokenSourceImpl. This will exponentially backoff while it tries to ge
t the access | |
| 333 // token. See {@link RetryingTokenSource} for more information. The un
derlying | |
| 334 // TokenSourceImpl will attempt to query GoogleAuthUtil, but might fail
if there is no | |
| 335 // account selected, in which case it will ask this Activity to show an
account chooser | |
| 336 // and notify it of the selection result. | |
| 337 mTokenSource = new RetryingTokenSource(new TokenSourceImpl(this)); | |
| 338 mTokenSource.setCallback(this); | |
| 339 mTokenSource.getToken(); | |
| 340 } | |
| 341 } | |
| OLD | NEW |