chromium/chrome/android/java/src/org/chromium/chrome/browser/firstrun/ChildAccountStatusSupplier.java

// Copyright 2022 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.firstrun;

import android.os.SystemClock;

import androidx.annotation.Nullable;

import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.AccountUtils;

/**
 * Fetches the child account status to be used by other FRE components.
 *
 * This class checks app restrictions for Chrome to obtain the child account status faster. This
 * optimisation leverages the fact that FamilyLink always pushes some policies for Chrome on
 * supervised devices. So, if there are no app restrictions specified for Chrome -
 * {@link ChildAccountStatusSupplier} will consider that the child account status is false.
 * Note: this optimisation creates a potential conflict if there are no app restrictions on
 * a supervised device. However, this should never happen on real devices.
 */
public class ChildAccountStatusSupplier implements OneshotSupplier<Boolean> {
    private final OneshotSupplierImpl<Boolean> mValue = new OneshotSupplierImpl<>();
    private final long mChildAccountStatusStartTime;

    private Boolean mHasRestriction;
    private Boolean mChildAccountStatusFromAccountManagerFacade;

    /**
     * Creates ChildAccountStatusSupplier and starts fetching the child account status.
     * @param accountManagerFacade {@link AccountManagerFacade} instance to use for getting accounts
     * @param appRestrictionInfo instance of {@link FirstRunAppRestrictionInfo} that can
     *         be used to check app restrictions (see class-level JavaDoc).
     */
    public ChildAccountStatusSupplier(
            AccountManagerFacade accountManagerFacade,
            FirstRunAppRestrictionInfo appRestrictionInfo) {
        mChildAccountStatusStartTime = SystemClock.elapsedRealtime();

        appRestrictionInfo.getHasAppRestriction(this::onAppRestrictionDetected);

        accountManagerFacade
                .getCoreAccountInfos()
                .then(
                        coreAccountInfos -> {
                            AccountUtils.checkChildAccountStatus(
                                    accountManagerFacade,
                                    coreAccountInfos,
                                    (isChild, account) -> onChildAccountStatusReady(isChild));
                        });
    }

    @Override
    public Boolean onAvailable(Callback<Boolean> callback) {
        return mValue.onAvailable(callback);
    }

    @Override
    public Boolean get() {
        return mValue.get();
    }

    private void onAppRestrictionDetected(boolean hasAppRestriction) {
        mHasRestriction = hasAppRestriction;
        setSupplierIfDecidable();
    }

    private void onChildAccountStatusReady(boolean isChild) {
        mChildAccountStatusFromAccountManagerFacade = isChild;
        setSupplierIfDecidable();
    }

    private void setSupplierIfDecidable() {
        // Early return if the value has been set.
        if (mValue.get() != null) return;

        Boolean value = tryCalculateSupplierValue();
        if (value == null) return;

        RecordHistogram.recordTimesHistogram(
                "MobileFre.ChildAccountStatusDuration",
                SystemClock.elapsedRealtime() - mChildAccountStatusStartTime);
        mValue.set(value);
    }

    private @Nullable Boolean tryCalculateSupplierValue() {
        if (mChildAccountStatusFromAccountManagerFacade != null) {
            // Child account status from AccountManagerFacade is more reliable than app
            // restrictions, so use it if available.
            return mChildAccountStatusFromAccountManagerFacade;
        }

        boolean confirmedNoAppRestriction = mHasRestriction != null && !mHasRestriction;
        if (confirmedNoAppRestriction) {
            // No app restriction is found. On real devices this means that there are no child
            // accounts on the device, as FamilyLink pushes some policies for supervised devices.
            return false;
        }
        // Otherwise, we can't determine the supplier value yet.
        return null;
    }
}