| // Copyright 2019 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.weblayer_private; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.provider.Settings; |
| import android.view.View; |
| import android.webkit.ValueCallback; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.fragment.app.FragmentManager; |
| |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.components.embedder_support.view.ContentView; |
| import org.chromium.ui.base.DeviceFormFactor; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.weblayer_private.interfaces.APICallException; |
| import org.chromium.weblayer_private.interfaces.BrowserEmbeddabilityMode; |
| import org.chromium.weblayer_private.interfaces.DarkModeStrategy; |
| import org.chromium.weblayer_private.interfaces.IBrowser; |
| import org.chromium.weblayer_private.interfaces.IBrowserClient; |
| import org.chromium.weblayer_private.interfaces.IObjectWrapper; |
| import org.chromium.weblayer_private.interfaces.ITab; |
| import org.chromium.weblayer_private.interfaces.IUrlBarController; |
| import org.chromium.weblayer_private.interfaces.ObjectWrapper; |
| import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; |
| import org.chromium.weblayer_private.media.MediaRouteDialogFragmentImpl; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Implementation of {@link IBrowser}. |
| */ |
| @JNINamespace("weblayer") |
| public class BrowserImpl extends IBrowser.Stub implements View.OnAttachStateChangeListener { |
| private final ObserverList<VisibleSecurityStateObserver> mVisibleSecurityStateObservers = |
| new ObserverList<VisibleSecurityStateObserver>(); |
| |
| // Key used to save the crypto key in instance state. |
| public static final String SAVED_STATE_SESSION_SERVICE_CRYPTO_KEY = |
| "SAVED_STATE_SESSION_SERVICE_CRYPTO_KEY"; |
| |
| // Key used to save the minimal persistence state in instance state. Only used if a persistence |
| // id was not specified. |
| public static final String SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY = |
| "SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY"; |
| |
| // Number of instances that have not been destroyed. |
| private static int sInstanceCount; |
| |
| private long mNativeBrowser; |
| private final ProfileImpl mProfile; |
| private Context mEmbedderActivityContext; |
| private BrowserViewController mViewController; |
| // Used to save UI state between destroyAttachmentState() and createAttachmentState() calls so |
| // it can be preserved during device rotations or other events that cause the Fragment to be |
| // recreated. |
| private BrowserViewController.State mViewControllerState; |
| private FragmentWindowAndroid mWindowAndroid; |
| private IBrowserClient mClient; |
| private LocaleChangedBroadcastReceiver mLocaleReceiver; |
| private boolean mInDestroy; |
| private final UrlBarControllerImpl mUrlBarController; |
| private boolean mFragmentStarted; |
| private boolean mFragmentResumed; |
| |
| // Tracks whether the fragment is in the middle of a configuration change and was attached when |
| // the configuration change started. This is set to true in onFragmentStop() and false when |
| // isViewAttachedToWindow() is true in either onViewAttachedToWindow() or onFragmentStarted(). |
| // It's important to only set this to false when isViewAttachedToWindow() is true, as otherwise |
| // the WebContents may be prematurely hidden. |
| private boolean mInConfigurationChangeAndWasAttached; |
| |
| // If true, the WebContents is forced visible. This value may be changed by the embedder for |
| // temporary detach operations (such as fullscreen or rotations) that should not impact the |
| // visibility of the WebContents (otherwise video may stop). As this value is only temporarily |
| // true, the value is implicitly reset on attach. |
| private boolean mForcedVisible = false; |
| |
| // Cache the value instead of querying system every time. |
| private Boolean mPasswordEchoEnabled; |
| private Boolean mDarkThemeEnabled; |
| @DarkModeStrategy |
| private int mDarkModeStrategy = DarkModeStrategy.WEB_THEME_DARKENING_ONLY; |
| private Float mFontScale; |
| private boolean mViewAttachedToWindow; |
| private boolean mNotifyOnBrowserControlsOffsetsChanged; |
| |
| // Created in the constructor from saved state and used in setClient(). |
| private PersistenceInfo mPersistenceInfo; |
| |
| private int mMinimumSurfaceWidth; |
| private int mMinimumSurfaceHeight; |
| |
| private static final class PersistenceInfo { |
| String mPersistenceId; |
| byte[] mCryptoKey; |
| byte[] mMinimalPersistenceState; |
| }; |
| |
| /** |
| * @param windowAndroid a window that was created by a {@link BrowserFragmentImpl}. It's not |
| * valid to call this method with other {@link WindowAndroid} instances. Typically this |
| * should be the {@link WindowAndroid} of a {@link WebContents}. |
| * @return the associated BrowserImpl instance. |
| */ |
| public static BrowserImpl fromWindowAndroid(WindowAndroid windowAndroid) { |
| assert windowAndroid instanceof FragmentWindowAndroid; |
| return ((FragmentWindowAndroid) windowAndroid).getBrowser(); |
| } |
| |
| /** |
| * Allows observing of visible security state of the active tab. |
| */ |
| public static interface VisibleSecurityStateObserver { |
| public void onVisibleSecurityStateOfActiveTabChanged(); |
| } |
| public void addVisibleSecurityStateObserver(VisibleSecurityStateObserver observer) { |
| mVisibleSecurityStateObservers.addObserver(observer); |
| } |
| public void removeVisibleSecurityStateObserver(VisibleSecurityStateObserver observer) { |
| mVisibleSecurityStateObservers.removeObserver(observer); |
| } |
| |
| public BrowserImpl(Context embedderAppContext, ProfileImpl profile, String persistenceId, |
| Bundle savedInstanceState, FragmentWindowAndroid windowAndroid) { |
| ++sInstanceCount; |
| profile.checkNotDestroyed(); |
| mProfile = profile; |
| |
| mPersistenceInfo = new PersistenceInfo(); |
| mPersistenceInfo.mPersistenceId = persistenceId; |
| mPersistenceInfo.mCryptoKey = savedInstanceState != null |
| ? savedInstanceState.getByteArray(SAVED_STATE_SESSION_SERVICE_CRYPTO_KEY) |
| : null; |
| mPersistenceInfo.mMinimalPersistenceState = |
| (savedInstanceState != null && (persistenceId == null || persistenceId.isEmpty())) |
| ? savedInstanceState.getByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY) |
| : null; |
| |
| windowAndroid.restoreInstanceState(savedInstanceState); |
| |
| createAttachmentState(embedderAppContext, windowAndroid); |
| mNativeBrowser = BrowserImplJni.get().createBrowser(profile.getNativeProfile(), this); |
| mUrlBarController = new UrlBarControllerImpl(this, mNativeBrowser); |
| } |
| |
| public WindowAndroid getWindowAndroid() { |
| return mWindowAndroid; |
| } |
| |
| public ContentView getViewAndroidDelegateContainerView() { |
| if (mViewController == null) return null; |
| return mViewController.getContentView(); |
| } |
| |
| public UrlBarControllerImpl getUrlBarControllerImpl() { |
| return mUrlBarController; |
| } |
| |
| // Called from constructor and onFragmentAttached() to configure state needed when attached. |
| private void createAttachmentState( |
| Context embedderAppContext, FragmentWindowAndroid windowAndroid) { |
| assert mViewController == null; |
| assert mWindowAndroid == null; |
| assert mEmbedderActivityContext == null; |
| mWindowAndroid = windowAndroid; |
| mEmbedderActivityContext = embedderAppContext; |
| mViewController = new BrowserViewController( |
| windowAndroid, this, mViewControllerState, mInConfigurationChangeAndWasAttached); |
| mViewController.setMinimumSurfaceSize(mMinimumSurfaceWidth, mMinimumSurfaceHeight); |
| mLocaleReceiver = new LocaleChangedBroadcastReceiver(windowAndroid.getContext().get()); |
| mPasswordEchoEnabled = null; |
| } |
| |
| public void onFragmentAttached( |
| Context embedderAppContext, FragmentWindowAndroid windowAndroid) { |
| createAttachmentState(embedderAppContext, windowAndroid); |
| updateAllTabsAndSetActive(); |
| } |
| |
| public void onFragmentDetached() { |
| destroyAttachmentState(); |
| updateAllTabs(); |
| } |
| |
| public void onSaveInstanceState(Bundle outState) { |
| boolean hasPersistenceId = !BrowserImplJni.get().getPersistenceId(mNativeBrowser).isEmpty(); |
| if (mProfile.isIncognito() && hasPersistenceId) { |
| // Trigger a save now as saving may generate a new crypto key. This doesn't actually |
| // save synchronously, rather triggers a save on a background task runner. |
| BrowserImplJni.get().saveBrowserPersisterIfNecessary(mNativeBrowser); |
| outState.putByteArray(SAVED_STATE_SESSION_SERVICE_CRYPTO_KEY, |
| BrowserImplJni.get().getBrowserPersisterCryptoKey(mNativeBrowser)); |
| } else if (!hasPersistenceId) { |
| outState.putByteArray(SAVED_STATE_MINIMAL_PERSISTENCE_STATE_KEY, |
| BrowserImplJni.get().getMinimalPersistenceState(mNativeBrowser)); |
| } |
| |
| if (mWindowAndroid != null) { |
| mWindowAndroid.saveInstanceState(outState); |
| } |
| } |
| |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (mWindowAndroid != null) { |
| mWindowAndroid.onActivityResult(requestCode, resultCode, data); |
| } |
| } |
| |
| public void onRequestPermissionsResult( |
| int requestCode, String[] permissions, int[] grantResults) { |
| if (mWindowAndroid != null) { |
| mWindowAndroid.handlePermissionResult(requestCode, permissions, grantResults); |
| } |
| } |
| |
| @Override |
| public void setTopView(IObjectWrapper viewWrapper) { |
| StrictModeWorkaround.apply(); |
| getViewController().setTopView(ObjectWrapper.unwrap(viewWrapper, View.class)); |
| } |
| |
| @Override |
| public void setTopViewAndScrollingBehavior(IObjectWrapper viewWrapper, int minHeight, |
| boolean onlyExpandControlsAtPageTop, boolean animate) { |
| StrictModeWorkaround.apply(); |
| if (minHeight < 0) { |
| throw new IllegalArgumentException("Top view min height must be non-negative."); |
| } |
| |
| getViewController().setTopControlsAnimationsEnabled(animate); |
| getViewController().setTopView(ObjectWrapper.unwrap(viewWrapper, View.class)); |
| getViewController().setTopControlsMinHeight(minHeight); |
| getViewController().setOnlyExpandTopControlsAtPageTop(onlyExpandControlsAtPageTop); |
| } |
| |
| @Override |
| public void setBottomView(IObjectWrapper viewWrapper) { |
| StrictModeWorkaround.apply(); |
| getViewController().setBottomView(ObjectWrapper.unwrap(viewWrapper, View.class)); |
| } |
| |
| @Override |
| public TabImpl createTab() { |
| TabImpl tab = new TabImpl(this, mProfile, mWindowAndroid); |
| // This needs |alwaysAdd| set to true as the Tab is created with the Browser already set to |
| // this. |
| addTab(tab, /* alwaysAdd */ true); |
| return tab; |
| } |
| |
| @Override |
| public void setSupportsEmbedding(boolean enable, IObjectWrapper valueCallback) { |
| StrictModeWorkaround.apply(); |
| getViewController().setEmbeddabilityMode( |
| enable ? BrowserEmbeddabilityMode.SUPPORTED : BrowserEmbeddabilityMode.UNSUPPORTED, |
| (ValueCallback<Boolean>) ObjectWrapper.unwrap(valueCallback, ValueCallback.class)); |
| } |
| |
| @Override |
| public void setEmbeddabilityMode( |
| @BrowserEmbeddabilityMode int mode, IObjectWrapper valueCallback) { |
| StrictModeWorkaround.apply(); |
| getViewController().setEmbeddabilityMode(mode, |
| (ValueCallback<Boolean>) ObjectWrapper.unwrap(valueCallback, ValueCallback.class)); |
| } |
| |
| @Override |
| public void setChangeVisibilityOnNextDetach(boolean changeVisibility) { |
| StrictModeWorkaround.apply(); |
| if (isViewAttachedToWindow()) { |
| mForcedVisible = !changeVisibility; |
| } |
| } |
| |
| @Override |
| public void setMinimumSurfaceSize(int width, int height) { |
| StrictModeWorkaround.apply(); |
| mMinimumSurfaceWidth = width; |
| mMinimumSurfaceHeight = height; |
| BrowserViewController viewController = getPossiblyNullViewController(); |
| if (viewController == null) return; |
| viewController.setMinimumSurfaceSize(width, height); |
| } |
| |
| // Only call this if it's guaranteed that Browser is attached to an activity. |
| @NonNull |
| public BrowserViewController getViewController() { |
| if (mViewController == null) { |
| throw new RuntimeException("Currently Tab requires Activity context, so " |
| + "it exists only while BrowserFragment is attached to an Activity"); |
| } |
| return mViewController; |
| } |
| |
| // Can be null in the middle of destroy, or if fragment is detached from activity. |
| @Nullable |
| public BrowserViewController getPossiblyNullViewController() { |
| return mViewController; |
| } |
| |
| public Context getContext() { |
| if (mWindowAndroid == null) { |
| return null; |
| } |
| |
| return mWindowAndroid.getContext().get(); |
| } |
| |
| public boolean isWindowOnSmallDevice() { |
| assert mWindowAndroid != null; |
| return !DeviceFormFactor.isWindowOnTablet(mWindowAndroid); |
| } |
| |
| @Override |
| @NonNull |
| public ProfileImpl getProfile() { |
| StrictModeWorkaround.apply(); |
| return mProfile; |
| } |
| |
| @Override |
| public void addTab(ITab iTab) { |
| StrictModeWorkaround.apply(); |
| addTab((TabImpl) iTab, /* alwaysAdd */ false); |
| } |
| |
| private void addTab(TabImpl tab, boolean alwaysAdd) { |
| if (!alwaysAdd && tab.getBrowser() == this) return; |
| BrowserImplJni.get().addTab(mNativeBrowser, tab.getNativeTab()); |
| } |
| |
| @CalledByNative |
| private void createJavaTabForNativeTab(long nativeTab) { |
| new TabImpl(this, mProfile, mWindowAndroid, nativeTab); |
| } |
| |
| private void checkPreferences() { |
| boolean changed = false; |
| if (mPasswordEchoEnabled != null) { |
| boolean oldEnabled = mPasswordEchoEnabled; |
| mPasswordEchoEnabled = null; |
| boolean newEnabled = getPasswordEchoEnabled(); |
| changed = changed || oldEnabled != newEnabled; |
| } |
| if (mDarkThemeEnabled != null) { |
| boolean oldEnabled = mDarkThemeEnabled; |
| mDarkThemeEnabled = null; |
| boolean newEnabled = getDarkThemeEnabled(); |
| changed = changed || oldEnabled != newEnabled; |
| } |
| if (mFontScale != null) { |
| float oldFontScale = mFontScale; |
| mFontScale = null; |
| float newFontScale = getFontScale(); |
| changed = changed || oldFontScale != newFontScale; |
| } |
| if (changed) { |
| BrowserImplJni.get().webPreferencesChanged(mNativeBrowser); |
| } |
| } |
| |
| @CalledByNative |
| private boolean getPasswordEchoEnabled() { |
| Context context = getContext(); |
| if (context == null) return false; |
| if (mPasswordEchoEnabled == null) { |
| mPasswordEchoEnabled = Settings.System.getInt(context.getContentResolver(), |
| Settings.System.TEXT_SHOW_PASSWORD, 1) |
| == 1; |
| } |
| return mPasswordEchoEnabled; |
| } |
| |
| @CalledByNative |
| boolean getDarkThemeEnabled() { |
| if (mEmbedderActivityContext == null) return false; |
| if (mDarkThemeEnabled == null) { |
| if (mEmbedderActivityContext == null) return false; |
| int uiMode = mEmbedderActivityContext.getResources().getConfiguration().uiMode; |
| mDarkThemeEnabled = |
| (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; |
| } |
| return mDarkThemeEnabled; |
| } |
| |
| @CalledByNative |
| private float getFontScale() { |
| Context context = getContext(); |
| if (context == null) return 1.0f; |
| if (mFontScale == null) { |
| mFontScale = context.getResources().getConfiguration().fontScale; |
| } |
| return mFontScale; |
| } |
| |
| Context getEmbedderActivityContext() { |
| return mEmbedderActivityContext; |
| } |
| |
| @CalledByNative |
| private void onTabAdded(TabImpl tab) throws RemoteException { |
| tab.attachToBrowser(this); |
| if (mClient != null) mClient.onTabAdded(tab); |
| } |
| |
| @CalledByNative |
| private void onActiveTabChanged(TabImpl tab) throws RemoteException { |
| if (mViewController != null) mViewController.setActiveTab(tab); |
| if (!mInDestroy && mClient != null) { |
| mClient.onActiveTabChanged(tab != null ? tab.getId() : 0); |
| } |
| } |
| |
| @CalledByNative |
| private void onTabRemoved(TabImpl tab) throws RemoteException { |
| if (mInDestroy) return; |
| if (mClient != null) mClient.onTabRemoved(tab.getId()); |
| // This doesn't reset state on TabImpl as |browser| is either about to be |
| // destroyed, or switching to a different fragment. |
| } |
| |
| @CalledByNative |
| private void onVisibleSecurityStateOfActiveTabChanged() { |
| for (VisibleSecurityStateObserver observer : mVisibleSecurityStateObservers) { |
| observer.onVisibleSecurityStateOfActiveTabChanged(); |
| } |
| } |
| |
| @CalledByNative |
| private boolean compositorHasSurface() { |
| if (mViewController == null) return false; |
| return mViewController.compositorHasSurface(); |
| } |
| |
| @Override |
| public boolean setActiveTab(ITab controller) { |
| StrictModeWorkaround.apply(); |
| TabImpl tab = (TabImpl) controller; |
| if (tab != null && tab.getBrowser() != this) return false; |
| BrowserImplJni.get().setActiveTab(mNativeBrowser, tab != null ? tab.getNativeTab() : 0); |
| return true; |
| } |
| |
| public @Nullable TabImpl getActiveTab() { |
| return BrowserImplJni.get().getActiveTab(mNativeBrowser); |
| } |
| |
| @Override |
| public List getTabs() { |
| StrictModeWorkaround.apply(); |
| return Arrays.asList(BrowserImplJni.get().getTabs(mNativeBrowser)); |
| } |
| |
| @Override |
| public int getActiveTabId() { |
| StrictModeWorkaround.apply(); |
| return getActiveTab() != null ? getActiveTab().getId() : 0; |
| } |
| |
| @Override |
| public void setClient(IBrowserClient client) { |
| StrictModeWorkaround.apply(); |
| mClient = client; |
| |
| // This function is called from the client once everything has been setup (meaning all the |
| // client classes have been created and AIDL interfaces established in both directions). |
| // This function is called immediately after the constructor of BrowserImpl from the client. |
| assert mPersistenceInfo != null; |
| PersistenceInfo persistenceInfo = mPersistenceInfo; |
| mPersistenceInfo = null; |
| BrowserImplJni.get().restoreStateIfNecessary(mNativeBrowser, persistenceInfo.mPersistenceId, |
| persistenceInfo.mCryptoKey, persistenceInfo.mMinimalPersistenceState); |
| |
| if (getTabs().size() > 0) { |
| updateAllTabsAndSetActive(); |
| } else if (persistenceInfo.mPersistenceId == null |
| || persistenceInfo.mPersistenceId.isEmpty()) { |
| boolean setActiveResult = setActiveTab(createTab()); |
| assert setActiveResult; |
| } // else case is session restore, which will asynchronously create tabs. |
| } |
| |
| @Override |
| public void destroyTab(ITab iTab) { |
| StrictModeWorkaround.apply(); |
| TabImpl tab = (TabImpl) iTab; |
| if (tab.getBrowser() != this) return; |
| destroyTabImpl((TabImpl) iTab); |
| } |
| |
| @CalledByNative |
| private void destroyTabImpl(TabImpl tab) { |
| tab.destroy(); |
| } |
| |
| @Override |
| public void setDarkModeStrategy(@DarkModeStrategy int strategy) { |
| if (mDarkModeStrategy == strategy) { |
| return; |
| } |
| mDarkModeStrategy = strategy; |
| BrowserImplJni.get().webPreferencesChanged(mNativeBrowser); |
| } |
| |
| @CalledByNative |
| int getDarkModeStrategy() { |
| return mDarkModeStrategy; |
| } |
| |
| @Override |
| public IUrlBarController getUrlBarController() { |
| StrictModeWorkaround.apply(); |
| return mUrlBarController; |
| } |
| |
| @Override |
| public void setBrowserControlsOffsetsEnabled(boolean enable) { |
| mNotifyOnBrowserControlsOffsetsChanged = enable; |
| } |
| |
| public void onBrowserControlsOffsetsChanged(TabImpl tab, boolean isTop, int controlsOffset) { |
| if (mNotifyOnBrowserControlsOffsetsChanged && tab == getActiveTab()) { |
| try { |
| mClient.onBrowserControlsOffsetsChanged(isTop, controlsOffset); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isRestoringPreviousState() { |
| return BrowserImplJni.get().isRestoringPreviousState(mNativeBrowser); |
| } |
| |
| @CalledByNative |
| private void onRestoreCompleted() throws RemoteException { |
| mClient.onRestoreCompleted(); |
| } |
| |
| public View getFragmentView() { |
| return getViewController().getView(); |
| } |
| |
| public void destroy() { |
| mInDestroy = true; |
| BrowserImplJni.get().prepareForShutdown(mNativeBrowser); |
| setActiveTab(null); |
| for (Object tab : getTabs()) { |
| destroyTabImpl((TabImpl) tab); |
| } |
| destroyAttachmentState(); |
| |
| // mUrlBarController keeps a reference to mNativeBrowser, and hence must be destroyed before |
| // mNativeBrowser. |
| mUrlBarController.destroy(); |
| BrowserImplJni.get().deleteBrowser(mNativeBrowser); |
| |
| if (--sInstanceCount == 0) { |
| WebLayerAccessibilityUtil.get().onAllBrowsersDestroyed(); |
| } |
| } |
| |
| public void onFragmentStart() { |
| mFragmentStarted = true; |
| if (mViewAttachedToWindow) { |
| mInConfigurationChangeAndWasAttached = false; |
| mForcedVisible = false; |
| } |
| BrowserImplJni.get().onFragmentStart(mNativeBrowser); |
| updateAllTabs(); |
| checkPreferences(); |
| } |
| |
| public void onFragmentStop(boolean forConfigurationChange) { |
| mInConfigurationChangeAndWasAttached = forConfigurationChange; |
| mFragmentStarted = false; |
| updateAllTabs(); |
| } |
| |
| public void onFragmentResume() { |
| mFragmentResumed = true; |
| WebLayerAccessibilityUtil.get().onBrowserResumed(); |
| BrowserImplJni.get().onFragmentResume(mNativeBrowser); |
| } |
| |
| public void onFragmentPause() { |
| mFragmentResumed = false; |
| BrowserImplJni.get().onFragmentPause(mNativeBrowser); |
| } |
| |
| public boolean isStarted() { |
| return mFragmentStarted; |
| } |
| |
| public boolean isResumed() { |
| return mFragmentResumed; |
| } |
| |
| public FragmentManager getFragmentManager() { |
| return mWindowAndroid.getFragmentManager(); |
| } |
| |
| public boolean isViewAttachedToWindow() { |
| return mViewAttachedToWindow; |
| } |
| |
| long getNativeBrowser() { |
| return mNativeBrowser; |
| } |
| |
| @Override |
| public void onViewAttachedToWindow(View v) { |
| mViewAttachedToWindow = true; |
| if (mFragmentStarted) { |
| mInConfigurationChangeAndWasAttached = false; |
| mForcedVisible = false; |
| } |
| updateAllTabsViewAttachedState(); |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View v) { |
| // Note this separate state is needed because v.isAttachedToWindow() |
| // still returns true inside this call. |
| mViewAttachedToWindow = false; |
| updateAllTabsViewAttachedState(); |
| } |
| |
| public MediaRouteDialogFragmentImpl createMediaRouteDialogFragment() { |
| try { |
| return MediaRouteDialogFragmentImpl.fromRemoteFragment( |
| mClient.createMediaRouteDialogFragment()); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| } |
| |
| private void updateAllTabsViewAttachedState() { |
| for (Object tab : getTabs()) { |
| ((TabImpl) tab).updateViewAttachedStateFromBrowser(); |
| } |
| } |
| |
| private void destroyAttachmentState() { |
| if (mLocaleReceiver != null) { |
| mLocaleReceiver.destroy(); |
| mLocaleReceiver = null; |
| } |
| if (mViewController != null) { |
| mViewControllerState = mViewController.getState(); |
| mViewController.destroy(); |
| mViewController = null; |
| mViewAttachedToWindow = false; |
| updateAllTabsViewAttachedState(); |
| } |
| if (mWindowAndroid != null) { |
| mWindowAndroid.destroy(); |
| mWindowAndroid = null; |
| mEmbedderActivityContext = null; |
| } |
| |
| mVisibleSecurityStateObservers.clear(); |
| } |
| |
| /** |
| * Returns true if the active tab should be considered visible. |
| */ |
| public boolean isActiveTabVisible() { |
| return mForcedVisible || mInConfigurationChangeAndWasAttached |
| || (isStarted() && isViewAttachedToWindow()); |
| } |
| |
| private void updateAllTabsAndSetActive() { |
| if (getTabs().size() > 0) { |
| updateAllTabs(); |
| mViewController.setActiveTab(getActiveTab()); |
| } |
| } |
| |
| private void updateAllTabs() { |
| for (Object tab : getTabs()) { |
| ((TabImpl) tab).updateFromBrowser(); |
| } |
| } |
| |
| @NativeMethods |
| interface Natives { |
| long createBrowser(long profile, BrowserImpl caller); |
| void deleteBrowser(long browser); |
| void addTab(long nativeBrowserImpl, long nativeTab); |
| TabImpl[] getTabs(long nativeBrowserImpl); |
| void setActiveTab(long nativeBrowserImpl, long nativeTab); |
| TabImpl getActiveTab(long nativeBrowserImpl); |
| void prepareForShutdown(long nativeBrowserImpl); |
| String getPersistenceId(long nativeBrowserImpl); |
| void saveBrowserPersisterIfNecessary(long nativeBrowserImpl); |
| byte[] getBrowserPersisterCryptoKey(long nativeBrowserImpl); |
| byte[] getMinimalPersistenceState(long nativeBrowserImpl); |
| void restoreStateIfNecessary(long nativeBrowserImpl, String persistenceId, |
| byte[] persistenceCryptoKey, byte[] minimalPersistenceState); |
| void webPreferencesChanged(long nativeBrowserImpl); |
| void onFragmentStart(long nativeBrowserImpl); |
| void onFragmentResume(long nativeBrowserImpl); |
| void onFragmentPause(long nativeBrowserImpl); |
| boolean isRestoringPreviousState(long nativeBrowserImpl); |
| } |
| } |