chromium/android_webview/java/src/org/chromium/android_webview/common/DeveloperModeUtils.java

// Copyright 2019 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.android_webview.common;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;

import org.chromium.android_webview.common.services.ServiceNames;
import org.chromium.base.ContextUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * Utilities for communication with the developer mode ContentProvider.
 *
 * <p>This should only be called in processes which have called {@link
 * ContextUtils.initApplicationContext(Context)}.
 */
public final class DeveloperModeUtils {
    // Do not instantiate this class.
    private DeveloperModeUtils() {}

    public static final String DEVELOPER_MODE_STATE_COMPONENT =
            "org.chromium.android_webview.devui.DeveloperModeState";
    public static final String URI_AUTHORITY_SUFFIX = ".DeveloperModeContentProvider";
    public static final String FLAG_OVERRIDE_URI_PATH = "/flag-overrides";
    public static final String FLAG_OVERRIDE_NAME_COLUMN = "flagName";
    public static final String FLAG_OVERRIDE_STATE_COLUMN = "flagState";

    /**
     * Quickly determine whether developer mode is enabled. Developer mode is off-by-default.
     *
     * <p>This makes no guarantees about which processes are alive, it only indicates whether the
     * user has stepped in or out of "developer mode." Developer mode may be enabled and the
     * ContentProvider process may be dead if the user has taken a WebView update since enabling
     * developer mode.
     *
     * @param webViewPackageName the package name of the WebView implementation to fetch the flags
     *     from (generally this is the current WebView provider).
     */
    public static boolean isDeveloperModeEnabled(String webViewPackageName) {
        final Context context = ContextUtils.getApplicationContext();
        ComponentName developerModeComponent =
                new ComponentName(webViewPackageName, DEVELOPER_MODE_STATE_COMPONENT);
        int enabledState =
                context.getPackageManager().getComponentEnabledSetting(developerModeComponent);
        return enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
    }

    private static void startDeveloperUiService(String webViewPackageName) {
        final Context context = ContextUtils.getApplicationContext();
        Intent intent = new Intent();
        intent.setClassName(webViewPackageName, ServiceNames.DEVELOPER_UI_SERVICE);
        // Best effort attempt to start the service. If this fails, proceed anyway.
        try {
            context.startForegroundService(intent);
        } catch (IllegalStateException e) {
            assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
                    : "Unable to start DeveloperUiService, this is only expected on Android S";
        }
    }

    /**
     * Fetch the flag overrides from the developer mode ContentProvider. This should only be called
     * if {@link #isDeveloperModeEnabled(String)} returns {@code true}, otherwise this may incur
     * unnecessary IPC or start up processes unnecessarily.
     *
     * @param webViewPackageName the package name of the WebView implementation to fetch the flags
     *     from (generally this is the current WebView provider).
     */
    public static Map<String, Boolean> getFlagOverrides(String webViewPackageName) {
        Map<String, Boolean> flagOverrides = new HashMap<>();

        Uri uri =
                new Uri.Builder()
                        .scheme("content")
                        .authority(webViewPackageName + URI_AUTHORITY_SUFFIX)
                        .path(FLAG_OVERRIDE_URI_PATH)
                        .build();
        final Context appContext = ContextUtils.getApplicationContext();
        startDeveloperUiService(webViewPackageName);
        try (Cursor cursor =
                appContext
                        .getContentResolver()
                        .query(
                                uri,
                                /* projection= */ null,
                                /* selection= */ null,
                                /* selectionArgs= */ null,
                                /* sortOrder= */ null)) {
            assert cursor != null : "ContentProvider doesn't support querying '" + uri + "'";
            int flagNameColumnIndex = cursor.getColumnIndexOrThrow(FLAG_OVERRIDE_NAME_COLUMN);
            int flagStateColumnIndex = cursor.getColumnIndexOrThrow(FLAG_OVERRIDE_STATE_COLUMN);
            while (cursor.moveToNext()) {
                String flagName = cursor.getString(flagNameColumnIndex);
                boolean flagState = cursor.getInt(flagStateColumnIndex) != 0;
                flagOverrides.put(flagName, flagState);
            }
        }
        return flagOverrides;
    }
}