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

// Copyright 2013 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 androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

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

import org.chromium.chrome.browser.cookies.CookiesFetcher;
import org.chromium.components.profile_metrics.BrowserProfileType;
import org.chromium.content_public.browser.BrowserContextHandle;
import org.chromium.content_public.browser.WebContents;

/** Wrapper that allows passing a Profile reference around in the Java layer. */
public class Profile implements BrowserContextHandle {
    private final @Nullable OTRProfileID mOtrProfileId;

    /** Pointer to the Native-side Profile. */
    private long mNativeProfile;

    private boolean mDestroyNotified;

    @CalledByNative
    private Profile(long nativeProfile, @Nullable OTRProfileID otrProfileId) {
        mNativeProfile = nativeProfile;
        mOtrProfileId = otrProfileId;
    }

    /**
     * @param webContents {@link WebContents} object.
     * @return {@link Profile} object associated with the given WebContents.
     */
    public static Profile fromWebContents(WebContents webContents) {
        return ProfileJni.get().fromWebContents(webContents);
    }

    /**
     * Handles type conversion of Java side {@link BrowserContextHandle} to {@link Profile}.
     *
     * @param browserContextHandle Java reference to native BrowserContext.
     * @return A strongly typed reference the {@link Profile}.
     */
    public static Profile fromBrowserContextHandle(BrowserContextHandle browserContextHandle) {
        return (Profile) browserContextHandle;
    }

    /**
     * Returns the {@link BrowserProfileType} for the corresponding profile.
     *
     * Please note {@link BrowserProfileType} is generated from native so, it also contains other
     * types of Profile like Guest and System that we don't support in Android.
     */
    public static @BrowserProfileType int getBrowserProfileTypeFromProfile(Profile profile) {
        assert profile != null;

        if (!profile.isOffTheRecord()) return BrowserProfileType.REGULAR;
        if (profile.isPrimaryOTRProfile()) return BrowserProfileType.INCOGNITO;
        return BrowserProfileType.OTHER_OFF_THE_RECORD_PROFILE;
    }

    public Profile getOriginalProfile() {
        return ProfileJni.get().getOriginalProfile(mNativeProfile);
    }

    /**
     * Returns the OffTheRecord profile with given OTRProfileiD. If the profile does not exist and
     * createIfNeeded is true, a new profile is created, otherwise returns null.
     *
     * @param profileID {@link OTRProfileID} object.
     * @param createIfNeeded Boolean indicating the profile should be created if doesn't exist.
     */
    public Profile getOffTheRecordProfile(OTRProfileID profileID, boolean createIfNeeded) {
        assert profileID != null;
        return ProfileJni.get().getOffTheRecordProfile(mNativeProfile, profileID, createIfNeeded);
    }

    /**
     * Returns the OffTheRecord profile for incognito tabs. If the profile does not exist and
     * createIfNeeded is true, a new profile is created, otherwise returns null.
     *
     * @param createIfNeeded Boolean indicating the profile should be created if doesn't exist.
     */
    public Profile getPrimaryOTRProfile(boolean createIfNeeded) {
        return ProfileJni.get().getPrimaryOTRProfile(mNativeProfile, createIfNeeded);
    }

    /**
     * Returns the OffTheRecord profile id for OffTheRecord profiles, and null for regular profiles.
     */
    public @Nullable OTRProfileID getOTRProfileID() {
        return mOtrProfileId;
    }

    /**
     * Returns if OffTheRecord profile with given OTRProfileID exists.
     *
     * @param profileID {@link OTRProfileID} object.
     */
    public boolean hasOffTheRecordProfile(OTRProfileID profileID) {
        assert profileID != null;
        return ProfileJni.get().hasOffTheRecordProfile(mNativeProfile, profileID);
    }

    /** Returns if primary OffTheRecord profile exists. */
    public boolean hasPrimaryOTRProfile() {
        return ProfileJni.get().hasPrimaryOTRProfile(mNativeProfile);
    }

    /** Returns if the profile is a primary OTR Profile. */
    public boolean isPrimaryOTRProfile() {
        return mOtrProfileId != null && mOtrProfileId.isPrimaryOTRId();
    }

    /**
     * Returns if the profile is a primary OTR Profile or an Incognito CCT. The primary OTR profile
     * is the OffTheRecord profile for incognito tabs in the main Chrome App. All Incognito branded
     * profiles return true for {@link #isOffTheRecord()} but not all OffTheRecord profiles are
     * Incognito themed. Use this to evaluate features that should appear exclusively for Incognito
     * themed profiles (Incognito lock, Incognito snapshot controller..) or for usages that force
     * the Incognito theme (Dark colors, Incognito logo..). If you are unsure whether this fits your
     * usage, reach out to incognito/OWNERS.
     */
    public boolean isIncognitoBranded() {
        boolean isIncognitoCCT = mOtrProfileId != null && mOtrProfileId.isIncognitoCCId();
        return isPrimaryOTRProfile() || isIncognitoCCT;
    }

    /**
     * Returns if the profile is off the record. Off the record sessions are not persistent and
     * browsing data generated within this profile is cleared after the session ends. Note that this
     * does not imply Incognito as other OTR sessions (e.g. Ephemeral CCT) are not Incognito
     * branded.
     */
    public boolean isOffTheRecord() {
        return mOtrProfileId != null;
    }

    public ProfileKey getProfileKey() {
        return ProfileJni.get().getProfileKey(mNativeProfile);
    }

    /**
     * @return Whether the profile is signed in to a child account.
     * @deprecated Please use {@link
     *     org.chromium.components.signin.base.AccountCapabilities#isSubjectToParentalControls}
     *     instead.
     */
    @Deprecated
    public boolean isChild() {
        return ProfileJni.get().isChild(mNativeProfile);
    }

    /** Wipes all data for this profile. */
    public void wipe() {
        ProfileJni.get().wipe(mNativeProfile);
    }

    /**
     * @return Whether or not the native side profile exists.
     */
    @VisibleForTesting
    public boolean isNativeInitialized() {
        return mNativeProfile != 0;
    }

    /**
     * When called, raises an exception if the native pointer is not initialized. This is useful to
     * get a more debuggable stacktrace than failing on native-side when dereferencing.
     */
    public void ensureNativeInitialized() {
        if (mNativeProfile == 0) {
            throw new RuntimeException("Native profile pointer not initialized.");
        }
    }

    @Override
    public long getNativeBrowserContextPointer() {
        return mNativeProfile;
    }

    /**
     * Returns whether shutdown has been initiated. This is a signal that the object will be
     * destroyed soon and no new references to this object should be created.
     */
    public boolean shutdownStarted() {
        return mDestroyNotified;
    }

    private void notifyWillBeDestroyed() {
        assert !mDestroyNotified;
        mDestroyNotified = true;

        ProfileManager.onProfileDestroyed(this);

        if (isPrimaryOTRProfile()) {
            CookiesFetcher.scheduleDeleteCookies();
        }
    }

    @CalledByNative
    private void onProfileWillBeDestroyed() {
        notifyWillBeDestroyed();
    }

    @CalledByNative
    private void onNativeDestroyed() {
        mNativeProfile = 0;

        if (!mDestroyNotified) {
            assert false : "Destroy should have been notified previously.";
            notifyWillBeDestroyed();
        }
    }

    @CalledByNative
    private long getNativePointer() {
        return mNativeProfile;
    }

    @NativeMethods
    public interface Natives {
        Profile fromWebContents(WebContents webContents);

        Profile getOriginalProfile(long ptr);

        Profile getOffTheRecordProfile(long ptr, OTRProfileID otrProfileID, boolean createIfNeeded);

        Profile getPrimaryOTRProfile(long ptr, boolean createIfNeeded);

        boolean hasOffTheRecordProfile(long ptr, OTRProfileID otrProfileID);

        boolean hasPrimaryOTRProfile(long ptr);

        boolean isChild(long ptr);

        void wipe(long ptr);

        ProfileKey getProfileKey(long ptr);
    }
}