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

Commit 1ecc0394 authored by Philip Junker's avatar Philip Junker
Browse files

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
parent b67178d6
Loading
Loading
Loading
Loading
+25 −159
Original line number Diff line number Diff line
@@ -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);
        }
        ap.mPositiveButtonText = getString(android.R.string.ok);
        ap.mNegativeButtonText = getString(android.R.string.cancel);
        ap.mPositiveButtonListener = this;
        ap.mNegativeButtonListener = this;

        // add "always use" checkbox
        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()));
            // UsbAccessory case
            strId = R.string.usb_accessory_confirm_prompt;
        }
            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) {
            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);
    void onConfirm() {
        mDialogHelper.grantUidAccessPermission();
        if (isAlwaysUseChecked()) {
            mDialogHelper.setDefaultPackage();
        } 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);
            }
            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);
        }
    }
}
+127 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+29 −162
Original line number Diff line number Diff line
@@ -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 {

    private static final String TAG = "UsbPermissionActivity";
/**
 * Dialog shown when a package requests access to a USB device or accessory.
 */
public class UsbPermissionActivity extends UsbDialogActivity {

    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);

        }

        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()));
            // UsbAccessory case
            strId = R.string.usb_accessory_permission_prompt;
        }
            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);
                    }
                }
    protected void onPause() {
        if (isFinishing()) {
            mDialogHelper.sendPermissionDialogResponse(mPermissionGranted);
        }
            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);
        super.onPause();
    }

        if (mDisconnectedReceiver != null) {
            unregisterReceiver(mDisconnectedReceiver);
        }
        super.onDestroy();
    @Override
    void onConfirm() {
        mDialogHelper.grantUidAccessPermission();
        if (isAlwaysUseChecked()) {
            mDialogHelper.setDefaultPackage();
        }

    public void onClick(DialogInterface dialog, int which) {
        if (which == AlertDialog.BUTTON_POSITIVE) {
        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);
        }
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -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) {