chromium/components/policy/android/java/src/org/chromium/components/policy/AbstractAppRestrictionsProvider.java

// Copyright 2015 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.components.policy;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.StrictMode;

import androidx.annotation.VisibleForTesting;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;

/**
 * Retrieves app restrictions and provides them to the parent class as Bundles.
 *
 * Needs to be subclassed to specify how to retrieve the restrictions.
 */
public abstract class AbstractAppRestrictionsProvider extends PolicyProvider {
    private static final String TAG = "policy";

    /** {@link Bundle} holding the restrictions to be used during tests. */
    private static Bundle sTestRestrictions;

    private final Context mContext;
    private final BroadcastReceiver mAppRestrictionsChangedReceiver =
            new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    refresh();
                }
            };

    /** @param context The application context. */
    public AbstractAppRestrictionsProvider(Context context) {
        mContext = context;
    }

    /**
     * @return The restrictions for the provided package name, an empty bundle if they are not
     * available.
     */
    protected abstract Bundle getApplicationRestrictions(String packageName);

    /**
     * @return The intent action to listen to to be notified of restriction changes,
     * {@code null} if it is not supported. The action will/must be a protected broadcast action.
     */
    protected abstract String getRestrictionChangeIntentAction();

    /**
     * Start listening for restrictions changes. Does nothing if this is not supported by the
     * platform.
     */
    @Override
    public void startListeningForPolicyChanges() {
        String changeIntentAction = getRestrictionChangeIntentAction();
        if (changeIntentAction == null) return;

        ContextUtils.registerProtectedBroadcastReceiver(
                mContext,
                mAppRestrictionsChangedReceiver,
                new IntentFilter(changeIntentAction),
                new Handler(ThreadUtils.getUiThreadLooper()));
    }

    /**
     * Retrieve the restrictions. {@link #notifySettingsAvailable(Bundle)} will be called as a
     * result.
     */
    @Override
    public void refresh() {
        if (sTestRestrictions != null) {
            notifySettingsAvailable(sTestRestrictions);
            return;
        }

        // Because some policies are needed during startup this has to be synchronous. There is
        // no way of reading policies (or cached policies from a previous run) without doing
        // a disk read, so we have to disable strict mode here.
        StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
        long startTime = System.currentTimeMillis();
        final Bundle bundle = getApplicationRestrictions(mContext.getPackageName());
        StrictMode.setThreadPolicy(policy);

        notifySettingsAvailable(bundle);
    }

    @Override
    public void destroy() {
        stopListening();
        super.destroy();
    }

    /**
     * Stop listening for restrictions changes. Does nothing if this is not supported by the
     * platform.
     */
    public void stopListening() {
        if (getRestrictionChangeIntentAction() != null) {
            mContext.unregisterReceiver(mAppRestrictionsChangedReceiver);
        }
    }

    /**
     * Restrictions to be used during tests. Subsequent attempts to retrieve the restrictions will
     * return the provided bundle instead.
     *
     * Chrome and WebView tests are set up to use annotations for policy testing and reset the
     * restrictions to an empty bundle if nothing is specified. To stop using a test bundle,
     * provide {@code null} as value instead.
     */
    @VisibleForTesting
    public static void setTestRestrictions(Bundle policies) {
        Log.d(
                TAG,
                "Test Restrictions: %s",
                (policies == null ? null : policies.keySet().toArray()));
        sTestRestrictions = policies;
    }

    /** Returns whether any restrictions were set using {@link #setTestRestrictions}. */
    public static boolean hasTestRestrictions() {
        return sTestRestrictions != null;
    }
}