| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/hosted/ChromeBrowserConnection.java
|
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/hosted/ChromeBrowserConnection.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/hosted/ChromeBrowserConnection.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4684943ace395460e81696ca1155caa57d4fb6f5
|
| --- /dev/null
|
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/hosted/ChromeBrowserConnection.java
|
| @@ -0,0 +1,226 @@
|
| +// 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.hosted;
|
| +
|
| +import android.annotation.SuppressLint;
|
| +import android.app.ActivityManager;
|
| +import android.app.Application;
|
| +import android.content.Context;
|
| +import android.os.Binder;
|
| +import android.os.Bundle;
|
| +import android.os.IBinder;
|
| +import android.os.RemoteException;
|
| +import android.util.LongSparseArray;
|
| +import android.util.SparseArray;
|
| +
|
| +import org.chromium.base.Log;
|
| +import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.annotations.SuppressFBWarnings;
|
| +import org.chromium.base.library_loader.ProcessInitException;
|
| +import org.chromium.chrome.browser.ChromiumApplication;
|
| +import org.chromium.chrome.browser.WarmupManager;
|
| +import org.chromium.content.browser.ChildProcessLauncher;
|
| +import org.chromium.content_public.browser.WebContents;
|
| +
|
| +import java.security.SecureRandom;
|
| +import java.util.ArrayList;
|
| +import java.util.List;
|
| +import java.util.concurrent.atomic.AtomicBoolean;
|
| +
|
| +/**
|
| + * Implementation of the IBrowserConnectionService interface.
|
| + */
|
| +class ChromeBrowserConnection extends IBrowserConnectionService.Stub {
|
| + private static final String TAG = Log.makeTag("ChromeConnection");
|
| + private static final long RESULT_OK = 0;
|
| + private static final long RESULT_ERROR = -1;
|
| +
|
| + private static final Object sConstructionLock = new Object();
|
| + private static ChromeBrowserConnection sInstance;
|
| +
|
| + private final Application mApplication;
|
| + private final AtomicBoolean mWarmupHasBeenCalled;
|
| +
|
| + private final Object mLock;
|
| + private final SparseArray<IBrowserConnectionCallback> mUidToCallback;
|
| + private final LongSparseArray<Integer> mSessionIdToUid;
|
| +
|
| + private ChromeBrowserConnection(Application application) {
|
| + super();
|
| + mApplication = application;
|
| + mWarmupHasBeenCalled = new AtomicBoolean();
|
| + mLock = new Object();
|
| + mUidToCallback = new SparseArray<IBrowserConnectionCallback>();
|
| + mSessionIdToUid = new LongSparseArray<Integer>();
|
| + }
|
| +
|
| + /**
|
| + * @return The unique instance of ChromeBrowserConnection.
|
| + */
|
| + public static ChromeBrowserConnection getInstance(Application application) {
|
| + synchronized (sConstructionLock) {
|
| + if (sInstance == null) sInstance = new ChromeBrowserConnection(application);
|
| + }
|
| + return sInstance;
|
| + }
|
| +
|
| + @Override
|
| + public long finishSetup(IBrowserConnectionCallback callback) {
|
| + final int uid = Binder.getCallingUid();
|
| + synchronized (mLock) {
|
| + if (mUidToCallback.get(uid) != null) return RESULT_ERROR;
|
| + try {
|
| + callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
|
| + @Override
|
| + public void binderDied() {
|
| + synchronized (mLock) {
|
| + cleanupAlreadyLocked(uid);
|
| + }
|
| + }
|
| + }, 0);
|
| + } catch (RemoteException e) {
|
| + // The return code doesn't matter, because this executes when
|
| + // the caller has died.
|
| + return RESULT_ERROR;
|
| + }
|
| + mUidToCallback.put(uid, callback);
|
| + }
|
| + return RESULT_OK;
|
| + }
|
| +
|
| + @Override
|
| + public long warmup(long flags) {
|
| + // Here and in mayLaunchUrl(), don't do expensive work for background applications.
|
| + if (!isUidForeground(Binder.getCallingUid())) return RESULT_ERROR;
|
| + if (!mWarmupHasBeenCalled.compareAndSet(false, true)) return RESULT_OK;
|
| + // The call is non-blocking and this must execute on the UI thread, post a task.
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + @SuppressFBWarnings("DM_EXIT")
|
| + public void run() {
|
| + try {
|
| + // TODO(lizeb): Warm up more of the browser.
|
| + ChromiumApplication app = (ChromiumApplication) mApplication;
|
| + app.startBrowserProcessesAndLoadLibrariesSync(
|
| + app.getApplicationContext(), true);
|
| + ChildProcessLauncher.warmUp(app.getApplicationContext());
|
| + } catch (ProcessInitException e) {
|
| + Log.e(TAG, "ProcessInitException while starting the browser process.");
|
| + // Cannot do anything without the native library, and cannot show a
|
| + // dialog to the user.
|
| + System.exit(-1);
|
| + }
|
| + }
|
| + });
|
| + return RESULT_OK;
|
| + }
|
| +
|
| + @Override
|
| + @SuppressLint("TrulyRandom") // TODO(lizeb): Figure out whether using SecureRandom is OK.
|
| + public long newSession() {
|
| + synchronized (mLock) {
|
| + long sessionId;
|
| + SecureRandom randomSource = new SecureRandom();
|
| + do {
|
| + sessionId = randomSource.nextLong();
|
| + // Because Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE.
|
| + if (sessionId == Long.MIN_VALUE) continue;
|
| + sessionId = Math.abs(sessionId);
|
| + } while (sessionId == 0 || mSessionIdToUid.get(sessionId) != null);
|
| + mSessionIdToUid.put(sessionId, Binder.getCallingUid());
|
| + return sessionId;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + public long mayLaunchUrl(
|
| + long sessionId, final String url, Bundle extras, List<Bundle> otherLikelyBundles) {
|
| + if (!isUidForeground(Binder.getCallingUid())) return RESULT_ERROR;
|
| + synchronized (mLock) {
|
| + if (mSessionIdToUid.get(sessionId) == null) return RESULT_ERROR;
|
| + }
|
| + ThreadUtils.postOnUiThread(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + WarmupManager.getInstance().maybePrefetchDnsForUrlInBackground(
|
| + mApplication.getApplicationContext(), url);
|
| + }
|
| + });
|
| + // TODO(lizeb): Prerendering.
|
| + return sessionId;
|
| + }
|
| +
|
| + /**
|
| + * Transfers a prerendered WebContents if one exists.
|
| + *
|
| + * This resets the internal WebContents; a subsequent call to this method
|
| + * returns null.
|
| + *
|
| + * @param sessionId The session ID, returned by {@link newSession}.
|
| + * @param url The URL the WebContents is for.
|
| + * @param extras from the intent.
|
| + * @return The prerendered WebContents, or null.
|
| + */
|
| + WebContents takePrerenderedUrl(long sessionId, String url, Bundle extras) {
|
| + // TODO(lizeb): Pre-rendering.
|
| + return null;
|
| + }
|
| +
|
| + /**
|
| + * Calls the onUserNavigation callback for a given sessionId.
|
| + *
|
| + * This is non-blocking.
|
| + *
|
| + * @param sessionId Session ID associated with the callback
|
| + * @param url URL the user has navigated to.
|
| + * @param bundle Reserved for future use.
|
| + * @return false if there is no client to deliver the callback to.
|
| + */
|
| + boolean deliverOnUserNavigationCallback(long sessionId, String url, Bundle extras) {
|
| + synchronized (mLock) {
|
| + if (mSessionIdToUid.get(sessionId) == null) return false;
|
| + int uid = mSessionIdToUid.get(sessionId);
|
| + IBrowserConnectionCallback cb = mUidToCallback.get(uid);
|
| + if (cb == null) return false;
|
| + try {
|
| + cb.onUserNavigation(sessionId, url, extras);
|
| + } catch (RemoteException e) {
|
| + return false;
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @return true iff the UID is associated with a process having a foreground importance.
|
| + */
|
| + private boolean isUidForeground(int uid) {
|
| + ActivityManager am =
|
| + (ActivityManager) mApplication.getSystemService(Context.ACTIVITY_SERVICE);
|
| + List<ActivityManager.RunningAppProcessInfo> running = am.getRunningAppProcesses();
|
| + for (ActivityManager.RunningAppProcessInfo rpi : running) {
|
| + boolean isForeground =
|
| + rpi.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
| + if (rpi.uid == uid && isForeground) return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Called when a remote client has died.
|
| + */
|
| + private void cleanupAlreadyLocked(int uid) {
|
| + List<Long> keysToRemove = new ArrayList<Long>();
|
| + // TODO(lizeb): If iterating through all the session IDs is too costly,
|
| + // use two mappings.
|
| + for (int i = 0; i < mSessionIdToUid.size(); i++) {
|
| + if (mSessionIdToUid.valueAt(i) == uid) keysToRemove.add(mSessionIdToUid.keyAt(i));
|
| + }
|
| + for (Long sessionId : keysToRemove) {
|
| + mSessionIdToUid.remove(sessionId);
|
| + }
|
| + mUidToCallback.remove(uid);
|
| + }
|
| +}
|
|
|