chromium/chrome/browser/profiles/android/java/src/org/chromium/chrome/browser/profiles/OTRProfileID.java

// Copyright 2020 The Chromium Authors
// 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.profiles;

import android.text.TextUtils;

import androidx.annotation.Nullable;

import org.jni_zero.CalledByNative;
import org.jni_zero.NativeMethods;

/** Wrapper that allows passing a OTRProfileID reference around in the Java layer. */
public class OTRProfileID {
    private final String mProfileID;
    // OTRProfileID value should be same with Profile::OTRProfileID::PrimaryID in native.
    private static final OTRProfileID sPrimaryOTRProfileID =
            new OTRProfileID("profile::primary_otr");
    private static final String INCOGNITO_CCT_OTR_PROFILE_ID_PREFIX = "CCT:Incognito";

    @CalledByNative
    public OTRProfileID(String profileID) {
        assert profileID != null;
        mProfileID = profileID;
    }

    @CalledByNative
    private String getProfileID() {
        return mProfileID;
    }

    /** Creates a unique profile id by appending a unique serial number to the given prefix. */
    public static OTRProfileID createUnique(String profileIDPrefix) {
        return OTRProfileIDJni.get().createUniqueOTRProfileID(profileIDPrefix);
    }

    /**
     * Creates a new unique Incognito CCT profile id by appending a unique serial number to the iCCT
     * OTR profile id prefix {@link #INCOGNITO_CCT_OTR_PROFILE_ID_PREFIX}.
     */
    public static OTRProfileID createUniqueIncognitoCCTId() {
        return OTRProfileIDJni.get().createUniqueOTRProfileID(INCOGNITO_CCT_OTR_PROFILE_ID_PREFIX);
    }

    /**
     * Deconstruct OTRProfileID to a string representation. Deconstructed version will be used to
     * pass the id to the native side and /components/ layer.
     *
     * @param otrProfileID An OTRProfileId instance.
     * @return A string that represents the given otrProfileID.
     */
    @CalledByNative
    public static String serialize(OTRProfileID otrProfileID) {
        // The OTRProfileID might be null, if it represents the regular profile.
        if (otrProfileID == null) return null;

        return otrProfileID.toString();
    }

    /**
     * Construct OTRProfileID from the string representation.
     *
     * @param value The string representation of the OTRProfileID that is generated with {@link
     *         OTRProfileID#serialize} function.
     * @return An OTRProfileID instance.
     * @throws IllegalStateException when the OTR profile belongs to the OTRProfileID is not
     *         available. The off-the-record profile should exist when OTRProfileId will be used.
     */
    public static OTRProfileID deserialize(String value) {
        OTRProfileID otrProfileId = deserializeWithoutVerify(value);

        // The off-the-record profile should exist for the given OTRProfileID, since OTRProfileID
        // creation is completed just before the profile creation. So there should always be a
        // profile for the id. If OTR profile is not available, deserialize function should not be
        // called.
        if (otrProfileId != null
                && !ProfileManager.getLastUsedRegularProfile()
                        .hasOffTheRecordProfile(otrProfileId)) {
            throw new IllegalStateException("The OTR profile should exist for otr profile id.");
        }

        return otrProfileId;
    }

    /**
     * Construct OTRProfileID from the string representation. It is possible this {@link
     * OTRProfileID} does not correspond to a real profile.
     * @param value The string representation of the OTRProfileID that is generated with {@link
     *        OTRProfileID#serialize} function.
     * @return An OTRProfileID instance.
     */
    @CalledByNative
    public static OTRProfileID deserializeWithoutVerify(String value) {
        // The value might be null, if it represents the regular profile.
        if (TextUtils.isEmpty(value)) return null;

        // Check if the format is align with |OTRProfileID#toString| function.
        assert value.startsWith("OTRProfileID{") && value.endsWith("}");

        // Be careful when changing here. This should be consistent with |OTRProfileID#toString|
        // function.
        String id = value.substring("OTRProfileID{".length(), value.length() - 1);
        OTRProfileID otrProfileId = new OTRProfileID(id);

        return otrProfileId;
    }

    public boolean isPrimaryOTRId() {
        return this.equals(sPrimaryOTRProfileID);
    }

    /**
     * Returns true if the OTR profile id starts with {@link #INCOGNITO_CCT_OTR_PROFILE_ID_PREFIX}.
     */
    public boolean isIncognitoCCId() {
        return this.getProfileID().startsWith(INCOGNITO_CCT_OTR_PROFILE_ID_PREFIX);
    }

    /**
     * @return The OTRProfileID of the primary off-the-record profile.
     */
    public static OTRProfileID getPrimaryOTRProfileID() {
        return sPrimaryOTRProfileID;
    }

    /**
     * Returns true for id of primary and non-primary off-the-record profiles. Otherwise returns
     * false.
     * @param profileID The OTRProfileID
     * @return Whether given OTRProfileID belongs to a off-the-record profile.
     */
    public static boolean isOffTheRecord(@Nullable OTRProfileID profileID) {
        return profileID != null;
    }

    /**
     * Checks whether the given OTRProfileIDs belong to the same profile.
     * @param otrProfileID1 The first OTRProfileID
     * @param otrProfileID2 The second OTRProfileID
     * @return Whether the given OTRProfileIDs are equals.
     */
    public static boolean areEqual(
            @Nullable OTRProfileID otrProfileID1, @Nullable OTRProfileID otrProfileID2) {
        // If both OTRProfileIDs null, then both belong to the regular profile.
        if (otrProfileID1 == null) return otrProfileID2 == null;

        return otrProfileID1.equals(otrProfileID2);
    }

    /**
     * Checks whether the given OTRProfileID strings belong to the same profile.
     * @param otrProfileID1 The string of first OTRProfileID
     * @param otrProfileID2 The string of second OTRProfileID
     * @return Whether the given OTRProfileIDs are equals.
     */
    public static boolean areEqual(@Nullable String otrProfileID1, @Nullable String otrProfileID2) {
        // If both OTRProfileIDs null, then both belong to the regular profile.
        if (TextUtils.isEmpty(otrProfileID1)) {
            return TextUtils.isEmpty(otrProfileID2);
        }

        return otrProfileID1.equals(otrProfileID2);
    }

    @Override
    public String toString() {
        return String.format("OTRProfileID{%s}", mProfileID);
    }

    @Override
    public int hashCode() {
        return mProfileID.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof OTRProfileID)) return false;
        OTRProfileID other = (OTRProfileID) obj;
        return mProfileID.equals(other.mProfileID);
    }

    @NativeMethods
    public interface Natives {
        OTRProfileID createUniqueOTRProfileID(String profileIDPrefix);

        OTRProfileID getPrimaryID();
    }
}