blob: 25037d2e1e86c4369e4d1772a9aba57cdcb74b26 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.customtabs;
import android.content.Intent;
import android.net.Uri;
import android.os.IBinder;
import android.support.customtabs.CustomTabsCallback;
import android.support.customtabs.CustomTabsIntent;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.IntentHandler.ExternalAppId;
import org.chromium.chrome.browser.Tab;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.appmenu.ChromeAppMenuPropertiesDelegate;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel.StateChangeReason;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerDocument;
import org.chromium.chrome.browser.document.BrandColorUtils;
import org.chromium.chrome.browser.tabmodel.SingleTabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
import org.chromium.chrome.browser.toolbar.ToolbarControlContainer;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager;
import org.chromium.content_public.browser.LoadUrlParams;
/**
* The activity for custom tabs. It will be launched on top of a client's task.
*/
public class CustomTabActivity extends ChromeActivity {
private static final String TAG = "cr.CustomTabActivity";
private static CustomTabContentHandler sActiveContentHandler;
private CustomTab mTab;
private FindToolbarManager mFindToolbarManager;
private CustomTabIntentDataProvider mIntentDataProvider;
private IBinder mSession;
private CustomTabContentHandler mCustomTabContentHandler;
// This is to give the right package name while using the client's resources during an
// overridePendingTransition call.
// TODO(ianwen, yusufo): Figure out a solution to extract external resources without having to
// change the package name.
private boolean mShouldOverridePackage;
private boolean mRecordedStartupUma;
/**
* Sets the currently active {@link CustomTabContentHandler} in focus.
* @param contentHandler {@link CustomTabContentHandler} to set.
*/
public static void setActiveContentHandler(CustomTabContentHandler contentHandler) {
sActiveContentHandler = contentHandler;
}
/**
* Used to check whether an incoming intent can be handled by the
* current {@link CustomTabContentHandler}.
* @return Whether the active {@link CustomTabContentHandler} has handled the intent.
*/
public static boolean handleInActiveContentIfNeeded(Intent intent) {
if (sActiveContentHandler == null) return false;
if (sActiveContentHandler.shouldIgnoreIntent(intent)) {
Log.w(TAG, "Incoming intent to Custom Tab was ignored.");
return false;
}
IBinder session = IntentUtils.safeGetBinderExtra(intent, CustomTabsIntent.EXTRA_SESSION);
if (session == null || !session.equals(sActiveContentHandler.getSession())) return false;
String url = IntentHandler.getUrlFromIntent(intent);
if (TextUtils.isEmpty(url)) return false;
sActiveContentHandler.loadUrlAndTrackFromTimestamp(new LoadUrlParams(url),
IntentHandler.getTimestampFromIntent(intent));
return true;
}
@Override
public void onStart() {
super.onStart();
CustomTabsConnection.getInstance(getApplication())
.keepAliveForSession(mIntentDataProvider.getSession(),
mIntentDataProvider.getKeepAliveServiceIntent());
}
@Override
public void onStop() {
super.onStop();
CustomTabsConnection.getInstance(getApplication())
.dontKeepAliveForSession(mIntentDataProvider.getSession());
}
@Override
public void preInflationStartup() {
super.preInflationStartup();
setTabModelSelector(new SingleTabModelSelector(this, false, true) {
@Override
public Tab openNewTab(LoadUrlParams loadUrlParams, TabLaunchType type, Tab parent,
boolean incognito) {
// A custom tab either loads a url or starts an external activity to handle the url.
// It never opens a new tab/chrome activity.
mTab.loadUrl(loadUrlParams);
return mTab;
}
});
mIntentDataProvider = new CustomTabIntentDataProvider(getIntent(), this);
supportRequestWindowFeature(Window.FEATURE_ACTION_MODE_OVERLAY);
}
@Override
public void postInflationStartup() {
super.postInflationStartup();
getToolbarManager().setCloseButtonDrawable(mIntentDataProvider.getCloseButtonDrawable());
getToolbarManager().setShowTitle(mIntentDataProvider.getTitleVisibilityState()
== CustomTabIntentDataProvider.SHOW_PAGE_TITLE);
int toolbarColor = mIntentDataProvider.getToolbarColor();
getToolbarManager().updatePrimaryColor(toolbarColor);
if (toolbarColor != getResources().getColor(R.color.default_primary_color)) {
ApiCompatibilityUtils.setStatusBarColor(getWindow(),
BrandColorUtils.computeStatusBarColor(toolbarColor));
}
// Setting task title and icon to be null will preserve the client app's title and icon.
ApiCompatibilityUtils.setTaskDescription(this, null, null, toolbarColor);
if (mIntentDataProvider.shouldShowActionButton()) {
getToolbarManager().addCustomActionButton(mIntentDataProvider.getActionButtonIcon(),
mIntentDataProvider.getActionButtonDescription(), new OnClickListener() {
@Override
public void onClick(View v) {
mIntentDataProvider.sendButtonPendingIntentWithUrl(
getApplicationContext(), mTab.getUrl());
RecordUserAction.record("CustomTabsCustomActionButtonClick");
}
});
}
}
@Override
public void finishNativeInitialization() {
String url = IntentHandler.getUrlFromIntent(getIntent());
String referrer = IntentHandler.getReferrerUrlIncludingExtraHeaders(getIntent(), this);
mSession = mIntentDataProvider.getSession();
// If extra headers have been passed, cancel any current prerender, as
// prerendering doesn't support extra headers.
if (IntentHandler.getExtraHeadersFromIntent(getIntent()) != null) {
CustomTabsConnection.getInstance(getApplication())
.takePrerenderedUrl(mSession, "", null);
}
mTab = new CustomTab(this, getWindowAndroid(), mSession, url, referrer,
Tab.INVALID_TAB_ID, mIntentDataProvider.shouldEnableUrlBarHiding());
getTabModelSelector().setTab(mTab);
ToolbarControlContainer controlContainer = (ToolbarControlContainer) findViewById(
R.id.control_container);
LayoutManagerDocument layoutDriver = new LayoutManagerDocument(getCompositorViewHolder());
initializeCompositorContent(layoutDriver, findViewById(R.id.url_bar),
(ViewGroup) findViewById(android.R.id.content), controlContainer);
mFindToolbarManager = new FindToolbarManager(this, getTabModelSelector(),
getToolbarManager().getContextualMenuBar().getCustomSelectionActionModeCallback());
getToolbarManager().initializeWithNative(getTabModelSelector(), getFullscreenManager(),
mFindToolbarManager, null, layoutDriver, null, null, null,
new OnClickListener() {
@Override
public void onClick(View v) {
CustomTabActivity.this.finish();
}
});
mTab.setFullscreenManager(getFullscreenManager());
loadUrlInCurrentTab(new LoadUrlParams(url),
IntentHandler.getTimestampFromIntent(getIntent()));
mCustomTabContentHandler = new CustomTabContentHandler() {
@Override
public void loadUrlAndTrackFromTimestamp(LoadUrlParams params, long timestamp) {
loadUrlInCurrentTab(params, timestamp);
}
@Override
public IBinder getSession() {
return mSession;
}
@Override
public boolean shouldIgnoreIntent(Intent intent) {
return mIntentHandler.shouldIgnoreIntent(CustomTabActivity.this, intent);
}
};
super.finishNativeInitialization();
}
@Override
public void onStartWithNative() {
super.onStartWithNative();
setActiveContentHandler(mCustomTabContentHandler);
if (!mRecordedStartupUma) {
mRecordedStartupUma = true;
ExternalAppId externalId =
IntentHandler.determineExternalIntentSource(getPackageName(), getIntent());
RecordHistogram.recordEnumeratedHistogram("CustomTabs.ClientAppId",
externalId.ordinal(), ExternalAppId.INDEX_BOUNDARY.ordinal());
}
}
@Override
public void onPauseWithNative() {
super.onPauseWithNative();
CustomTabsConnection.getInstance(getApplication()).notifyNavigationEvent(
mSession, CustomTabsCallback.TAB_HIDDEN);
}
@Override
public void onStopWithNative() {
super.onStopWithNative();
setActiveContentHandler(null);
}
/**
* Loads the current tab with the given load params. Unlike
* {@link CustomTab#loadUrlAndTrackFromTimestamp(LoadUrlParams, long)}, this method takes client
* referrer and extra headers into account.
*/
private void loadUrlInCurrentTab(LoadUrlParams params, long timeStamp) {
Intent intent = getIntent();
IntentHandler.addReferrerAndHeaders(params, intent, this);
mTab.loadUrlAndTrackFromTimestamp(params, timeStamp);
}
@Override
public void createContextualSearchTab(String searchUrl) {
if (mTab == null) return;
mTab.loadUrl(new LoadUrlParams(searchUrl));
}
@Override
public SingleTabModelSelector getTabModelSelector() {
return (SingleTabModelSelector) super.getTabModelSelector();
}
@Override
protected ChromeAppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new CustomTabAppMenuPropertiesDelegate(this, mIntentDataProvider.getMenuTitles());
}
@Override
protected int getAppMenuLayoutId() {
return R.menu.custom_tabs_menu;
}
@Override
protected int getControlContainerLayoutId() {
return R.layout.custom_tabs_control_container;
}
@Override
protected int getControlContainerHeightResource() {
return R.dimen.custom_tabs_control_container_height;
}
@Override
public String getPackageName() {
if (mShouldOverridePackage) return mIntentDataProvider.getClientPackageName();
return super.getPackageName();
}
@Override
public void finish() {
super.finish();
if (mIntentDataProvider.shouldAnimateOnFinish()) {
mShouldOverridePackage = true;
overridePendingTransition(mIntentDataProvider.getAnimationEnterRes(),
mIntentDataProvider.getAnimationExitRes());
mShouldOverridePackage = false;
}
}
@Override
protected boolean handleBackPressed() {
if (mTab == null) return false;
if (mTab.canGoBack()) {
mTab.goBack();
} else {
finish();
}
return true;
}
@Override
public boolean shouldShowAppMenu() {
return mTab != null && getToolbarManager().isInitialized();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuIndex = getAppMenuPropertiesDelegate().getIndexOfMenuItem(item);
if (menuIndex >= 0) {
mIntentDataProvider.clickMenuItemWithUrl(getApplicationContext(), menuIndex,
getTabModelSelector().getCurrentTab().getUrl());
RecordUserAction.record("CustomTabsMenuCustomMenuItem");
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onMenuOrKeyboardAction(int id, boolean fromMenu) {
if (id == R.id.show_menu) {
if (shouldShowAppMenu()) {
getAppMenuHandler().showAppMenu(getToolbarManager().getMenuAnchor(), true, false);
return true;
}
} else if (id == R.id.open_in_chrome_id) {
String url = getTabModelSelector().getCurrentTab().getUrl();
Intent chromeIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
chromeIntent.setPackage(getApplicationContext().getPackageName());
chromeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(chromeIntent);
RecordUserAction.record("CustomTabsMenuOpenInChrome");
return true;
} else if (id == R.id.find_in_page_id) {
mFindToolbarManager.showToolbar();
if (getContextualSearchManager() != null) {
getContextualSearchManager().hideContextualSearch(StateChangeReason.UNKNOWN);
}
if (fromMenu) {
RecordUserAction.record("MobileMenuFindInPage");
} else {
RecordUserAction.record("MobileShortcutFindInPage");
}
return true;
}
return super.onMenuOrKeyboardAction(id, fromMenu);
}
/**
* @return The {@link AppMenuPropertiesDelegate} associated with this activity. For test
* purposes only.
*/
@VisibleForTesting
@Override
public CustomTabAppMenuPropertiesDelegate getAppMenuPropertiesDelegate() {
return (CustomTabAppMenuPropertiesDelegate) super.getAppMenuPropertiesDelegate();
}
/**
* @return The {@link CustomTabIntentDataProvider} for this {@link CustomTabActivity}. For test
* purposes only.
*/
@VisibleForTesting
CustomTabIntentDataProvider getIntentDataProvider() {
return mIntentDataProvider;
}
}