OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.hosted; |
| 6 |
| 7 import android.annotation.SuppressLint; |
| 8 import android.app.ActivityManager; |
| 9 import android.app.Application; |
| 10 import android.content.Context; |
| 11 import android.os.Binder; |
| 12 import android.os.Bundle; |
| 13 import android.os.IBinder; |
| 14 import android.os.RemoteException; |
| 15 import android.util.LongSparseArray; |
| 16 import android.util.SparseArray; |
| 17 |
| 18 import org.chromium.base.Log; |
| 19 import org.chromium.base.ThreadUtils; |
| 20 import org.chromium.base.annotations.SuppressFBWarnings; |
| 21 import org.chromium.base.library_loader.ProcessInitException; |
| 22 import org.chromium.chrome.browser.ChromiumApplication; |
| 23 import org.chromium.chrome.browser.WarmupManager; |
| 24 import org.chromium.content.browser.ChildProcessLauncher; |
| 25 import org.chromium.content_public.browser.WebContents; |
| 26 |
| 27 import java.security.SecureRandom; |
| 28 import java.util.ArrayList; |
| 29 import java.util.List; |
| 30 import java.util.concurrent.atomic.AtomicBoolean; |
| 31 |
| 32 /** |
| 33 * Implementation of the IBrowserConnectionService interface. |
| 34 */ |
| 35 class ChromeBrowserConnection extends IBrowserConnectionService.Stub { |
| 36 private static final String TAG = Log.makeTag("ChromeConnection"); |
| 37 private static final long RESULT_OK = 0; |
| 38 private static final long RESULT_ERROR = -1; |
| 39 |
| 40 private static final Object sConstructionLock = new Object(); |
| 41 private static ChromeBrowserConnection sInstance; |
| 42 |
| 43 private final Application mApplication; |
| 44 private final AtomicBoolean mWarmupHasBeenCalled; |
| 45 |
| 46 private final Object mLock; |
| 47 private final SparseArray<IBrowserConnectionCallback> mUidToCallback; |
| 48 private final LongSparseArray<Integer> mSessionIdToUid; |
| 49 |
| 50 private ChromeBrowserConnection(Application application) { |
| 51 super(); |
| 52 mApplication = application; |
| 53 mWarmupHasBeenCalled = new AtomicBoolean(); |
| 54 mLock = new Object(); |
| 55 mUidToCallback = new SparseArray<IBrowserConnectionCallback>(); |
| 56 mSessionIdToUid = new LongSparseArray<Integer>(); |
| 57 } |
| 58 |
| 59 /** |
| 60 * @return The unique instance of ChromeBrowserConnection. |
| 61 */ |
| 62 public static ChromeBrowserConnection getInstance(Application application) { |
| 63 synchronized (sConstructionLock) { |
| 64 if (sInstance == null) sInstance = new ChromeBrowserConnection(appli
cation); |
| 65 } |
| 66 return sInstance; |
| 67 } |
| 68 |
| 69 @Override |
| 70 public long finishSetup(IBrowserConnectionCallback callback) { |
| 71 final int uid = Binder.getCallingUid(); |
| 72 synchronized (mLock) { |
| 73 if (mUidToCallback.get(uid) != null) return RESULT_ERROR; |
| 74 try { |
| 75 callback.asBinder().linkToDeath(new IBinder.DeathRecipient() { |
| 76 @Override |
| 77 public void binderDied() { |
| 78 synchronized (mLock) { |
| 79 cleanupAlreadyLocked(uid); |
| 80 } |
| 81 } |
| 82 }, 0); |
| 83 } catch (RemoteException e) { |
| 84 // The return code doesn't matter, because this executes when |
| 85 // the caller has died. |
| 86 return RESULT_ERROR; |
| 87 } |
| 88 mUidToCallback.put(uid, callback); |
| 89 } |
| 90 return RESULT_OK; |
| 91 } |
| 92 |
| 93 @Override |
| 94 public long warmup(long flags) { |
| 95 // Here and in mayLaunchUrl(), don't do expensive work for background ap
plications. |
| 96 if (!isUidForeground(Binder.getCallingUid())) return RESULT_ERROR; |
| 97 if (!mWarmupHasBeenCalled.compareAndSet(false, true)) return RESULT_OK; |
| 98 // The call is non-blocking and this must execute on the UI thread, post
a task. |
| 99 ThreadUtils.postOnUiThread(new Runnable() { |
| 100 @Override |
| 101 @SuppressFBWarnings("DM_EXIT") |
| 102 public void run() { |
| 103 try { |
| 104 // TODO(lizeb): Warm up more of the browser. |
| 105 ChromiumApplication app = (ChromiumApplication) mApplication
; |
| 106 app.startBrowserProcessesAndLoadLibrariesSync( |
| 107 app.getApplicationContext(), true); |
| 108 ChildProcessLauncher.warmUp(app.getApplicationContext()); |
| 109 } catch (ProcessInitException e) { |
| 110 Log.e(TAG, "ProcessInitException while starting the browser
process."); |
| 111 // Cannot do anything without the native library, and cannot
show a |
| 112 // dialog to the user. |
| 113 System.exit(-1); |
| 114 } |
| 115 } |
| 116 }); |
| 117 return RESULT_OK; |
| 118 } |
| 119 |
| 120 @Override |
| 121 @SuppressLint("TrulyRandom") // TODO(lizeb): Figure out whether using Secure
Random is OK. |
| 122 public long newSession() { |
| 123 synchronized (mLock) { |
| 124 long sessionId; |
| 125 SecureRandom randomSource = new SecureRandom(); |
| 126 do { |
| 127 sessionId = randomSource.nextLong(); |
| 128 // Because Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE. |
| 129 if (sessionId == Long.MIN_VALUE) continue; |
| 130 sessionId = Math.abs(sessionId); |
| 131 } while (sessionId == 0 || mSessionIdToUid.get(sessionId) != null); |
| 132 mSessionIdToUid.put(sessionId, Binder.getCallingUid()); |
| 133 return sessionId; |
| 134 } |
| 135 } |
| 136 |
| 137 @Override |
| 138 public long mayLaunchUrl( |
| 139 long sessionId, final String url, Bundle extras, List<Bundle> otherL
ikelyBundles) { |
| 140 if (!isUidForeground(Binder.getCallingUid())) return RESULT_ERROR; |
| 141 synchronized (mLock) { |
| 142 if (mSessionIdToUid.get(sessionId) == null) return RESULT_ERROR; |
| 143 } |
| 144 ThreadUtils.postOnUiThread(new Runnable() { |
| 145 @Override |
| 146 public void run() { |
| 147 WarmupManager.getInstance().maybePrefetchDnsForUrlInBackground( |
| 148 mApplication.getApplicationContext(), url); |
| 149 } |
| 150 }); |
| 151 // TODO(lizeb): Prerendering. |
| 152 return sessionId; |
| 153 } |
| 154 |
| 155 /** |
| 156 * Transfers a prerendered WebContents if one exists. |
| 157 * |
| 158 * This resets the internal WebContents; a subsequent call to this method |
| 159 * returns null. |
| 160 * |
| 161 * @param sessionId The session ID, returned by {@link newSession}. |
| 162 * @param url The URL the WebContents is for. |
| 163 * @param extras from the intent. |
| 164 * @return The prerendered WebContents, or null. |
| 165 */ |
| 166 WebContents takePrerenderedUrl(long sessionId, String url, Bundle extras) { |
| 167 // TODO(lizeb): Pre-rendering. |
| 168 return null; |
| 169 } |
| 170 |
| 171 /** |
| 172 * Calls the onUserNavigation callback for a given sessionId. |
| 173 * |
| 174 * This is non-blocking. |
| 175 * |
| 176 * @param sessionId Session ID associated with the callback |
| 177 * @param url URL the user has navigated to. |
| 178 * @param bundle Reserved for future use. |
| 179 * @return false if there is no client to deliver the callback to. |
| 180 */ |
| 181 boolean deliverOnUserNavigationCallback(long sessionId, String url, Bundle e
xtras) { |
| 182 synchronized (mLock) { |
| 183 if (mSessionIdToUid.get(sessionId) == null) return false; |
| 184 int uid = mSessionIdToUid.get(sessionId); |
| 185 IBrowserConnectionCallback cb = mUidToCallback.get(uid); |
| 186 if (cb == null) return false; |
| 187 try { |
| 188 cb.onUserNavigation(sessionId, url, extras); |
| 189 } catch (RemoteException e) { |
| 190 return false; |
| 191 } |
| 192 } |
| 193 return true; |
| 194 } |
| 195 |
| 196 /** |
| 197 * @return true iff the UID is associated with a process having a foreground
importance. |
| 198 */ |
| 199 private boolean isUidForeground(int uid) { |
| 200 ActivityManager am = |
| 201 (ActivityManager) mApplication.getSystemService(Context.ACTIVITY
_SERVICE); |
| 202 List<ActivityManager.RunningAppProcessInfo> running = am.getRunningAppPr
ocesses(); |
| 203 for (ActivityManager.RunningAppProcessInfo rpi : running) { |
| 204 boolean isForeground = |
| 205 rpi.importance == ActivityManager.RunningAppProcessInfo.IMPO
RTANCE_FOREGROUND; |
| 206 if (rpi.uid == uid && isForeground) return true; |
| 207 } |
| 208 return false; |
| 209 } |
| 210 |
| 211 /** |
| 212 * Called when a remote client has died. |
| 213 */ |
| 214 private void cleanupAlreadyLocked(int uid) { |
| 215 List<Long> keysToRemove = new ArrayList<Long>(); |
| 216 // TODO(lizeb): If iterating through all the session IDs is too costly, |
| 217 // use two mappings. |
| 218 for (int i = 0; i < mSessionIdToUid.size(); i++) { |
| 219 if (mSessionIdToUid.valueAt(i) == uid) keysToRemove.add(mSessionIdTo
Uid.keyAt(i)); |
| 220 } |
| 221 for (Long sessionId : keysToRemove) { |
| 222 mSessionIdToUid.remove(sessionId); |
| 223 } |
| 224 mUidToCallback.remove(uid); |
| 225 } |
| 226 } |
OLD | NEW |