| // 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.init; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Configuration; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.provider.Settings; |
| import android.provider.Settings.SettingNotFoundException; |
| import android.support.v7.app.AppCompatActivity; |
| import android.view.Display; |
| import android.view.Surface; |
| import android.view.View; |
| import android.view.ViewTreeObserver; |
| import android.view.ViewTreeObserver.OnPreDrawListener; |
| import android.view.WindowManager; |
| |
| import org.chromium.base.ApiCompatibilityUtils; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.library_loader.LoaderErrors; |
| import org.chromium.base.library_loader.ProcessInitException; |
| import org.chromium.chrome.browser.ChromeApplication; |
| import org.chromium.chrome.browser.WarmupManager; |
| import org.chromium.chrome.browser.metrics.LaunchMetrics; |
| import org.chromium.chrome.browser.metrics.MemoryUma; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.tabmodel.DocumentModeAssassin; |
| import org.chromium.chrome.browser.upgrade.UpgradeActivity; |
| import org.chromium.ui.base.DeviceFormFactor; |
| |
| import java.lang.reflect.Field; |
| |
| /** |
| * An activity that talks with application and activity level delegates for async initialization. |
| */ |
| public abstract class AsyncInitializationActivity extends AppCompatActivity implements |
| ChromeActivityNativeDelegate, BrowserParts { |
| |
| private static final LaunchMetrics.BooleanEvent sBadIntentMetric = |
| new LaunchMetrics.BooleanEvent("Launch.InvalidIntent"); |
| |
| protected final Handler mHandler; |
| |
| // Time at which onCreate is called. This is realtime, counted in ms since device boot. |
| private long mOnCreateTimestampMs; |
| |
| // Time at which onCreate is called. This is uptime, to be sent to native code. |
| private long mOnCreateTimestampUptimeMs; |
| private Bundle mSavedInstanceState; |
| private int mCurrentOrientation = Surface.ROTATION_0; |
| private boolean mDestroyed; |
| private NativeInitializationController mNativeInitializationController; |
| private MemoryUma mMemoryUma; |
| private long mLastUserInteractionTime; |
| private boolean mIsTablet; |
| |
| public AsyncInitializationActivity() { |
| mHandler = new Handler(); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| mDestroyed = true; |
| super.onDestroy(); |
| } |
| |
| @Override |
| protected void attachBaseContext(Context newBase) { |
| super.attachBaseContext(newBase); |
| |
| // On N+, Chrome should always retain the tab strip layout on tablets. Normally in |
| // multi-window, if Chrome is launched into a smaller screen Android will load the tab |
| // switcher resources. Overriding the smallestScreenWidthDp in the Configuration ensures |
| // Android will load the tab strip resources. See crbug.com/588838. |
| if (Build.VERSION.CODENAME.equals("N") || Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { |
| int smallestDeviceWidthDp = DeviceFormFactor.getSmallestDeviceWidthDp(this); |
| |
| if (smallestDeviceWidthDp >= DeviceFormFactor.MINIMUM_TABLET_WIDTH_DP) { |
| Configuration overrideConfiguration = new Configuration(); |
| overrideConfiguration.smallestScreenWidthDp = smallestDeviceWidthDp; |
| applyOverrideConfiguration(overrideConfiguration); |
| } |
| } |
| } |
| |
| @Override |
| public void preInflationStartup() { |
| mIsTablet = DeviceFormFactor.isTablet(this); |
| } |
| |
| @Override |
| public final void setContentViewAndLoadLibrary() { |
| // setContentView inflating the decorView and the basic UI hierarhcy as stubs. |
| // This is done here before kicking long running I/O because inflation includes accessing |
| // resource files(xmls etc) even if we are inflating views defined by the framework. If this |
| // operation gets blocked because other long running I/O are running, we delay onCreate(), |
| // onStart() and first draw consequently. |
| |
| setContentView(); |
| if (mLaunchBehindWorkaround != null) mLaunchBehindWorkaround.onSetContentView(); |
| |
| // Kick off long running IO tasks that can be done in parallel. |
| mNativeInitializationController = new NativeInitializationController(this, this); |
| initializeChildProcessCreationParams(); |
| mNativeInitializationController.startBackgroundTasks(); |
| } |
| |
| /** |
| * Allow derived classes to initialize their own {@link ChildProcessCreationParams}. |
| */ |
| protected void initializeChildProcessCreationParams() {} |
| |
| @Override |
| public void postInflationStartup() { |
| final View firstDrawView = getViewToBeDrawnBeforeInitializingNative(); |
| assert firstDrawView != null; |
| ViewTreeObserver.OnPreDrawListener firstDrawListener = |
| new ViewTreeObserver.OnPreDrawListener() { |
| @Override |
| public boolean onPreDraw() { |
| firstDrawView.getViewTreeObserver().removeOnPreDrawListener(this); |
| onFirstDrawComplete(); |
| return true; |
| } |
| }; |
| firstDrawView.getViewTreeObserver().addOnPreDrawListener(firstDrawListener); |
| } |
| |
| /** |
| * @return The primary view that must have completed at least one draw before initializing |
| * native. This must be non-null. |
| */ |
| protected View getViewToBeDrawnBeforeInitializingNative() { |
| return findViewById(android.R.id.content); |
| } |
| |
| @Override |
| public void maybePreconnect() { |
| TraceEvent.begin("maybePreconnect"); |
| Intent intent = getIntent(); |
| if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { |
| final String url = intent.getDataString(); |
| WarmupManager.getInstance() |
| .maybePreconnectUrlAndSubResources(Profile.getLastUsedProfile(), url); |
| } |
| TraceEvent.end("maybePreconnect"); |
| } |
| |
| @Override |
| public void initializeCompositor() { } |
| |
| @Override |
| public void initializeState() { } |
| |
| @Override |
| public void finishNativeInitialization() { |
| // Set up the initial orientation of the device. |
| checkOrientation(); |
| findViewById(android.R.id.content).addOnLayoutChangeListener( |
| new View.OnLayoutChangeListener() { |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, |
| int oldLeft, int oldTop, int oldRight, int oldBottom) { |
| checkOrientation(); |
| } |
| }); |
| mMemoryUma = new MemoryUma(); |
| mNativeInitializationController.onNativeInitializationComplete(); |
| } |
| |
| /** |
| * Actions that may be run at some point after startup. Place tasks that are not critical to the |
| * startup path here. This method will be called automatically and should not be called |
| * directly by subclasses. Overriding methods should call super.onDeferredStartup(). |
| */ |
| protected void onDeferredStartup() { } |
| |
| @Override |
| public void onStartupFailure() { |
| ProcessInitException e = |
| new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_STARTUP_FAILED); |
| ChromeApplication.reportStartupErrorAndExit(e); |
| } |
| |
| /** |
| * Extending classes should override {@link AsyncInitializationActivity#preInflationStartup()}, |
| * {@link AsyncInitializationActivity#setContentView()} and |
| * {@link AsyncInitializationActivity#postInflationStartup()} instead of this call which will |
| * be called on that order. |
| */ |
| @Override |
| protected final void onCreate(Bundle savedInstanceState) { |
| if (DocumentModeAssassin.getInstance().isMigrationNecessary()) { |
| super.onCreate(null); |
| |
| // Kick the user to the MigrationActivity. |
| UpgradeActivity.launchInstance(this, getIntent()); |
| |
| // Don't remove this task -- it may be a DocumentActivity that exists only in Recents. |
| finish(); |
| return; |
| } |
| |
| if (!isStartedUpCorrectly(getIntent())) { |
| sBadIntentMetric.recordHit(); |
| super.onCreate(null); |
| ApiCompatibilityUtils.finishAndRemoveTask(this); |
| return; |
| } |
| |
| super.onCreate(savedInstanceState); |
| mOnCreateTimestampMs = SystemClock.elapsedRealtime(); |
| mOnCreateTimestampUptimeMs = SystemClock.uptimeMillis(); |
| mSavedInstanceState = savedInstanceState; |
| |
| ChromeBrowserInitializer.getInstance(this).handlePreNativeStartup(this); |
| } |
| |
| /** |
| * Whether or not the Activity was started up via a valid Intent. |
| */ |
| protected boolean isStartedUpCorrectly(Intent intent) { |
| return true; |
| } |
| |
| /** |
| * @return The elapsed real time for the activity creation in ms. |
| */ |
| protected long getOnCreateTimestampUptimeMs() { |
| return mOnCreateTimestampUptimeMs; |
| } |
| |
| /** |
| * @return The uptime for the activity creation in ms. |
| */ |
| protected long getOnCreateTimestampMs() { |
| return mOnCreateTimestampMs; |
| } |
| |
| /** |
| * @return The saved bundle for the last recorded state. |
| */ |
| protected Bundle getSavedInstanceState() { |
| return mSavedInstanceState; |
| } |
| |
| /** |
| * Resets the saved state and makes it unavailable for the rest of the activity lifecycle. |
| */ |
| protected void resetSavedInstanceState() { |
| mSavedInstanceState = null; |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| mNativeInitializationController.onStart(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| mNativeInitializationController.onResume(); |
| if (mLaunchBehindWorkaround != null) mLaunchBehindWorkaround.onResume(); |
| } |
| |
| @Override |
| public void onPause() { |
| mNativeInitializationController.onPause(); |
| super.onPause(); |
| if (mLaunchBehindWorkaround != null) mLaunchBehindWorkaround.onPause(); |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| if (mMemoryUma != null) mMemoryUma.onStop(); |
| mNativeInitializationController.onStop(); |
| } |
| |
| @Override |
| protected void onNewIntent(Intent intent) { |
| if (intent == null) return; |
| mNativeInitializationController.onNewIntent(intent); |
| setIntent(intent); |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| mNativeInitializationController.onActivityResult(requestCode, resultCode, data); |
| } |
| |
| @Override |
| public final void onCreateWithNative() { |
| try { |
| ChromeBrowserInitializer.getInstance(this).handlePostNativeStartup(true, this); |
| } catch (ProcessInitException e) { |
| ChromeApplication.reportStartupErrorAndExit(e); |
| } |
| } |
| |
| @Override |
| public void onStartWithNative() { } |
| |
| @Override |
| public void onResumeWithNative() { } |
| |
| @Override |
| public void onPauseWithNative() { } |
| |
| @Override |
| public void onStopWithNative() { } |
| |
| @Override |
| public boolean isActivityDestroyed() { |
| return mDestroyed; |
| } |
| |
| @Override |
| public boolean isActivityFinishing() { |
| return isFinishing(); |
| } |
| |
| @Override |
| public boolean shouldStartGpuProcess() { |
| return true; |
| } |
| |
| @Override |
| public final void onFirstDrawComplete() { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| mNativeInitializationController.firstDrawComplete(); |
| } |
| }); |
| } |
| |
| @Override |
| public void onNewIntentWithNative(Intent intent) { } |
| |
| @Override |
| public boolean onActivityResultWithNative(int requestCode, int resultCode, Intent data) { |
| return false; |
| } |
| |
| @Override |
| public void onLowMemory() { |
| super.onLowMemory(); |
| if (mMemoryUma != null) mMemoryUma.onLowMemory(); |
| } |
| |
| @Override |
| public void onTrimMemory(int level) { |
| super.onTrimMemory(level); |
| if (mMemoryUma != null) mMemoryUma.onTrimMemory(level); |
| } |
| |
| /** |
| * @return Whether the activity is running in tablet mode. |
| */ |
| public boolean isTablet() { |
| return mIsTablet; |
| } |
| |
| @Override |
| public void onUserInteraction() { |
| mLastUserInteractionTime = SystemClock.elapsedRealtime(); |
| } |
| |
| /** |
| * @return timestamp when the last user interaction was made. |
| */ |
| public long getLastUserInteractionTime() { |
| return mLastUserInteractionTime; |
| } |
| |
| /** |
| * Called when the orientation of the device changes. The orientation is checked/detected on |
| * root view layouts. |
| * @param orientation One of {@link Surface#ROTATION_0} (no rotation), |
| * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}, or |
| * {@link Surface#ROTATION_270}. |
| */ |
| protected void onOrientationChange(int orientation) { |
| } |
| |
| private void checkOrientation() { |
| WindowManager wm = getWindowManager(); |
| if (wm == null) return; |
| |
| Display display = wm.getDefaultDisplay(); |
| if (display == null) return; |
| |
| int oldOrientation = mCurrentOrientation; |
| mCurrentOrientation = display.getRotation(); |
| |
| if (oldOrientation != mCurrentOrientation) onOrientationChange(mCurrentOrientation); |
| } |
| |
| /** |
| * Removes the window background. |
| */ |
| protected void removeWindowBackground() { |
| boolean removeWindowBackground = true; |
| try { |
| Field field = Settings.Secure.class.getField( |
| "ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED"); |
| field.setAccessible(true); |
| |
| if (field.getType() == String.class) { |
| String accessibilityMagnificationSetting = (String) field.get(null); |
| // When Accessibility magnification is turned on, setting a null window |
| // background causes the overlaid android views to stretch when panning. |
| // (crbug/332994) |
| if (Settings.Secure.getInt( |
| getContentResolver(), accessibilityMagnificationSetting) == 1) { |
| removeWindowBackground = false; |
| } |
| } |
| } catch (SettingNotFoundException e) { |
| // Window background is removed if an exception occurs. |
| } catch (NoSuchFieldException e) { |
| // Window background is removed if an exception occurs. |
| } catch (IllegalAccessException e) { |
| // Window background is removed if an exception occurs. |
| } catch (IllegalArgumentException e) { |
| // Window background is removed if an exception occurs. |
| } |
| if (removeWindowBackground) getWindow().setBackgroundDrawable(null); |
| } |
| |
| /** |
| * Extending classes should implement this and call {@link Activity#setContentView(int)} in it. |
| */ |
| protected abstract void setContentView(); |
| |
| /** |
| * Lollipop (pre-MR1) makeTaskLaunchBehind() workaround. |
| * |
| * Our activity's surface is destroyed at the end of the new activity animation |
| * when ActivityOptions.makeTaskLaunchBehind() is used, which causes a crash. |
| * Making everything invisible when paused prevents the crash, since view changes |
| * will not trigger draws to the missing surface. However, we need to wait until |
| * after the first draw to make everything invisible, as the activity launch |
| * animation needs a full frame (or it will delay the animation excessively). |
| */ |
| private final LaunchBehindWorkaround mLaunchBehindWorkaround = |
| (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) |
| ? new LaunchBehindWorkaround() |
| : null; |
| |
| private class LaunchBehindWorkaround { |
| private boolean mPaused = false; |
| |
| private View getDecorView() { |
| return getWindow().getDecorView(); |
| } |
| |
| private ViewTreeObserver getViewTreeObserver() { |
| return getDecorView().getViewTreeObserver(); |
| } |
| |
| private void onPause() { |
| mPaused = true; |
| } |
| |
| public void onResume() { |
| mPaused = false; |
| getDecorView().setVisibility(View.VISIBLE); |
| } |
| |
| public void onSetContentView() { |
| getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); |
| } |
| |
| // Note, we probably want onDrawListener here, but it isn't being called |
| // when I add this to the decorView. However, it should be the same for |
| // this purpose as long as no other pre-draw listener returns false. |
| private final OnPreDrawListener mPreDrawListener = new OnPreDrawListener() { |
| @Override |
| public boolean onPreDraw() { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mPaused) { |
| getDecorView().setVisibility(View.GONE); |
| } |
| getViewTreeObserver().removeOnPreDrawListener(mPreDrawListener); |
| } |
| }); |
| return true; |
| } |
| }; |
| } |
| } |