ChromeShellActivity.java revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
1// Copyright 2014 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 5package org.chromium.chrome.shell; 6 7import android.app.Activity; 8import android.content.Intent; 9import android.os.Bundle; 10import android.text.TextUtils; 11import android.util.Log; 12import android.view.KeyEvent; 13import android.view.Menu; 14import android.view.MenuItem; 15import android.view.View; 16import android.widget.Toast; 17 18import com.google.common.annotations.VisibleForTesting; 19 20import org.chromium.base.ApiCompatibilityUtils; 21import org.chromium.base.BaseSwitches; 22import org.chromium.base.CommandLine; 23import org.chromium.base.MemoryPressureListener; 24import org.chromium.base.library_loader.ProcessInitException; 25import org.chromium.chrome.browser.DevToolsServer; 26import org.chromium.chrome.browser.appmenu.AppMenuHandler; 27import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; 28import org.chromium.chrome.browser.printing.PrintingControllerFactory; 29import org.chromium.chrome.browser.printing.TabPrinter; 30import org.chromium.chrome.browser.share.ShareHelper; 31import org.chromium.chrome.shell.sync.SyncController; 32import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; 33import org.chromium.content.browser.ActivityContentVideoViewClient; 34import org.chromium.content.browser.BrowserStartupController; 35import org.chromium.content.browser.ContentView; 36import org.chromium.content.browser.DeviceUtils; 37import org.chromium.content.common.ContentSwitches; 38import org.chromium.printing.PrintManagerDelegateImpl; 39import org.chromium.printing.PrintingController; 40import org.chromium.sync.signin.AccountManagerHelper; 41import org.chromium.sync.signin.ChromeSigninController; 42import org.chromium.ui.base.ActivityWindowAndroid; 43import org.chromium.ui.base.WindowAndroid; 44 45/** 46 * The {@link android.app.Activity} component of a basic test shell to test Chrome features. 47 */ 48public class ChromeShellActivity extends Activity implements AppMenuPropertiesDelegate { 49 private static final String TAG = "ChromeShellActivity"; 50 private static final String CHROME_DISTILLER_SCHEME = "chrome-distiller"; 51 52 /** 53 * Factory used to set up a mock ActivityWindowAndroid for testing. 54 */ 55 public interface ActivityWindowAndroidFactory { 56 /** 57 * @return ActivityWindowAndroid for the given activity. 58 */ 59 public ActivityWindowAndroid getActivityWindowAndroid(Activity activity); 60 } 61 62 private static ActivityWindowAndroidFactory sWindowAndroidFactory = 63 new ActivityWindowAndroidFactory() { 64 @Override 65 public ActivityWindowAndroid getActivityWindowAndroid(Activity activity) { 66 return new ActivityWindowAndroid(activity); 67 } 68 }; 69 70 private WindowAndroid mWindow; 71 private TabManager mTabManager; 72 private DevToolsServer mDevToolsServer; 73 private SyncController mSyncController; 74 private PrintingController mPrintingController; 75 76 /** 77 * Factory used to set up a mock AppMenuHandler for testing. 78 */ 79 public interface AppMenuHandlerFactory { 80 /** 81 * @return AppMenuHandler for the given activity and menu resource id. 82 */ 83 public AppMenuHandler getAppMenuHandler(Activity activity, 84 AppMenuPropertiesDelegate delegate, int menuResourceId); 85 } 86 87 private static AppMenuHandlerFactory sAppMenuHandlerFactory = 88 new AppMenuHandlerFactory() { 89 @Override 90 public AppMenuHandler getAppMenuHandler(Activity activity, 91 AppMenuPropertiesDelegate delegate, int menuResourceId) { 92 return new AppMenuHandler(activity, delegate, menuResourceId); 93 } 94 }; 95 private AppMenuHandler mAppMenuHandler; 96 97 @Override 98 protected void onCreate(final Bundle savedInstanceState) { 99 super.onCreate(savedInstanceState); 100 101 ChromeShellApplication.initCommandLine(); 102 waitForDebuggerIfNeeded(); 103 104 DeviceUtils.addDeviceSpecificUserAgentSwitch(this); 105 106 BrowserStartupController.StartupCallback callback = 107 new BrowserStartupController.StartupCallback() { 108 @Override 109 public void onSuccess(boolean alreadyStarted) { 110 finishInitialization(savedInstanceState); 111 } 112 113 @Override 114 public void onFailure() { 115 Toast.makeText(ChromeShellActivity.this, 116 R.string.browser_process_initialization_failed, 117 Toast.LENGTH_SHORT).show(); 118 Log.e(TAG, "Chromium browser process initialization failed"); 119 finish(); 120 } 121 }; 122 try { 123 BrowserStartupController.get(this).startBrowserProcessesAsync(callback); 124 } catch (ProcessInitException e) { 125 Log.e(TAG, "Unable to load native library.", e); 126 System.exit(-1); 127 } 128 } 129 130 private void finishInitialization(final Bundle savedInstanceState) { 131 setContentView(R.layout.chrome_shell_activity); 132 mTabManager = (TabManager) findViewById(R.id.tab_manager); 133 134 mWindow = sWindowAndroidFactory.getActivityWindowAndroid(this); 135 mWindow.restoreInstanceState(savedInstanceState); 136 mTabManager.initialize(mWindow, new ActivityContentVideoViewClient(this) { 137 @Override 138 public void onShowCustomView(View view) { 139 super.onShowCustomView(view); 140 if (!CommandLine.getInstance().hasSwitch( 141 ContentSwitches.DISABLE_OVERLAY_FULLSCREEN_VIDEO_SUBTITLE)) { 142 mTabManager.setOverlayVideoMode(true); 143 } 144 } 145 146 @Override 147 public void onDestroyContentVideoView() { 148 super.onDestroyContentVideoView(); 149 if (!CommandLine.getInstance().hasSwitch( 150 ContentSwitches.DISABLE_OVERLAY_FULLSCREEN_VIDEO_SUBTITLE)) { 151 mTabManager.setOverlayVideoMode(false); 152 } 153 } 154 }); 155 156 String startupUrl = getUrlFromIntent(getIntent()); 157 if (!TextUtils.isEmpty(startupUrl)) { 158 mTabManager.setStartupUrl(startupUrl); 159 } 160 ChromeShellToolbar mToolbar = (ChromeShellToolbar) findViewById(R.id.toolbar); 161 mAppMenuHandler = sAppMenuHandlerFactory.getAppMenuHandler(this, this, R.menu.main_menu); 162 mToolbar.setMenuHandler(mAppMenuHandler); 163 164 mDevToolsServer = new DevToolsServer("chrome_shell"); 165 mDevToolsServer.setRemoteDebuggingEnabled(true); 166 167 mPrintingController = PrintingControllerFactory.create(this); 168 169 mSyncController = SyncController.get(this); 170 // In case this method is called after the first onStart(), we need to inform the 171 // SyncController that we have started. 172 mSyncController.onStart(); 173 } 174 175 @Override 176 protected void onDestroy() { 177 super.onDestroy(); 178 179 if (mDevToolsServer != null) mDevToolsServer.destroy(); 180 mDevToolsServer = null; 181 } 182 183 @Override 184 protected void onSaveInstanceState(Bundle outState) { 185 // TODO(dtrainor): Save/restore the tab state. 186 if (mWindow != null) mWindow.saveInstanceState(outState); 187 } 188 189 @Override 190 public boolean onKeyUp(int keyCode, KeyEvent event) { 191 if (keyCode == KeyEvent.KEYCODE_BACK) { 192 ChromeShellTab tab = getActiveTab(); 193 if (tab != null && tab.canGoBack()) { 194 tab.goBack(); 195 return true; 196 } 197 } 198 199 return super.onKeyUp(keyCode, event); 200 } 201 202 @Override 203 protected void onNewIntent(Intent intent) { 204 if (MemoryPressureListener.handleDebugIntent(this, intent.getAction())) return; 205 206 String url = getUrlFromIntent(intent); 207 if (!TextUtils.isEmpty(url)) { 208 ChromeShellTab tab = getActiveTab(); 209 if (tab != null) tab.loadUrlWithSanitization(url); 210 } 211 } 212 213 @Override 214 protected void onStop() { 215 super.onStop(); 216 217 ContentView view = getActiveContentView(); 218 if (view != null) view.onHide(); 219 } 220 221 @Override 222 protected void onStart() { 223 super.onStart(); 224 225 ContentView view = getActiveContentView(); 226 if (view != null) view.onShow(); 227 228 if (mSyncController != null) { 229 mSyncController.onStart(); 230 } 231 } 232 233 @Override 234 public void onActivityResult(int requestCode, int resultCode, Intent data) { 235 mWindow.onActivityResult(requestCode, resultCode, data); 236 } 237 238 /** 239 * @return The {@link WindowAndroid} associated with this activity. 240 */ 241 public WindowAndroid getWindowAndroid() { 242 return mWindow; 243 } 244 245 /** 246 * @return The {@link ChromeShellTab} that is currently visible. 247 */ 248 public ChromeShellTab getActiveTab() { 249 return mTabManager != null ? mTabManager.getCurrentTab() : null; 250 } 251 252 /** 253 * @return The ContentView of the active tab. 254 */ 255 public ContentView getActiveContentView() { 256 ChromeShellTab tab = getActiveTab(); 257 return tab != null ? tab.getContentView() : null; 258 } 259 260 /** 261 * Creates a {@link ChromeShellTab} with a URL specified by {@code url}. 262 * 263 * @param url The URL the new {@link ChromeShellTab} should start with. 264 */ 265 @VisibleForTesting 266 public void createTab(String url) { 267 mTabManager.createTab(url); 268 } 269 270 /** 271 * Override the menu key event to show AppMenu. 272 */ 273 @Override 274 public boolean onKeyDown(int keyCode, KeyEvent event) { 275 if (keyCode == KeyEvent.KEYCODE_MENU && event.getRepeatCount() == 0) { 276 mAppMenuHandler.showAppMenu(findViewById(R.id.menu_button), true, false); 277 return true; 278 } 279 return super.onKeyDown(keyCode, event); 280 } 281 282 @Override 283 public boolean onOptionsItemSelected(MenuItem item) { 284 ChromeShellTab activeTab = getActiveTab(); 285 switch (item.getItemId()) { 286 case R.id.signin: 287 if (ChromeSigninController.get(this).isSignedIn()) { 288 SyncController.openSignOutDialog(getFragmentManager()); 289 } else if (AccountManagerHelper.get(this).hasGoogleAccounts()) { 290 SyncController.openSigninDialog(getFragmentManager()); 291 } else { 292 Toast.makeText(this, R.string.signin_no_account, Toast.LENGTH_SHORT).show(); 293 } 294 return true; 295 case R.id.print: 296 if (activeTab != null) { 297 mPrintingController.startPrint(new TabPrinter(activeTab), 298 new PrintManagerDelegateImpl(this)); 299 } 300 return true; 301 case R.id.distill_page: 302 if (activeTab != null) { 303 String viewUrl = DomDistillerUrlUtils.getDistillerViewUrlFromUrl( 304 CHROME_DISTILLER_SCHEME, activeTab.getUrl()); 305 activeTab.loadUrlWithSanitization(viewUrl); 306 } 307 return true; 308 case R.id.back_menu_id: 309 if (activeTab != null && activeTab.canGoBack()) { 310 activeTab.goBack(); 311 } 312 return true; 313 case R.id.forward_menu_id: 314 if (activeTab != null && activeTab.canGoForward()) { 315 activeTab.goForward(); 316 } 317 return true; 318 case R.id.share_menu_id: 319 case R.id.direct_share_menu_id: 320 ShareHelper.share(item.getItemId() == R.id.direct_share_menu_id, this, 321 activeTab.getTitle(), activeTab.getUrl(), null); 322 return true; 323 default: 324 return super.onOptionsItemSelected(item); 325 } 326 } 327 328 private void waitForDebuggerIfNeeded() { 329 if (CommandLine.getInstance().hasSwitch(BaseSwitches.WAIT_FOR_JAVA_DEBUGGER)) { 330 Log.e(TAG, "Waiting for Java debugger to connect..."); 331 android.os.Debug.waitForDebugger(); 332 Log.e(TAG, "Java debugger connected. Resuming execution."); 333 } 334 } 335 336 private static String getUrlFromIntent(Intent intent) { 337 return intent != null ? intent.getDataString() : null; 338 } 339 340 @Override 341 public boolean shouldShowAppMenu() { 342 return true; 343 } 344 345 @Override 346 public void prepareMenu(Menu menu) { 347 menu.setGroupVisible(R.id.MAIN_MENU, true); 348 ChromeShellTab activeTab = getActiveTab(); 349 350 // Disable the "Back" menu item if there is no page to go to. 351 MenuItem backMenuItem = menu.findItem(R.id.back_menu_id); 352 backMenuItem.setEnabled(activeTab != null ? activeTab.canGoBack() : false); 353 354 // Disable the "Forward" menu item if there is no page to go to. 355 MenuItem forwardMenuItem = menu.findItem(R.id.forward_menu_id); 356 forwardMenuItem.setEnabled(activeTab != null ? activeTab.canGoForward() : false); 357 358 // ChromeShell does not know about bookmarks yet 359 menu.findItem(R.id.bookmark_this_page_id).setEnabled(true); 360 361 MenuItem signinItem = menu.findItem(R.id.signin); 362 if (ChromeSigninController.get(this).isSignedIn()) { 363 signinItem.setTitle(ChromeSigninController.get(this).getSignedInAccountName()); 364 } else { 365 signinItem.setTitle(R.string.signin_sign_in); 366 } 367 368 menu.findItem(R.id.print).setVisible(ApiCompatibilityUtils.isPrintingSupported()); 369 370 MenuItem distillPageItem = menu.findItem(R.id.distill_page); 371 if (CommandLine.getInstance().hasSwitch(ChromeShellSwitches.ENABLE_DOM_DISTILLER)) { 372 String url = activeTab != null ? activeTab.getUrl() : null; 373 distillPageItem.setEnabled(!DomDistillerUrlUtils.isUrlReportable( 374 CHROME_DISTILLER_SCHEME, url)); 375 distillPageItem.setVisible(true); 376 } else { 377 distillPageItem.setVisible(false); 378 } 379 ShareHelper.configureDirectShareMenuItem(this, menu.findItem(R.id.direct_share_menu_id)); 380 } 381 382 @VisibleForTesting 383 public AppMenuHandler getAppMenuHandler() { 384 return mAppMenuHandler; 385 } 386 387 @Override 388 public int getMenuThemeResourceId() { 389 return android.R.style.Theme_Holo_Light; 390 } 391 392 @VisibleForTesting 393 public static void setActivityWindowAndroidFactory(ActivityWindowAndroidFactory factory) { 394 sWindowAndroidFactory = factory; 395 } 396 397 @VisibleForTesting 398 public static void setAppMenuHandlerFactory(AppMenuHandlerFactory factory) { 399 sAppMenuHandlerFactory = factory; 400 } 401} 402