// 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.tab;
import android.content.Context;
import android.content.SharedPreferences;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.tabmodel.TabModel;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Maintains a monotonically increasing ID that is used for uniquely identifying {@link Tab}s. This
* class is responsible for ensuring that Tabs created in the same process, across every
* {@link TabModel}, are allocated a unique ID. Note that only the browser process should be
* generating Tab IDs to prevent collisions.
*
* Calling {@link TabIdManager#incrementIdCounterTo(int)} will ensure new {@link Tab}s get IDs
* greater than or equal to the parameter passed to that method. This should be used when doing
* things like loading persisted {@link Tab}s from disk on process start to ensure all new
* {@link Tab}s don't have id collision.
*
* TODO(dfalcantara): Tab ID generation prior to M45 is haphazard and dependent on which Activity is
* started first. Unify the ways the maximum Tab ID is set (crbug.com/502384).
*/
public class TabIdManager {
@VisibleForTesting
public static final String PREF_NEXT_ID =
"org.chromium.chrome.browser.tab.TabIdManager.NEXT_ID";
private static final Object INSTANCE_LOCK = new Object();
private static TabIdManager sInstance;
private final Context mContext;
private final AtomicInteger mIdCounter = new AtomicInteger();
private SharedPreferences mPreferences;
/** Returns the Singleton instance of the TabIdManager. */
public static TabIdManager getInstance() {
return getInstance(ContextUtils.getApplicationContext());
}
/** Returns the Singleton instance of the TabIdManager. */
@VisibleForTesting
static TabIdManager getInstance(Context context) {
synchronized (INSTANCE_LOCK) {
if (sInstance == null) sInstance = new TabIdManager(context);
}
return sInstance;
}
/**
* Validates {@code id} and increments the internal counter to make sure future ids don't
* collide.
* @param id The current id. May be {@link #INVALID_TAB_ID}.
* @return A new id if {@code id} was {@link #INVALID_TAB_ID}, or {@code id}.
*/
public final int generateValidId(int id) {
if (id == Tab.INVALID_TAB_ID) id = mIdCounter.getAndIncrement();
incrementIdCounterTo(id + 1);
return id;
}
/**
* Ensures the counter is at least as high as the specified value. The counter should always
* point to an unused ID (which will be handed out next time a request comes in). Exposed so
* that anything externally loading tabs and ids can set enforce new tabs start at the correct
* id.
* TODO(dfalcantara): Reduce the visibility of this method once all TabModels are united in how
* the IDs are assigned (crbug.com/502384).
* @param id The minimum id we should hand out to the next new tab.
*/
public final void incrementIdCounterTo(int id) {
int diff = id - mIdCounter.get();
if (diff < 0) return;
// It's possible idCounter has been incremented between the get and the add but that's OK --
// in the worst case mIdCounter will just be overly incremented.
mIdCounter.addAndGet(diff);
mPreferences.edit().putInt(PREF_NEXT_ID, mIdCounter.get()).apply();
}
private TabIdManager(Context context) {
mContext = context;
// Read the shared preference. This has to be done on the critical path to ensure that the
// myriad Activities that serve as entries into Chrome are all synchronized on the correct
// maximum Tab ID.
mPreferences = ContextUtils.getAppSharedPreferences();
mIdCounter.set(mPreferences.getInt(PREF_NEXT_ID, 0));
}
}