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