Home | History | Annotate | Download | only in shell
      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          */
     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          */
     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
    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 
    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
    180     protected void onDestroy() {
    181         super.onDestroy();
    182 
    183         if (mDevToolsServer != null) mDevToolsServer.destroy();
    184         mDevToolsServer = null;
    185     }
    186 
    187     @Override
    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
    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
    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
    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
    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
    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      */
    247     public WindowAndroid getWindowAndroid() {
    248         return mWindow;
    249     }
    250 
    251     /**
    252      * @return The {@link ChromeShellTab} that is currently visible.
    253      */
    254     public ChromeShellTab getActiveTab() {
    255         return mTabManager != null ? mTabManager.getCurrentTab() : null;
    256     }
    257 
    258     /**
    259      * @return The ContentViewCore of the active tab.
    260      */
    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
    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
    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
    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 
    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 
    341     private static String getUrlFromIntent(Intent intent) {
    342         return intent != null ? intent.getDataString() : null;
    343     }
    344 
    345     @Override
    346     public boolean shouldShowAppMenu() {
    347         return true;
    348     }
    349 
    350     @Override
    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
    388     public AppMenuHandler getAppMenuHandler() {
    389         return mAppMenuHandler;
    390     }
    391 
    392     @Override
    393     public int getMenuThemeResourceId() {
    394         return R.style.OverflowMenuTheme;
    395     }
    396 
    397     @VisibleForTesting
    398     public static void setActivityWindowAndroidFactory(ActivityWindowAndroidFactory factory) {
    399         sWindowAndroidFactory = factory;
    400     }
    401 
    402     @VisibleForTesting
    403     public static void setAppMenuHandlerFactory(AppMenuHandlerFactory factory) {
    404         sAppMenuHandlerFactory = factory;
    405     }
    406 }
    407