Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e977c349 authored by Presubmit Automerger Backend's avatar Presubmit Automerger Backend
Browse files

[automerge] [DO NOT MERGE] Change USB audio device permission warning dialog 2p: cce76716

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16977625

Bug: 199034378
Bug: 222434773
Change-Id: I816c711db8eda1397f39187388589768b46cc079
parents a11bcaea cce76716
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
# USB audio Permission and Confirmation warning dialog resource string id matrix table
### go/support-usb-access-aoc-offload-feature

     |---|------------|----------------|------------------|-----------------|--------------------|
     | # | Permission |isUsbAudioDevice| hasAudioPlayback | hasAudioCapture | string resource ID |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 1 |  TRUE      |  TRUE          |  TRUE            |  FALSE          | usb_audio_device_
                                                                              permission_prompt  |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 2 |  TRUE      |  TRUE          |  FALSE           |  TRUE           | usb_audio_device_
                                                                              permission_prompt  |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 3 |  TRUE      |  TRUE          |  TRUE            |  TRUE           | usb_audio_device_
                                                                              permission_prompt  |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 4 |  TRUE      |  FALSE         |  N/A             |  N/A            | usb_device_
                                                                              permission_prompt  |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 5 |  FALSE     |  TRUE          |  TRUE            |  FALSE          | usb_audio_device_
                                                                              permission_prompt  |
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 6 |  FALSE     |  TRUE          |  FALSE           |  TRUE           | usb_audio_device_
                                                                            permission_prompt_warn
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 7 |  FALSE     |  TRUE          |  TRUE            |  TRUE           | usb_audio_device_
                                                                            permission_prompt_warn
     |---|------------|----------------|------------------|-----------------|--------------------|
     | 8 |  FALSE     |  FALSE         |  N/A             |  N/A            | usb_device_
                                                                             permission_prompt   |
     |---|------------|----------------|------------------|-----------------|--------------------|
+119 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.usb;

import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.annotation.IntDef;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.util.Log;

import com.android.systemui.R;

import java.lang.annotation.Retention;

/**
 * USB Audio devices warning dialog messages help class.
 */
public class UsbAudioWarningDialogMessage {
    private static final String TAG = "UsbAudioWarningDialogMessage";

    @Retention(SOURCE)
    @IntDef({TYPE_PERMISSION, TYPE_CONFIRM})
    public @interface DialogType {}
    public static final int TYPE_PERMISSION = 0;
    public static final int TYPE_CONFIRM = 1;

    private final int mDialogType;
    private UsbDialogHelper mDialogHelper;

    public UsbAudioWarningDialogMessage(Context context, Intent intent, @DialogType int type) {
        mDialogType = type;
        try {
            mDialogHelper = new UsbDialogHelper(context, intent);
        } catch (IllegalStateException e) {
            Log.e(TAG, "Unable to initialize UsbDialogHelper!", e);
        }
    }

    private boolean hasRecordPermission() {
        return mDialogHelper.packageHasAudioRecordingPermission();
    }

    private boolean isUsbAudioDevice() {
        return mDialogHelper.isUsbDevice() && (mDialogHelper.deviceHasAudioCapture()
                || (mDialogHelper.deviceHasAudioPlayback()));
    }

    private boolean hasAudioPlayback() {
        return mDialogHelper.deviceHasAudioPlayback();
    }

    private boolean hasAudioCapture() {
        return mDialogHelper.deviceHasAudioCapture();
    }

    /**
     * According to USB audio warning dialog matrix table to return warning message id.
     * @return string resId for USB audio warning dialog message, otherwise {ID_NULL}.
     * See usb_audio.md for USB audio Permission and Confirmation warning dialog resource
     * string id matrix table.
     */
    public int getMessageId() {
        if (!mDialogHelper.isUsbDevice()) {
            return getUsbAccessoryPromptId();
        }

        if (hasRecordPermission() && isUsbAudioDevice()) {
            // case# 1, 2, 3
            return R.string.usb_audio_device_prompt;
        } else if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioPlayback()
                && !hasAudioCapture()) {
            // case# 5
            return R.string.usb_audio_device_prompt;
        }

        if (!hasRecordPermission() && isUsbAudioDevice() && hasAudioCapture()) {
            // case# 6,7
            return R.string.usb_audio_device_prompt_warn;
        }

        Log.w(TAG, "Only shows title with empty content description!");
        return Resources.ID_NULL;
    }

    /**
     * Gets prompt dialog title.
     * @return string id for USB prompt dialog title.
     */
    public int getPromptTitleId() {
        return (mDialogType == TYPE_PERMISSION)
                ? R.string.usb_audio_device_permission_prompt_title
                : R.string.usb_audio_device_confirm_prompt_title;
    }

    /**
     * Gets USB Accessory prompt message id.
     * @return string id for USB Accessory prompt message.
     */
    public int getUsbAccessoryPromptId() {
        return (mDialogType == TYPE_PERMISSION)
                ? R.string.usb_accessory_permission_prompt : R.string.usb_accessory_confirm_prompt;
    }
}
+12 −10
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
@@ -35,7 +36,6 @@ import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
@@ -57,6 +57,7 @@ public class UsbConfirmActivity extends AlertActivity
    private ResolveInfo mResolveInfo;
    private boolean mPermissionGranted;
    private UsbDisconnectedReceiver mDisconnectedReceiver;
    private UsbAudioWarningDialogMessage mUsbConfirmMessageHandler;

    @Override
    public void onCreate(Bundle icicle) {
@@ -70,16 +71,19 @@ public class UsbConfirmActivity extends AlertActivity
        mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
        mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo");
        String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);

        mUsbConfirmMessageHandler = new UsbAudioWarningDialogMessage(
                getApplicationContext(), getIntent(),
                UsbAudioWarningDialogMessage.TYPE_CONFIRM);
        PackageManager packageManager = getPackageManager();
        String appName = mResolveInfo.loadLabel(packageManager).toString();

        final AlertController.AlertParams ap = mAlertParams;
        ap.mTitle = appName;
        final int titleId = mUsbConfirmMessageHandler.getPromptTitleId();
        ap.mTitle = getString(titleId, appName, mDevice.getProductName());
        boolean useRecordWarning = false;
        if (mDevice == null) {
            ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
                    mAccessory.getDescription());
            final int messageId = mUsbConfirmMessageHandler.getUsbAccessoryPromptId();
            ap.mMessage = getString(messageId, appName, mAccessory.getDescription());
            mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
        } else {
            int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
@@ -91,11 +95,9 @@ public class UsbConfirmActivity extends AlertActivity
            boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
            useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;

            int strID = useRecordWarning
                    ? R.string.usb_device_confirm_prompt_warn
                    : R.string.usb_device_confirm_prompt;

            ap.mMessage = getString(strID, appName, mDevice.getProductName());
            final int messageId = mUsbConfirmMessageHandler.getMessageId();
            ap.mMessage = (messageId != Resources.ID_NULL) ? getString(messageId, appName,
                    mDevice.getProductName()) : null;
            mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
        }
        ap.mPositiveButtonText = getString(android.R.string.ok);
+303 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.usb;

import static android.Manifest.permission.RECORD_AUDIO;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;

/**
 * Helper class to separate model and view for USB permission and confirm dialogs.
 */
public class UsbDialogHelper {
    private static final String TAG = UsbDialogHelper.class.getSimpleName();
    private static final String EXTRA_RESOLVE_INFO = "rinfo";

    private final UsbDevice mDevice;
    private final UsbAccessory mAccessory;
    private final ResolveInfo mResolveInfo;
    private final String mPackageName;
    private final CharSequence mAppName;
    private final Context mContext;
    private final PendingIntent mPendingIntent;
    private final IUsbManager mUsbService;
    private final int mUid;
    private final boolean mCanBeDefault;

    private UsbDisconnectedReceiver mDisconnectedReceiver;
    private boolean mIsUsbDevice;
    private boolean mResponseSent;

    /**
     * @param context The Context of the caller.
     * @param intent The intent of the caller.
     * @throws IllegalStateException Thrown if both UsbDevice and UsbAccessory are null or if the
     *                               query for the matching ApplicationInfo is unsuccessful.
     */
    public UsbDialogHelper(Context context, Intent intent) throws IllegalStateException {
        mContext = context;
        mDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
        mAccessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
        mCanBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false);
        if (mDevice == null && mAccessory == null) {
            throw new IllegalStateException("Device and accessory are both null.");
        }
        if (mDevice != null) {
            mIsUsbDevice = true;
        }
        mResolveInfo = intent.getParcelableExtra(EXTRA_RESOLVE_INFO);
        PackageManager packageManager = mContext.getPackageManager();
        if (mResolveInfo != null) {
            // If a ResolveInfo is provided it will be used to determine the activity to start
            mUid = mResolveInfo.activityInfo.applicationInfo.uid;
            mPackageName = mResolveInfo.activityInfo.packageName;
            mPendingIntent = null;
        } else {
            mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
            mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
            mPendingIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
        }
        try {
            ApplicationInfo aInfo = packageManager.getApplicationInfo(mPackageName, 0);
            mAppName = aInfo.loadLabel(packageManager);
        } catch (PackageManager.NameNotFoundException e) {
            throw new IllegalStateException("unable to look up package name", e);
        }
        IBinder b = ServiceManager.getService(Context.USB_SERVICE);
        mUsbService = IUsbManager.Stub.asInterface(b);
    }

    /**
     * Registers UsbDisconnectedReceiver to dismiss dialog automatically when device or accessory
     * gets disconnected
     * @param activity The activity to finish when device / accessory gets disconnected.
     */
    public void registerUsbDisconnectedReceiver(Activity activity) {
        if (mIsUsbDevice) {
            mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mDevice);
        } else {
            mDisconnectedReceiver = new UsbDisconnectedReceiver(activity, mAccessory);
        }
    }

    /**
     * Unregisters the UsbDisconnectedReceiver. To be called when the activity is destroyed.
     * @param activity The activity registered to finish when device / accessory gets disconnected.
     */
    public void unregisterUsbDisconnectedReceiver(Activity activity) {
        if (mDisconnectedReceiver != null) {
            try {
                activity.unregisterReceiver(mDisconnectedReceiver);
            } catch (Exception e) {
                // pass
            }
            mDisconnectedReceiver = null;
        }
    }

    /**
     * @return True if the intent contains a UsbDevice which can capture audio.
     */
    public boolean deviceHasAudioCapture() {
        return mDevice != null && mDevice.getHasAudioCapture();
    }

    /**
     * @return True if the intent contains a UsbDevice which can play audio.
     */
    public boolean deviceHasAudioPlayback() {
        return mDevice != null && mDevice.getHasAudioPlayback();
    }

    /**
     * @return True if the package has RECORD_AUDIO permission specified in its manifest.
     */
    public boolean packageHasAudioRecordingPermission() {
        return PermissionChecker.checkPermissionForPreflight(mContext, RECORD_AUDIO,
                PermissionChecker.PID_UNKNOWN, mUid, mPackageName)
                == android.content.pm.PackageManager.PERMISSION_GRANTED;
    }

    /**
     * @return True if the intent contains a UsbDevice.
     */
    public boolean isUsbDevice() {
        return mIsUsbDevice;
    }

    /**
     * @return True if the intent contains a UsbAccessory.
     */
    public boolean isUsbAccessory() {
        return !mIsUsbDevice;
    }

    /**
     * Grants USB permission to the device / accessory to the calling uid.
     */
    public void grantUidAccessPermission() {
        try {
            if (mIsUsbDevice) {
                mUsbService.grantDevicePermission(mDevice, mUid);
            } else {
                mUsbService.grantAccessoryPermission(mAccessory, mUid);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "IUsbService connection failed", e);
        }
    }

    /**
     * Sets the package as default for the device / accessory.
     */
    public void setDefaultPackage() {
        final int userId = UserHandle.myUserId();
        try {
            if (mIsUsbDevice) {
                mUsbService.setDevicePackage(mDevice, mPackageName, userId);
            } else {
                mUsbService.setAccessoryPackage(mAccessory, mPackageName, userId);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "IUsbService connection failed", e);
        }
    }

    /**
     * Clears the default package of the device / accessory.
     */
    public void clearDefaultPackage() {
        final int userId = UserHandle.myUserId();
        try {
            if (mIsUsbDevice) {
                mUsbService.setDevicePackage(mDevice, null, userId);
            } else {
                mUsbService.setAccessoryPackage(mAccessory, null, userId);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "IUsbService connection failed", e);
        }
    }

    /**
     * Starts the activity which was selected to handle the device / accessory.
     */
    public void confirmDialogStartActivity() {
        final int userId = UserHandle.myUserId();
        Intent intent;

        if (mIsUsbDevice) {
            intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
            intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
        } else {
            intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
            intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setComponent(
                new ComponentName(mResolveInfo.activityInfo.packageName,
                        mResolveInfo.activityInfo.name));
        try {
            mContext.startActivityAsUser(intent, new UserHandle(userId));
        } catch (Exception e) {
            Log.e(TAG, "Unable to start activity", e);
        }
    }

    /**
     * Sends the result of the permission dialog via the provided PendingIntent.
     *
     * @param permissionGranted True if the user pressed ok in the permission dialog.
     */
    public void sendPermissionDialogResponse(boolean permissionGranted) {
        if (!mResponseSent) {
            // send response via pending intent
            Intent intent = new Intent();
            if (mIsUsbDevice) {
                intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
            } else {
                intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
            }
            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, permissionGranted);
            try {
                mPendingIntent.send(mContext, 0, intent);
                mResponseSent = true;
            } catch (PendingIntent.CanceledException e) {
                Log.w(TAG, "PendingIntent was cancelled");
            }
        }
    }

    /**
     * @return A description of the device / accessory
     */
    public String getDeviceDescription() {
        String desc;
        if (mIsUsbDevice) {
            desc = mDevice.getProductName();
            if (desc == null) {
                desc = mDevice.getDeviceName();
            }
        } else {
            // UsbAccessory
            desc = mAccessory.getDescription();
            if (desc == null) {
                desc = String.format("%s %s", mAccessory.getManufacturer(), mAccessory.getModel());
            }
        }
        return desc;
    }

    /**
     * Whether the calling package can set as default handler of the USB device or accessory.
     * In case of a UsbAccessory this is the case if the calling package has an intent filter for
     * {@link UsbManager#ACTION_USB_ACCESSORY_ATTACHED} with a usb-accessory filter matching the
     * attached accessory. In case of a UsbDevice this is the case if the calling package has an
     * intent filter for {@link UsbManager#ACTION_USB_DEVICE_ATTACHED} with a usb-device filter
     * matching the attached device.
     *
     * @return True if the package can be default for the USB device.
     */
    public boolean canBeDefault() {
        return mCanBeDefault;
    }

    /**
     * @return The name of the app which requested permission or the name of the app which will be
     * opened if the user allows it to handle the USB device.
     */
    public CharSequence getAppName() {
        return mAppName;
    }
}
+12 −8
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
@@ -59,6 +60,7 @@ public class UsbPermissionActivity extends AlertActivity
    private int mUid;
    private boolean mPermissionGranted;
    private UsbDisconnectedReceiver mDisconnectedReceiver;
    private UsbAudioWarningDialogMessage mUsbAudioPermissionMessageHandler;

    @Override
    public void onCreate(Bundle icicle) {
@@ -73,7 +75,9 @@ public class UsbPermissionActivity extends AlertActivity
        mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
        mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
        boolean canBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false);

        mUsbAudioPermissionMessageHandler = new UsbAudioWarningDialogMessage(
                getApplicationContext(), getIntent(),
                UsbAudioWarningDialogMessage.TYPE_PERMISSION);
        PackageManager packageManager = getPackageManager();
        ApplicationInfo aInfo;
        try {
@@ -86,13 +90,14 @@ public class UsbPermissionActivity extends AlertActivity
        String appName = aInfo.loadLabel(packageManager).toString();

        final AlertController.AlertParams ap = mAlertParams;
        ap.mTitle = appName;
        final int titleId = mUsbAudioPermissionMessageHandler.getPromptTitleId();
        ap.mTitle = getString(titleId, appName, mDevice.getProductName());
        boolean useRecordWarning = false;
        if (mDevice == null) {
            // Accessory Case

            ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
                    mAccessory.getDescription());
            final int messageId = mUsbAudioPermissionMessageHandler.getUsbAccessoryPromptId();
            ap.mMessage = getString(messageId, appName, mAccessory.getDescription());
            mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
        } else {
            boolean hasRecordPermission =
@@ -103,10 +108,9 @@ public class UsbPermissionActivity extends AlertActivity
            boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
            useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;

            int strID = useRecordWarning
                    ? R.string.usb_device_permission_prompt_warn
                    : R.string.usb_device_permission_prompt;
            ap.mMessage = getString(strID, appName, mDevice.getProductName());
            final int messageId =  mUsbAudioPermissionMessageHandler.getMessageId();
            ap.mMessage = (messageId != Resources.ID_NULL) ? getString(messageId, appName,
                    mDevice.getProductName()) : null;
            mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);

        }