Refactor USB permission and confirm dialogs.

Avoid null description in dialog by falling back to
UsbDevice#getDeviceName() and UsbAccessory#getManufacturer()
and UsbAccessory#getModel(). Use config_usbConfirmActivity
value instead of UsbConfirmActivity.class in UsbResolverActivity.

Bug: 196780010
Test: Flashed pixel device with custom build containing the change
Test: Open camera app that requests access to USB camera. Verify that TvUsbPermissionActivity is shown and verify functionality.
Test: Have an app installed which registers for USB_DEVICE_ATTACHED broadcast in its AndroidManifest. Attach USB camera device and verify that TvUsbConfirmActivity is shown and verify functionality.
Test: atest UsbPermissionActivityTest
Change-Id: I89c4176ccd5852f5b11955d65cbf05caf5260946
This commit is contained in:
Philip Junker
2021-09-15 17:21:22 +02:00
parent b67178d6c7
commit 1ecc039415
4 changed files with 185 additions and 322 deletions

View File

@@ -16,180 +16,46 @@
package com.android.systemui.usb;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.PermissionChecker;
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.Bundle;
import android.os.IBinder;
import android.os.ServiceManager;
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;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
public class UsbConfirmActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
private static final String TAG = "UsbConfirmActivity";
private CheckBox mAlwaysUse;
private TextView mClearDefaultHint;
private UsbDevice mDevice;
private UsbAccessory mAccessory;
private ResolveInfo mResolveInfo;
private boolean mPermissionGranted;
private UsbDisconnectedReceiver mDisconnectedReceiver;
/**
* Dialog shown to confirm the package to start when a USB device or accessory is attached and there
* is only one package that claims to handle this USB device or accessory.
*/
public class UsbConfirmActivity extends UsbDialogActivity {
@Override
public void onCreate(Bundle icicle) {
getWindow().addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(icicle);
Intent intent = getIntent();
mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo");
String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
PackageManager packageManager = getPackageManager();
String appName = mResolveInfo.loadLabel(packageManager).toString();
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = appName;
protected void onResume() {
super.onResume();
final int strId;
boolean useRecordWarning = false;
if (mDevice == null) {
ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
boolean hasRecordPermission =
PermissionChecker.checkPermissionForPreflight(
this, android.Manifest.permission.RECORD_AUDIO, -1, uid,
packageName)
== android.content.pm.PackageManager.PERMISSION_GRANTED;
boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;
int strID = useRecordWarning
if (mDialogHelper.isUsbDevice()) {
useRecordWarning = mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission();
strId = useRecordWarning
? R.string.usb_device_confirm_prompt_warn
: R.string.usb_device_confirm_prompt;
ap.mMessage = getString(strID, appName, mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
} else {
// UsbAccessory case
strId = R.string.usb_accessory_confirm_prompt;
}
ap.mPositiveButtonText = getString(android.R.string.ok);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
// add "always use" checkbox
setAlertParams(strId);
// Only show the "always use" checkbox if there is no USB/Record warning
if (!useRecordWarning) {
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
mAccessory.getDescription()));
} else {
mAlwaysUse.setText(getString(R.string.always_use_device, appName,
mDevice.getProductName()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView) ap.mView.findViewById(
com.android.internal.R.id.clearDefaultHint);
mClearDefaultHint.setVisibility(View.GONE);
addAlwaysUseCheckbox();
}
setupAlert();
}
@Override
protected void onDestroy() {
if (mDisconnectedReceiver != null) {
unregisterReceiver(mDisconnectedReceiver);
}
super.onDestroy();
}
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
try {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
final int uid = mResolveInfo.activityInfo.applicationInfo.uid;
final int userId = UserHandle.myUserId();
boolean alwaysUse = mAlwaysUse != null ? mAlwaysUse.isChecked() : false;
Intent intent = null;
if (mDevice != null) {
intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
// grant permission for the device
service.grantDevicePermission(mDevice, uid);
// set or clear default setting
if (alwaysUse) {
service.setDevicePackage(
mDevice, mResolveInfo.activityInfo.packageName, userId);
} else {
service.setDevicePackage(mDevice, null, userId);
}
} else if (mAccessory != null) {
intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
// grant permission for the accessory
service.grantAccessoryPermission(mAccessory, uid);
// set or clear default setting
if (alwaysUse) {
service.setAccessoryPackage(
mAccessory, mResolveInfo.activityInfo.packageName, userId);
} else {
service.setAccessoryPackage(mAccessory, null, userId);
}
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setComponent(
new ComponentName(mResolveInfo.activityInfo.packageName,
mResolveInfo.activityInfo.name));
startActivityAsUser(intent, new UserHandle(userId));
} catch (Exception e) {
Log.e(TAG, "Unable to start activity", e);
}
void onConfirm() {
mDialogHelper.grantUidAccessPermission();
if (isAlwaysUseChecked()) {
mDialogHelper.setDefaultPackage();
} else {
mDialogHelper.clearDefaultPackage();
}
mDialogHelper.confirmDialogStartActivity();
finish();
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClearDefaultHint == null) return;
if(isChecked) {
mClearDefaultHint.setVisibility(View.VISIBLE);
} else {
mClearDefaultHint.setVisibility(View.GONE);
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2021 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 android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
abstract class UsbDialogActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
private static final String TAG = UsbDialogActivity.class.getSimpleName();
UsbDialogHelper mDialogHelper;
private CheckBox mAlwaysUse;
private TextView mClearDefaultHint;
@Override
protected final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
try {
mDialogHelper = new UsbDialogHelper(getApplicationContext(), getIntent());
} catch (IllegalStateException e) {
Log.e(TAG, "unable to initialize", e);
finish();
}
}
@Override
protected void onResume() {
super.onResume();
mDialogHelper.registerUsbDisconnectedReceiver(this);
}
@Override
protected void onPause() {
if (mDialogHelper != null) {
mDialogHelper.unregisterUsbDisconnectedReceiver(this);
}
super.onPause();
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
onConfirm();
} else {
finish();
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClearDefaultHint == null) return;
if (isChecked) {
mClearDefaultHint.setVisibility(View.VISIBLE);
} else {
mClearDefaultHint.setVisibility(View.GONE);
}
}
void setAlertParams(int strId) {
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = mDialogHelper.getAppName();
ap.mMessage = getString(strId, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription());
ap.mPositiveButtonText = getString(android.R.string.ok);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
}
void addAlwaysUseCheckbox() {
final AlertController.AlertParams ap = mAlertParams;
LayoutInflater inflater = getSystemService(LayoutInflater.class);
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDialogHelper.isUsbAccessory()) {
mAlwaysUse.setText(getString(R.string.always_use_accessory, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription()));
} else {
// UsbDevice case
mAlwaysUse.setText(getString(R.string.always_use_device, mDialogHelper.getAppName(),
mDialogHelper.getDeviceDescription()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = ap.mView.findViewById(com.android.internal.R.id.clearDefaultHint);
mClearDefaultHint.setVisibility(View.GONE);
}
boolean isAlwaysUseChecked() {
return mAlwaysUse != null && mAlwaysUse.isChecked();
}
/**
* Called when the dialog is confirmed.
*/
abstract void onConfirm();
}

View File

@@ -16,186 +16,53 @@
package com.android.systemui.usb;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.PermissionChecker;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
public class UsbPermissionActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
/**
* Dialog shown when a package requests access to a USB device or accessory.
*/
public class UsbPermissionActivity extends UsbDialogActivity {
private static final String TAG = "UsbPermissionActivity";
private CheckBox mAlwaysUse;
private TextView mClearDefaultHint;
private UsbDevice mDevice;
private UsbAccessory mAccessory;
private PendingIntent mPendingIntent;
private String mPackageName;
private int mUid;
private boolean mPermissionGranted;
private UsbDisconnectedReceiver mDisconnectedReceiver;
private boolean mPermissionGranted = false;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
getWindow().addPrivateFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent intent = getIntent();
mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
mPendingIntent = (PendingIntent)intent.getParcelableExtra(Intent.EXTRA_INTENT);
mUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
mPackageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
boolean canBeDefault = intent.getBooleanExtra(UsbManager.EXTRA_CAN_BE_DEFAULT, false);
PackageManager packageManager = getPackageManager();
ApplicationInfo aInfo;
try {
aInfo = packageManager.getApplicationInfo(mPackageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "unable to look up package name", e);
finish();
return;
}
String appName = aInfo.loadLabel(packageManager).toString();
final AlertController.AlertParams ap = mAlertParams;
ap.mTitle = appName;
protected void onResume() {
super.onResume();
final int strId;
boolean useRecordWarning = false;
if (mDevice == null) {
// Accessory Case
ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
boolean hasRecordPermission =
PermissionChecker.checkPermissionForPreflight(
this, android.Manifest.permission.RECORD_AUDIO, -1, aInfo.uid,
mPackageName)
== android.content.pm.PackageManager.PERMISSION_GRANTED;
boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;
int strID = useRecordWarning
if (mDialogHelper.isUsbDevice()) {
useRecordWarning = mDialogHelper.deviceHasAudioCapture()
&& !mDialogHelper.packageHasAudioRecordingPermission();
strId = useRecordWarning
? R.string.usb_device_permission_prompt_warn
: R.string.usb_device_permission_prompt;
ap.mMessage = getString(strID, appName, mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
} else {
// UsbAccessory case
strId = R.string.usb_accessory_permission_prompt;
}
ap.mPositiveButtonText = getString(android.R.string.ok);
ap.mNegativeButtonText = getString(android.R.string.cancel);
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
// Don't show the "always use" checkbox if the USB/Record warning is in effect
if (!useRecordWarning && canBeDefault && (mDevice != null || mAccessory != null)) {
// add "open when" checkbox
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
mAccessory.getDescription()));
} else {
mAlwaysUse.setText(getString(R.string.always_use_device, appName,
mDevice.getProductName()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
com.android.internal.R.id.clearDefaultHint);
mClearDefaultHint.setVisibility(View.GONE);
setAlertParams(strId);
// Only show the "always use" checkbox if there is no USB/Record warning
if (!useRecordWarning && mDialogHelper.canBeDefault()) {
addAlwaysUseCheckbox();
}
setupAlert();
}
@Override
public void onDestroy() {
IBinder b = ServiceManager.getService(USB_SERVICE);
IUsbManager service = IUsbManager.Stub.asInterface(b);
// send response via pending intent
Intent intent = new Intent();
try {
if (mDevice != null) {
intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
if (mPermissionGranted) {
service.grantDevicePermission(mDevice, mUid);
if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setDevicePackage(mDevice, mPackageName, userId);
}
}
}
if (mAccessory != null) {
intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
if (mPermissionGranted) {
service.grantAccessoryPermission(mAccessory, mUid);
if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
final int userId = UserHandle.getUserId(mUid);
service.setAccessoryPackage(mAccessory, mPackageName, userId);
}
}
}
intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, mPermissionGranted);
mPendingIntent.send(this, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "PendingIntent was cancelled");
} catch (RemoteException e) {
Log.e(TAG, "IUsbService connection failed", e);
protected void onPause() {
if (isFinishing()) {
mDialogHelper.sendPermissionDialogResponse(mPermissionGranted);
}
if (mDisconnectedReceiver != null) {
unregisterReceiver(mDisconnectedReceiver);
}
super.onDestroy();
super.onPause();
}
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
mPermissionGranted = true;
@Override
void onConfirm() {
mDialogHelper.grantUidAccessPermission();
if (isAlwaysUseChecked()) {
mDialogHelper.setDefaultPackage();
}
mPermissionGranted = true;
finish();
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (mClearDefaultHint == null) return;
if(isChecked) {
mClearDefaultHint.setVisibility(View.VISIBLE);
} else {
mClearDefaultHint.setVisibility(View.GONE);
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.systemui.usb;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
@@ -109,7 +110,9 @@ public class UsbResolverActivity extends ResolverActivity {
mOtherProfileIntent.putParcelableArrayListExtra(EXTRA_RESOLVE_INFOS,
rListOtherProfile);
} else {
mOtherProfileIntent = new Intent(this, UsbConfirmActivity.class);
mOtherProfileIntent.setComponent(ComponentName.unflattenFromString(
this.getResources().getString(
com.android.internal.R.string.config_usbConfirmActivity)));
mOtherProfileIntent.putExtra(EXTRA_RESOLVE_INFO, rListOtherProfile.get(0));
if (mDevice != null) {