chromium/chrome/android/java/src/org/chromium/chrome/browser/permissions/PermissionUpdateRequester.java

// Copyright 2021 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.permissions;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;

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

import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.ApplicationStatus.ActivityStateListener;
import org.chromium.base.ContextUtils;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.permissions.PermissionCallback;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Triggers the Android runtime permission prompt UI to request missing
 * Chrome app-level permission(s) needed by the current website which already has the
 * website-level permission, and after the user expressed interest in fixing the situation
 * in the permission update infobar/message ui.
 */
class PermissionUpdateRequester implements PermissionCallback {
    private final WebContents mWebContents;
    private final Set<String> mRequiredAndroidPermissions;
    private final String[] mAndroidPermisisons;
    private long mNativePtr;
    private ActivityStateListener mActivityStateListener;

    @CalledByNative
    private static PermissionUpdateRequester create(
            long nativePtr,
            WebContents webContents,
            String[] requiredPermissions,
            String[] optionalPermissions) {
        return new PermissionUpdateRequester(
                nativePtr, webContents, requiredPermissions, optionalPermissions);
    }

    private PermissionUpdateRequester(
            long nativePtr,
            WebContents webContents,
            String[] requiredPermissions,
            String[] optionalPermissions) {
        mNativePtr = nativePtr;
        mWebContents = webContents;

        mRequiredAndroidPermissions = new HashSet<String>();
        Collections.addAll(mRequiredAndroidPermissions, requiredPermissions);

        Set<String> allPermissions = new HashSet<String>();
        Collections.addAll(allPermissions, requiredPermissions);
        Collections.addAll(allPermissions, optionalPermissions);
        mAndroidPermisisons = allPermissions.toArray(new String[allPermissions.size()]);
    }

    @CalledByNative
    private void onNativeDestroyed() {
        mNativePtr = 0;
        if (mActivityStateListener != null) {
            ApplicationStatus.unregisterActivityStateListener(mActivityStateListener);
            mActivityStateListener = null;
        }
    }

    @CalledByNative
    private void requestPermissions() {
        WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
        if (windowAndroid == null) {
            PermissionUpdateRequesterJni.get().onPermissionResult(mNativePtr, false);
            return;
        }

        boolean canRequestAllPermissions = true;
        for (int i = 0; i < mAndroidPermisisons.length; i++) {
            canRequestAllPermissions &=
                    (windowAndroid.hasPermission(mAndroidPermisisons[i])
                            || windowAndroid.canRequestPermission(mAndroidPermisisons[i]));
        }

        Activity activity = windowAndroid.getActivity().get();
        if (canRequestAllPermissions) {
            windowAndroid.requestPermissions(mAndroidPermisisons, this);
        } else {
            if (activity == null) {
                PermissionUpdateRequesterJni.get().onPermissionResult(mNativePtr, false);
                return;
            }

            mActivityStateListener =
                    new ActivityStateListener() {
                        @Override
                        public void onActivityStateChange(Activity activity, int newState) {
                            if (newState == ActivityState.DESTROYED) {
                                ApplicationStatus.unregisterActivityStateListener(this);
                                mActivityStateListener = null;

                                PermissionUpdateRequesterJni.get()
                                        .onPermissionResult(mNativePtr, false);
                            } else if (newState == ActivityState.RESUMED) {
                                ApplicationStatus.unregisterActivityStateListener(this);
                                mActivityStateListener = null;

                                notifyPermissionResult();
                            }
                        }
                    };
            ApplicationStatus.registerStateListenerForActivity(mActivityStateListener, activity);

            Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            settingsIntent.setData(
                    Uri.parse("package:" + ContextUtils.getApplicationContext().getPackageName()));
            settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            activity.startActivity(settingsIntent);
        }
    }

    @Override
    public void onRequestPermissionsResult(String[] permissions, int[] grantResults) {
        notifyPermissionResult();
    }

    private void notifyPermissionResult() {
        boolean hasAllPermissions = true;
        WindowAndroid windowAndroid = mWebContents.getTopLevelNativeWindow();
        if (windowAndroid == null) {
            hasAllPermissions = false;
        } else {
            for (int i = 0; i < mAndroidPermisisons.length; i++) {
                if (!mRequiredAndroidPermissions.contains(mAndroidPermisisons[i])) {
                    continue;
                }
                hasAllPermissions &= windowAndroid.hasPermission(mAndroidPermisisons[i]);
            }
        }
        if (mNativePtr != 0) {
            PermissionUpdateRequesterJni.get().onPermissionResult(mNativePtr, hasAllPermissions);
        }
    }

    @NativeMethods
    interface Natives {
        void onPermissionResult(
                long nativePermissionUpdateRequester, boolean allPermissionsGranted);
    }
}