Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3696)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/customtabs/ClientManager.java

Issue 1370473002: customtabs: Refactor CustomTabsConnection to simplify locking. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Quiet, Findbugs! Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/android/java/src/org/chromium/chrome/browser/customtabs/ClientManager.java
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/ClientManager.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/ClientManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec2226c7de16dcee341d57d648126ce49d97d865
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/ClientManager.java
@@ -0,0 +1,267 @@
+// 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.customtabs;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.customtabs.ICustomTabsCallback;
+import android.text.TextUtils;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.IntentHandler;
+import org.chromium.content_public.common.Referrer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/** Manages the clients' state for Custom Tabs. This class is threadsafe. */
+class ClientManager {
+ // Values for the "CustomTabs.PredictionStatus" UMA histogram. Append-only.
+ private static final int NO_PREDICTION = 0;
+ private static final int GOOD_PREDICTION = 1;
+ private static final int BAD_PREDICTION = 2;
+ private static final int PREDICTION_STATUS_COUNT = 3;
+
+ /** Per-session values. */
+ private static class SessionParams {
+ public final int uid;
+ public final Referrer referrer;
+ public final ICustomTabsCallback callback;
+ public final IBinder.DeathRecipient deathRecipient;
+ private ServiceConnection mKeepAliveConnection = null;
+ private String mPredictedUrl = null;
+ private long mLastMayLaunchUrlTimestamp = 0;
+
+ public SessionParams(Context context, int uid, ICustomTabsCallback callback,
+ IBinder.DeathRecipient deathRecipient) {
+ this.uid = uid;
+ referrer = constructReferrer(context, uid);
+ this.callback = callback;
+ this.deathRecipient = deathRecipient;
+ }
+
+ private static Referrer constructReferrer(Context context, int uid) {
+ PackageManager packageManager = context.getPackageManager();
+ String[] packageList = packageManager.getPackagesForUid(uid);
+ if (packageList.length != 1 || TextUtils.isEmpty(packageList[0])) return null;
+ return IntentHandler.constructValidReferrerForAuthority(packageList[0]);
+ }
+
+ public ServiceConnection getKeepAliveConnection() {
+ return mKeepAliveConnection;
+ }
+
+ public void setKeepAliveConnection(ServiceConnection serviceConnection) {
+ mKeepAliveConnection = serviceConnection;
+ }
+
+ public void setPredictionMetrics(String predictedUrl, long lastMayLaunchUrlTimestamp) {
+ mPredictedUrl = predictedUrl;
+ mLastMayLaunchUrlTimestamp = lastMayLaunchUrlTimestamp;
+ }
+
+ public String getPredictedUrl() {
+ return mPredictedUrl;
+ }
+
+ public long getLastMayLaunchUrlTimestamp() {
+ return mLastMayLaunchUrlTimestamp;
+ }
+ }
+
+ /** To be called when a client gets disconnected. */
+ public interface DisconnectCallback { public void run(IBinder session); }
+
+ private final Context mContext;
+ private final Map<IBinder, SessionParams> mSessionParams = new HashMap<>();
+
+ public ClientManager(Context context) {
+ mContext = context.getApplicationContext();
+ RequestThrottler.purgeOldEntries(mContext);
+ }
+
+ /** Creates a new session.
+ *
+ * @param cb Callback provided by the client.
+ * @param uid Client UID, as returned by Binder.getCallingUid(),
+ * @param onDisconnect To be called on the UI thread when a client gets disconnected.
+ * @return true for success.
+ */
+ public boolean newSession(
+ ICustomTabsCallback cb, int uid, final DisconnectCallback onDisconnect) {
+ if (cb == null) return false;
+ final IBinder session = cb.asBinder();
+ IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ ThreadUtils.postOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ cleanupSession(session);
+ onDisconnect.run(session);
+ }
+ });
+ }
+ };
+ SessionParams params = new SessionParams(mContext, uid, cb, deathRecipient);
+ synchronized (this) {
+ if (mSessionParams.containsKey(session)) return false;
+ try {
+ session.linkToDeath(deathRecipient, 0);
+ } catch (RemoteException e) {
+ // The return code doesn't matter, because this executes when
+ // the caller has died.
+ return false;
+ }
+ mSessionParams.put(session, params);
+ }
+ return true;
+ }
+
+ /** Updates the client behavior stats and returns whether speculation is allowed.
+ *
+ * @param session Client session.
+ * @param uid As returned by Binder.getCallingUid().
+ * @param url Predicted URL.
+ * @return true if speculation is allowed.
+ */
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized boolean updateStatsAndReturnWhetherAllowed(
+ IBinder session, int uid, String url) {
+ SessionParams params = mSessionParams.get(session);
+ if (params == null || params.uid != uid) return false;
+ params.setPredictionMetrics(url, SystemClock.elapsedRealtime());
+ RequestThrottler throttler = RequestThrottler.getForUid(mContext, uid);
+ return throttler.updateStatsAndReturnWhetherAllowed();
+ }
+
+ /**
+ * Registers that a client has launched a URL inside a Custom Tab.
+ */
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized void registerLaunch(IBinder session, String url) {
+ int outcome = NO_PREDICTION;
+ long elapsedTimeMs = -1;
+ SessionParams params = mSessionParams.get(session);
+ if (params != null) {
+ String predictedUrl = params.getPredictedUrl();
+ outcome = predictedUrl == null ? NO_PREDICTION : predictedUrl.equals(url)
+ ? GOOD_PREDICTION
+ : BAD_PREDICTION;
+ long now = SystemClock.elapsedRealtime();
+ elapsedTimeMs = now - params.getLastMayLaunchUrlTimestamp();
+ params.setPredictionMetrics(null, 0);
+ if (outcome == GOOD_PREDICTION) {
+ RequestThrottler.getForUid(mContext, params.uid).registerSuccess(url);
+ }
+ }
+ RecordHistogram.recordEnumeratedHistogram(
+ "CustomTabs.PredictionStatus", outcome, PREDICTION_STATUS_COUNT);
+ if (outcome == GOOD_PREDICTION) {
+ RecordHistogram.recordCustomTimesHistogram("CustomTabs.PredictionToLaunch",
+ elapsedTimeMs, 1, TimeUnit.MINUTES.toMillis(3), TimeUnit.MILLISECONDS, 100);
+ }
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized Referrer getReferrerForSession(IBinder session) {
+ SessionParams params = mSessionParams.get(session);
+ return params != null ? params.referrer : null;
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized ICustomTabsCallback getCallbackForSession(IBinder session) {
+ SessionParams params = mSessionParams.get(session);
+ return params != null ? params.callback : null;
+ }
+
+ /** Tries to bind to a client to keep it alive, and returns true for success. */
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized boolean keepAliveForSession(IBinder session, Intent intent) {
+ // When an application is bound to a service, its priority is raised to
+ // be at least equal to the application's one. This binds to a dummy
+ // service (no calls to this service are made).
+ if (intent == null || intent.getComponent() == null) return false;
+ SessionParams params = mSessionParams.get(session);
+ if (params == null) return false;
+
+ String packageName = intent.getComponent().getPackageName();
+ PackageManager pm = mContext.getApplicationContext().getPackageManager();
+ // Only binds to the application associated to this session.
+ if (!Arrays.asList(pm.getPackagesForUid(params.uid)).contains(packageName)) return false;
+ Intent serviceIntent = new Intent().setComponent(intent.getComponent());
+ // This ServiceConnection doesn't handle disconnects. This is on
+ // purpose, as it occurs when the remote process has died. Since the
+ // only use of this connection is to keep the application alive,
+ // re-connecting would just re-create the process, but the application
+ // state has been lost at that point, the callbacks invalidated, etc.
+ ServiceConnection connection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {}
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+ };
+ boolean ok;
+ try {
+ ok = mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE);
+ } catch (SecurityException e) {
+ return false;
+ }
+ if (ok) params.setKeepAliveConnection(connection);
+ return ok;
+ }
+
+ /** Unbind from the KeepAlive service for a client. */
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized void dontKeepAliveForSession(IBinder session) {
+ SessionParams params = mSessionParams.get(session);
+ if (params == null || params.getKeepAliveConnection() == null) return;
+ ServiceConnection connection = params.getKeepAliveConnection();
+ params.setKeepAliveConnection(null);
+ mContext.unbindService(connection);
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized boolean isPrerenderingAllowed(int uid) {
+ return RequestThrottler.getForUid(mContext, uid).isPrerenderingAllowed();
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized void registerPrerenderRequest(int uid, String url) {
+ RequestThrottler.getForUid(mContext, uid).registerPrerenderRequest(url);
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized void resetThrottling(int uid) {
+ RequestThrottler.getForUid(mContext, uid).reset();
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ public synchronized void cleanupAll() {
+ List<IBinder> sessions = new ArrayList<>(mSessionParams.keySet());
+ for (IBinder session : sessions) cleanupSession(session);
+ }
+
+ @SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
+ private synchronized void cleanupSession(IBinder session) {
+ SessionParams params = mSessionParams.get(session);
+ if (params == null) return;
+ mSessionParams.remove(session);
+ IBinder binder = params.callback.asBinder();
+ binder.unlinkToDeath(params.deathRecipient, 0);
+ }
+}
« no previous file with comments | « no previous file | chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698