Migrate USB audio device permission warning dialog

for both Direct USB Access and AoC Offload feature

According to USB audio warning dialog message conditions matrix, add
a UsbAudioWarningDialogMessage to handle the conditions.

Test: manually install NeutronPlayer to test usb audio device
permission warning dialog sentence.
Test: manually modify each audio api condition to test each sentence
correntness.
Test: atest SystemUITests

Change-Id: Ic9731590cb3bad259add1b93b43341af9518e107
This commit is contained in:
Jason Chang
2022-03-30 20:25:08 +08:00
parent 13c6ef8e69
commit b30cec4800
8 changed files with 267 additions and 44 deletions

View File

@@ -0,0 +1,30 @@
# 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 |
|---|------------|----------------|------------------|-----------------|--------------------|

View File

@@ -29,8 +29,10 @@ import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
import com.android.systemui.tuner.TunerActivity;
import com.android.systemui.usb.UsbConfirmActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
import com.android.systemui.usb.UsbPermissionActivity;
import com.android.systemui.user.CreateUserActivity;
import dagger.Binds;
@@ -80,6 +82,18 @@ public abstract class DefaultActivityBinder {
public abstract Activity bindUsbDebuggingSecondaryUserActivity(
UsbDebuggingSecondaryUserActivity activity);
/** Inject into UsbPermissionActivity. */
@Binds
@IntoMap
@ClassKey(UsbPermissionActivity.class)
public abstract Activity bindUsbPermissionActivity(UsbPermissionActivity activity);
/** Inject into UsbConfirmActivity. */
@Binds
@IntoMap
@ClassKey(UsbConfirmActivity.class)
public abstract Activity bindUsbConfirmActivity(UsbConfirmActivity activity);
/** Inject into CreateUserActivity. */
@Binds
@IntoMap

View File

@@ -0,0 +1,124 @@
/*
* 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.res.Resources;
import android.util.Log;
import com.android.systemui.R;
import java.lang.annotation.Retention;
import javax.inject.Inject;
/**
* 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 int mDialogType;
private UsbDialogHelper mDialogHelper;
@Inject
public UsbAudioWarningDialogMessage() {
}
/**
* Initialize USB audio warning dialog message type and helper class.
* @param type Dialog type for Activity.
* @param usbDialogHelper Helper class for getting USB permission and confirm dialogs
*/
public void init(@DialogType int type, UsbDialogHelper usbDialogHelper) {
mDialogType = type;
mDialogHelper = usbDialogHelper;
}
boolean hasRecordPermission() {
return mDialogHelper.packageHasAudioRecordingPermission();
}
boolean isUsbAudioDevice() {
return mDialogHelper.isUsbDevice() && (mDialogHelper.deviceHasAudioCapture()
|| (mDialogHelper.deviceHasAudioPlayback()));
}
boolean hasAudioPlayback() {
return mDialogHelper.deviceHasAudioPlayback();
}
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;
}
}

View File

@@ -16,7 +16,10 @@
package com.android.systemui.usb;
import com.android.systemui.R;
import android.content.res.Resources;
import android.os.Bundle;
import javax.inject.Inject;
/**
* Dialog shown to confirm the package to start when a USB device or accessory is attached and there
@@ -24,23 +27,35 @@ import com.android.systemui.R;
*/
public class UsbConfirmActivity extends UsbDialogActivity {
private UsbAudioWarningDialogMessage mUsbConfirmMessageHandler;
@Inject
public UsbConfirmActivity(UsbAudioWarningDialogMessage usbAudioWarningDialogMessage) {
mUsbConfirmMessageHandler = usbAudioWarningDialogMessage;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mUsbConfirmMessageHandler.init(UsbAudioWarningDialogMessage.TYPE_CONFIRM, mDialogHelper);
}
@Override
protected void onResume() {
super.onResume();
final int strId;
boolean useRecordWarning = false;
if (mDialogHelper.isUsbDevice()) {
useRecordWarning = mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission();
strId = useRecordWarning
? R.string.usb_device_confirm_prompt_warn
: R.string.usb_device_confirm_prompt;
} else {
// UsbAccessory case
strId = R.string.usb_accessory_confirm_prompt;
}
setAlertParams(strId);
// Only show the "always use" checkbox if there is no USB/Record warning
final boolean useRecordWarning = mDialogHelper.isUsbDevice()
&& (mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission());
final int titleId = mUsbConfirmMessageHandler.getPromptTitleId();
final String title = getString(titleId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription());
final int messageId = mUsbConfirmMessageHandler.getMessageId();
String message = (messageId != Resources.ID_NULL)
? getString(messageId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription()) : null;
setAlertParams(title, message);
if (!useRecordWarning) {
addAlwaysUseCheckbox();
}

View File

@@ -41,7 +41,7 @@ abstract class UsbDialogActivity extends AlertActivity
private TextView mClearDefaultHint;
@Override
protected final void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
@@ -87,11 +87,10 @@ abstract class UsbDialogActivity extends AlertActivity
}
}
void setAlertParams(int strId) {
void setAlertParams(String title, String message) {
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = mDialogHelper.getAppName();
ap.mMessage = getString(strId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription());
ap.mTitle = title;
ap.mMessage = message;
ap.mPositiveButtonText = getString(android.R.string.ok);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;

View File

@@ -133,6 +133,13 @@ public class UsbDialogHelper {
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.
*/
@@ -272,15 +279,15 @@ public class UsbDialogHelper {
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.
/**
* 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;

View File

@@ -16,7 +16,10 @@
package com.android.systemui.usb;
import com.android.systemui.R;
import android.content.res.Resources;
import android.os.Bundle;
import javax.inject.Inject;
/**
* Dialog shown when a package requests access to a USB device or accessory.
@@ -24,23 +27,36 @@ import com.android.systemui.R;
public class UsbPermissionActivity extends UsbDialogActivity {
private boolean mPermissionGranted = false;
private UsbAudioWarningDialogMessage mUsbPermissionMessageHandler;
@Inject
public UsbPermissionActivity(UsbAudioWarningDialogMessage usbAudioWarningDialogMessage) {
mUsbPermissionMessageHandler = usbAudioWarningDialogMessage;
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mUsbPermissionMessageHandler.init(UsbAudioWarningDialogMessage.TYPE_PERMISSION,
mDialogHelper);
}
@Override
protected void onResume() {
super.onResume();
final int strId;
boolean useRecordWarning = false;
if (mDialogHelper.isUsbDevice()) {
useRecordWarning = mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission();
strId = useRecordWarning
? R.string.usb_device_permission_prompt_warn
: R.string.usb_device_permission_prompt;
} else {
// UsbAccessory case
strId = R.string.usb_accessory_permission_prompt;
}
setAlertParams(strId);
final boolean useRecordWarning = mDialogHelper.isUsbDevice()
&& (mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission());
final int titleId = mUsbPermissionMessageHandler.getPromptTitleId();
final String title = getString(titleId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription());
final int messageId = mUsbPermissionMessageHandler.getMessageId();
String message = (messageId != Resources.ID_NULL)
? getString(messageId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription()) : null;
setAlertParams(title, message);
// Only show the "always use" checkbox if there is no USB/Record warning
if (!useRecordWarning && mDialogHelper.canBeDefault()) {
addAlwaysUseCheckbox();

View File

@@ -21,11 +21,16 @@ import android.hardware.usb.IUsbSerialReader
import android.hardware.usb.UsbAccessory
import android.hardware.usb.UsbManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
import androidx.test.filters.SmallTest
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.intercepting.SingleActivityFactory
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import javax.inject.Inject
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -37,14 +42,26 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper
class UsbPermissionActivityTest : SysuiTestCase() {
class UsbPermissionActivityTestable : UsbPermissionActivity()
private var mMessage: UsbAudioWarningDialogMessage = UsbAudioWarningDialogMessage()
open class UsbPermissionActivityTestable @Inject constructor (
val message: UsbAudioWarningDialogMessage
)
: UsbPermissionActivity(UsbAudioWarningDialogMessage())
@Rule
@JvmField
var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
UsbPermissionActivityTestable::class.java, false, false)
object : SingleActivityFactory<UsbPermissionActivityTestable>(
UsbPermissionActivityTestable::class.java
) {
override fun create(intent: Intent?): UsbPermissionActivityTestable {
return UsbPermissionActivityTestable(mMessage)
}
}, false, false)
private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
.apply {
@@ -72,6 +89,7 @@ class UsbPermissionActivityTest : SysuiTestCase() {
@Before
fun setUp() {
UsbPermissionActivityTestable(mMessage)
activityRule.launchActivity(activityIntent)
}