// Copyright 2014 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.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.content.browser.ChildProcessLauncher;
import java.util.ArrayList;
import java.util.List;
/**
* This class controls the different asynchronous states during our initialization:
* 1. During startBackgroundTasks(), we'll kick off loading the library and yield the call stack.
* 2. We may receive a onStart() / onStop() call any point after that, whether or not
* the library has been loaded.
*/
class NativeInitializationController {
private static final String TAG = "NativeInitializationController";
private final ChromeActivityNativeDelegate mActivityDelegate;
private final Handler mHandler;
private boolean mOnStartPending;
private boolean mOnResumePending;
private List<Intent> mPendingNewIntents;
private List<ActivityResult> mPendingActivityResults;
private boolean mWaitingForFirstDraw;
private boolean mHasDoneFirstDraw;
private boolean mInitializationComplete;
/**
* This class encapsulates a call to onActivityResult that has to be deferred because the native
* library is not yet loaded.
*/
static class ActivityResult {
public final int requestCode;
public final int resultCode;
public final Intent data;
public ActivityResult(int requestCode, int resultCode, Intent data) {
this.requestCode = requestCode;
this.resultCode = resultCode;
this.data = data;
}
}
/**
* Create the NativeInitializationController using the main loop and the application context.
* It will be linked back to the activity via the given delegate.
* @param activityDelegate The activity delegate for the owning activity.
*/
public NativeInitializationController(ChromeActivityNativeDelegate activityDelegate) {
mHandler = new Handler(Looper.getMainLooper());
mActivityDelegate = activityDelegate;
}
/**
* Start loading the native library in the background. This kicks off the native initialization
* process.
*
* @param allocateChildConnection Whether a spare child connection should be allocated. Set to
* false if you know that no new renderer is needed.
*/
public void startBackgroundTasks(final boolean allocateChildConnection) {
// TODO(yusufo) : Investigate using an AsyncTask for this.
new Thread() {
@Override
public void run() {
try {
LibraryLoader libraryLoader =
LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER);
libraryLoader.ensureInitialized();
// The prefetch is done after the library load for two reasons:
// - It is easier to know the library location after it has
// been loaded.
// - Testing has shown that this gives the best compromise,
// by avoiding performance regression on any tested
// device, and providing performance improvement on
// some. Doing it earlier delays UI inflation and more
// generally startup on some devices, most likely by
// competing for IO.
// For experimental results, see http://crbug.com/460438.
libraryLoader.asyncPrefetchLibrariesToMemory();
} catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e);
mActivityDelegate.onStartupFailure();
return;
}
if (allocateChildConnection) {
ChildProcessLauncher.warmUp(ContextUtils.getApplicationContext());
}
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
onLibraryLoaded();
}
});
}
}.start();
}
private void onLibraryLoaded() {
if (mHasDoneFirstDraw) {
// First draw is done
onNativeLibraryLoaded();
} else {
mWaitingForFirstDraw = true;
}
}
/**
* Called when the current activity has finished its first draw pass. This and the library
* load has to be completed to start the chromium browser process.
*/
public void firstDrawComplete() {
mHasDoneFirstDraw = true;
if (mWaitingForFirstDraw) {
mWaitingForFirstDraw = false;
// Allow the UI thread to continue its initialization
mHandler.post(new Runnable() {
@Override
public void run() {
onNativeLibraryLoaded();
}
});
}
}
private void onNativeLibraryLoaded() {
// Callback from LibraryLoader on UI thread, when the load has completed.
if (mActivityDelegate.isActivityDestroyed()) return;
mActivityDelegate.onCreateWithNative();
}
/**
* Called when native initialization for an activity has been finished.
*/
public void onNativeInitializationComplete() {
// Callback when we finished with ChromeActivityNativeDelegate.onCreateWithNative tasks
mInitializationComplete = true;
if (mOnStartPending) {
mOnStartPending = false;
startNowAndProcessPendingItems();
}
if (mOnResumePending) {
mOnResumePending = false;
onResume();
}
try {
LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER)
.onNativeInitializationComplete();
} catch (ProcessInitException e) {
Log.e(TAG, "Unable to load native library.", e);
mActivityDelegate.onStartupFailure();
return;
}
}
/**
* Called when an activity gets an onStart call and is done with java only tasks.
*/
public void onStart() {
if (mInitializationComplete) {
startNowAndProcessPendingItems();
} else {
mOnStartPending = true;
}
}
/**
* Called when an activity gets an onResume call and is done with java only tasks.
*/
public void onResume() {
if (mInitializationComplete) {
mActivityDelegate.onResumeWithNative();
} else {
mOnResumePending = true;
}
}
/**
* Called when an activity gets an onPause call and is done with java only tasks.
*/
public void onPause() {
mOnResumePending = false; // Clear the delayed resume if a pause happens first.
if (mInitializationComplete) mActivityDelegate.onPauseWithNative();
}
/**
* Called when an activity gets an onStop call and is done with java only tasks.
*/
public void onStop() {
mOnStartPending = false; // Clear the delayed start if a stop happens first.
if (!mInitializationComplete) return;
mActivityDelegate.onStopWithNative();
}
/**
* Called when an activity gets an onNewIntent call and is done with java only tasks.
* @param intent The intent that has arrived to the activity linked to the given delegate.
*/
public void onNewIntent(Intent intent) {
if (mInitializationComplete) {
mActivityDelegate.onNewIntentWithNative(intent);
} else {
if (mPendingNewIntents == null) mPendingNewIntents = new ArrayList<>(1);
mPendingNewIntents.add(intent);
}
}
/**
* This is the Android onActivityResult callback deferred, if necessary,
* to when the native library has loaded.
* @param requestCode The request code for the ActivityResult.
* @param resultCode The result code for the ActivityResult.
* @param data The intent that has been sent with the ActivityResult.
*/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mInitializationComplete) {
mActivityDelegate.onActivityResultWithNative(requestCode, resultCode, data);
} else {
if (mPendingActivityResults == null) {
mPendingActivityResults = new ArrayList<>(1);
}
mPendingActivityResults.add(new ActivityResult(requestCode, resultCode, data));
}
}
private void startNowAndProcessPendingItems() {
// onNewIntent and onActivityResult are called only when the activity is paused.
// To match the non-deferred behavior, onStart should be called before any processing
// of pending intents and activity results.
// Note that if we needed ChromeActivityNativeDelegate.onResumeWithNative(), the pending
// intents and activity results processing should have happened in the corresponding
// resumeNowAndProcessPendingItems, just before the call to
// ChromeActivityNativeDelegate.onResumeWithNative().
mActivityDelegate.onStartWithNative();
if (mPendingNewIntents != null) {
for (Intent intent : mPendingNewIntents) {
mActivityDelegate.onNewIntentWithNative(intent);
}
mPendingNewIntents = null;
}
if (mPendingActivityResults != null) {
ActivityResult activityResult;
for (int i = 0; i < mPendingActivityResults.size(); i++) {
activityResult = mPendingActivityResults.get(i);
mActivityDelegate.onActivityResultWithNative(activityResult.requestCode,
activityResult.resultCode, activityResult.data);
}
mPendingActivityResults = null;
}
}
}