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

Commit 7c509952 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Fix foreground Service background launch restriction" into sc-dev

parents 6cd4ad46 3eb374ad
Loading
Loading
Loading
Loading
+32 −31
Original line number Diff line number Diff line
@@ -37,9 +37,9 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
        if (action == null || !action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            return;
        }

        PowerManager powerManager = context.getSystemService(PowerManager.class);
    BluetoothDevice device =
        intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int pairingVariant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                BluetoothDevice.ERROR);
        String deviceAddress = device != null ? device.getAddress() : null;
@@ -62,6 +62,7 @@ public final class BluetoothPairingRequest extends BroadcastReceiver {
        } else {
            // Put up a notification that leads to the dialog
            intent.setClass(context, BluetoothPairingService.class);
            intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
            context.startServiceAsUser(intent, UserHandle.CURRENT);
        }
    }
+79 −43
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;

import com.android.settings.R;

/**
@@ -39,10 +42,14 @@ import com.android.settings.R;
 */
public final class BluetoothPairingService extends Service {

    private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;

    private static final String ACTION_DISMISS_PAIRING =
    @VisibleForTesting
    static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
    @VisibleForTesting
    static final String ACTION_DISMISS_PAIRING =
            "com.android.settings.bluetooth.ACTION_DISMISS_PAIRING";
    @VisibleForTesting
    static final String ACTION_PAIRING_DIALOG =
            "com.android.settings.bluetooth.ACTION_PAIRING_DIALOG";

    private static final String BLUETOOTH_NOTIFICATION_CHANNEL =
            "bluetooth_notification_channel";
@@ -51,6 +58,9 @@ public final class BluetoothPairingService extends Service {

    private BluetoothDevice mDevice;

    @VisibleForTesting
    NotificationManager mNm;

    public static Intent getPairingDialogIntent(Context context, Intent intent, int initiator) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
@@ -80,33 +90,35 @@ public final class BluetoothPairingService extends Service {
            if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                        BluetoothDevice.ERROR);
                Log.d(TAG, "onReceive() Bond state change : " + bondState + ", device name : "
                        + mDevice.getName());
                if ((bondState != BluetoothDevice.BOND_NONE) && (bondState != BluetoothDevice.BOND_BONDED)) {
                    return;
                }
            } else if (action.equals(ACTION_DISMISS_PAIRING)) {
                Log.d(TAG, "Notification cancel " + mDevice.getAddress() + " (" +
                Log.d(TAG, "Notification cancel " + " (" +
                        mDevice.getName() + ")");
                mDevice.cancelPairing();
            } else {
                int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
                        BluetoothDevice.ERROR);
                Log.d(TAG, "Dismiss pairing for " + mDevice.getAddress() + " (" +
                Log.d(TAG, "Dismiss pairing for " + " (" +
                        mDevice.getName() + "), BondState: " + bondState);
            }
            stopForeground(true);

            mNm.cancel(NOTIFICATION_ID);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
      NotificationManager mgr = (NotificationManager)this
         .getSystemService(Context.NOTIFICATION_SERVICE);
        mNm = getSystemService(NotificationManager.class);
        NotificationChannel notificationChannel = new NotificationChannel(
                BLUETOOTH_NOTIFICATION_CHANNEL,
                this.getString(R.string.bluetooth),
                NotificationManager.IMPORTANCE_HIGH);
      mgr.createNotificationChannel(notificationChannel);
        mNm.createNotificationChannel(notificationChannel);
    }

    @Override
@@ -116,23 +128,8 @@ public final class BluetoothPairingService extends Service {
            stopSelf();
            return START_NOT_STICKY;
        }

        Resources res = getResources();
        Notification.Builder builder = new Notification.Builder(this,
            BLUETOOTH_NOTIFICATION_CHANNEL)
                .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                .setTicker(res.getString(R.string.bluetooth_notif_ticker))
                .setLocalOnly(true);

        PendingIntent pairIntent = PendingIntent.getActivity(this, 0,
                getPairingDialogIntent(this, intent,
                        BluetoothDevice.EXTRA_PAIRING_INITIATOR_BACKGROUND),
                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
                        | PendingIntent.FLAG_IMMUTABLE);

        PendingIntent dismissIntent = PendingIntent.getBroadcast(this, 0,
                new Intent(ACTION_DISMISS_PAIRING), PendingIntent.FLAG_ONE_SHOT
                        | PendingIntent.FLAG_IMMUTABLE);
        String action = intent.getAction();
        Log.d(TAG, "onStartCommand() action : " + action);

        mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

@@ -142,36 +139,76 @@ public final class BluetoothPairingService extends Service {
            return START_NOT_STICKY;
        }

        if (TextUtils.equals(action, BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            createPairingNotification(intent);
        } else if (TextUtils.equals(action, ACTION_DISMISS_PAIRING)) {
            Log.d(TAG, "Notification cancel " + " (" + mDevice.getName() + ")");
            mDevice.cancelPairing();
            mNm.cancel(NOTIFICATION_ID);
            stopSelf();
        } else if (TextUtils.equals(action, ACTION_PAIRING_DIALOG)) {
            Intent pairingDialogIntent = getPairingDialogIntent(this, intent,
                    BluetoothDevice.EXTRA_PAIRING_INITIATOR_BACKGROUND);

            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
            filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL);
            filter.addAction(ACTION_DISMISS_PAIRING);
            registerReceiver(mCancelReceiver, filter);
            mRegistered = true;

            startActivity(pairingDialogIntent);
        }

        return START_STICKY;
    }

    private void createPairingNotification(Intent intent) {
        Resources res = getResources();
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,
                BLUETOOTH_NOTIFICATION_CHANNEL)
                .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
                .setTicker(res.getString(R.string.bluetooth_notif_ticker))
                .setLocalOnly(true);

        int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
                BluetoothDevice.ERROR);
        Intent pairingDialogIntent = new Intent(ACTION_PAIRING_DIALOG);
        pairingDialogIntent.setClass(this, BluetoothPairingService.class);
        pairingDialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        pairingDialogIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, type);
        PendingIntent pairIntent = PendingIntent.getService(this, 0, pairingDialogIntent,
                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);

        Intent serviceIntent = new Intent(ACTION_DISMISS_PAIRING);
        serviceIntent.setClass(this, BluetoothPairingService.class);
        serviceIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        PendingIntent dismissIntent = PendingIntent.getService(this, 0,
                serviceIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);

        String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
        if (TextUtils.isEmpty(name)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            name = device != null ? device.getAlias() : res.getString(android.R.string.unknownName);
        }

        Log.d(TAG, "Show pairing notification for " + mDevice.getAddress() + " (" + name + ")");
        Log.d(TAG, "Show pairing notification for " + " (" + name + ")");

        Notification.Action pairAction = new Notification.Action.Builder(0,
        NotificationCompat.Action pairAction = new NotificationCompat.Action.Builder(0,
                res.getString(R.string.bluetooth_device_context_pair_connect), pairIntent).build();
        Notification.Action dismissAction = new Notification.Action.Builder(0,
        NotificationCompat.Action dismissAction = new NotificationCompat.Action.Builder(0,
                res.getString(android.R.string.cancel), dismissIntent).build();

        builder.setContentTitle(res.getString(R.string.bluetooth_notif_title))
                .setContentText(res.getString(R.string.bluetooth_notif_message, name))
                .setContentIntent(pairIntent)
                .setDefaults(Notification.DEFAULT_SOUND)
                .setOngoing(true)
                .setColor(getColor(com.android.internal.R.color.system_notification_accent_color))
                .addAction(pairAction)
                .addAction(dismissAction);

        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL);
        filter.addAction(ACTION_DISMISS_PAIRING);
        registerReceiver(mCancelReceiver, filter);
        mRegistered = true;

        startForeground(NOTIFICATION_ID, builder.getNotification());
        return START_REDELIVER_INTENT;
        mNm.notify(NOTIFICATION_ID, builder.build());
    }

    @Override
@@ -180,7 +217,6 @@ public final class BluetoothPairingService extends Service {
            unregisterReceiver(mCancelReceiver);
            mRegistered = false;
        }
        stopForeground(true);
    }

    @Override
+125 −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.settings.bluetooth;

import static com.android.settings.bluetooth.BluetoothPairingService.ACTION_DISMISS_PAIRING;
import static com.android.settings.bluetooth.BluetoothPairingService.ACTION_PAIRING_DIALOG;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Application;
import android.app.NotificationManager;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.DisplayMetrics;

import com.android.settings.R;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;

@RunWith(RobolectricTestRunner.class)
public class BluetoothPairingServiceTest {

    private final String mFakeTicker = "fake_ticker";

    @Mock
    private NotificationManager mNm;
    @Mock
    private BluetoothDevice mDevice;
    @Mock
    private Context mContext;
    @Mock
    private Resources mResources;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private DisplayMetrics mDisplayMetrics;

    private BluetoothPairingService mBluetoothPairingService;
    private Application mApplication;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mBluetoothPairingService = new BluetoothPairingService();
        mBluetoothPairingService.mNm = mNm;
        mApplication = RuntimeEnvironment.application;

        ReflectionHelpers.setField(mBluetoothPairingService, "mBase", mContext);
        when(mContext.getResources()).thenReturn(mResources);
        when(mResources.getString(R.string.bluetooth_notif_ticker)).thenReturn(mFakeTicker);
        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
        mDisplayMetrics.density = 1.5f;
    }

    @Test
    public void receivePairingRequestAction_notificationShown() {
        Intent intent = new Intent();
        intent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);

        mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);

        verify(mNm).notify(eq(mBluetoothPairingService.NOTIFICATION_ID), any());
    }

    @Test
    public void receiveDismissPairingAction_cancelPairing() {
        Intent intent = new Intent();
        intent.setAction(ACTION_DISMISS_PAIRING);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);

        mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);

        verify(mDevice).cancelPairing();
        verify(mNm).cancel(mBluetoothPairingService.NOTIFICATION_ID);
    }

    @Test
    public void receivePairingDialogAction_startActivity() {
        Intent intent = new Intent();
        intent.setAction(ACTION_PAIRING_DIALOG);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
        intent.putExtra(BluetoothDevice.EXTRA_NAME, "fake_name");
        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);

        mBluetoothPairingService.onStartCommand(intent, /* flags */ 0, /* startId */ 0);

        verify(mContext).startActivity(any());
    }
}