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

Commit 655b1f43 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Bluetooth: MCE: Add new API to set message read status or deleted status" am: 0bd60631

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

Change-Id: I4586c0e230795e8ceea851898d7affb8a292b3e4
parents 61663ada 0bd60631
Loading
Loading
Loading
Loading
+69 −0
Original line number Diff line number Diff line
@@ -52,6 +52,18 @@ public final class BluetoothMapClient implements BluetoothProfile {
    public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
            "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";

    /**
     * Action to notify read status changed
     */
    public static final String ACTION_MESSAGE_READ_STATUS_CHANGED =
            "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED";

    /**
     * Action to notify deleted status changed
     */
    public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED =
            "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED";

    /* Extras used in ACTION_MESSAGE_RECEIVED intent.
     * NOTE: HANDLE is only valid for a single session with the device. */
    public static final String EXTRA_MESSAGE_HANDLE =
@@ -65,6 +77,25 @@ public final class BluetoothMapClient implements BluetoothProfile {
    public static final String EXTRA_SENDER_CONTACT_NAME =
            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";

    /**
     * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED
     * Contains the MAP message deleted status
     * Possible values are:
     * true: deleted
     * false: undeleted
     */
    public static final String EXTRA_MESSAGE_DELETED_STATUS =
            "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS";

    /**
     * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED
     * Possible values are:
     * 0: failure
     * 1: success
     */
    public static final String EXTRA_RESULT_CODE =
            "android.bluetooth.device.extra.RESULT_CODE";

    /** There was an error trying to obtain the state */
    public static final int STATE_ERROR = -1;

@@ -75,6 +106,12 @@ public final class BluetoothMapClient implements BluetoothProfile {

    private static final int UPLOADING_FEATURE_BITMASK = 0x08;

    /** Parameters in setMessageStatus */
    public static final int UNREAD = 0;
    public static final int READ = 1;
    public static final int UNDELETED = 2;
    public static final int DELETED = 3;

    private BluetoothAdapter mAdapter;
    private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
            new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
@@ -405,6 +442,38 @@ public final class BluetoothMapClient implements BluetoothProfile {
        return false;
    }

    /**
     * Set message status of message on MSE
     * <p>
     * When read status changed, the result will be published via
     * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED}
     * When deleted status changed, the result will be published via
     * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED}
     *
     * @param device Bluetooth device
     * @param handle message handle
     * @param status <code>UNREAD</code> for "unread", <code>READ</code> for
     *            "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for
     *            "deleted", otherwise return error
     * @return <code>true</code> if request has been sent, <code>false</code> on error
     *
     */
    @RequiresPermission(Manifest.permission.READ_SMS)
    public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
        if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
        final IBluetoothMapClient service = getService();
        if (service != null && isEnabled() && isValidDevice(device) && handle != null &&
            (status == READ || status == UNREAD || status == UNDELETED  || status == DELETED)) {
            try {
                return service.setMessageStatus(device, handle, status);
            } catch (RemoteException e) {
                Log.e(TAG, Log.getStackTraceString(new Throwable()));
                return false;
            }
        }
        return false;
    }

    private boolean isEnabled() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
+5 −1
Original line number Diff line number Diff line
@@ -15,14 +15,18 @@
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.android.bluetooth.tests" >
          package="com.android.bluetooth.tests"
          android:sharedUserId="android.uid.bluetooth" >

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+24 −0
Original line number Diff line number Diff line
@@ -360,6 +360,30 @@ public class BluetoothStressTest extends InstrumentationTestCase {
        mTestUtils.unpair(mAdapter, device);
    }

    /* Make sure there is at least 1 unread message in the last week on remote device */
    public void testMceSetMessageStatus() {
        int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations;
        if (iterations == 0) {
            return;
        }

        BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
        mTestUtils.enable(mAdapter);
        mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null);
        mTestUtils.mceGetUnreadMessage(mAdapter, device);

        for (int i = 0; i < iterations; i++) {
            mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ);
            mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD);
        }

        /**
         * It is hard to find device to support set undeleted status, so just
         * set deleted in 1 iteration
         **/
        mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED);
    }

    private void sleep(long time) {
        try {
            Thread.sleep(time);
+11 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.util.Log;
 *     [-e connect_input_iterations <iterations>] \
 *     [-e connect_pan_iterations <iterations>] \
 *     [-e start_stop_sco_iterations <iterations>] \
 *     [-e mce_set_message_status_iterations <iterations>] \
 *     [-e pair_address <address>] \
 *     [-e headset_address <address>] \
 *     [-e a2dp_address <address>] \
@@ -64,6 +65,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner {
    public static int sConnectInputIterations = 100;
    public static int sConnectPanIterations = 100;
    public static int sStartStopScoIterations = 100;
    public static int sMceSetMessageStatusIterations = 100;

    public static String sDeviceAddress = "";
    public static byte[] sDevicePairPin = {'1', '2', '3', '4'};
@@ -173,6 +175,15 @@ public class BluetoothTestRunner extends InstrumentationTestRunner {
            }
        }

        val = arguments.getString("mce_set_message_status_iterations");
        if (val != null) {
            try {
                sMceSetMessageStatusIterations = Integer.parseInt(val);
            } catch (NumberFormatException e) {
                // Invalid argument, fall back to default value
            }
        }

        val = arguments.getString("device_address");
        if (val != null) {
            sDeviceAddress = val;
+154 −2
Original line number Diff line number Diff line
@@ -56,6 +56,10 @@ public class BluetoothTestUtils extends Assert {
    private static final int CONNECT_PROXY_TIMEOUT = 5000;
    /** Time between polls in ms. */
    private static final int POLL_TIME = 100;
    /** Timeout to get map message in ms. */
    private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000;
    /** Timeout to set map message status in ms. */
    private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000;

    private abstract class FlagReceiver extends BroadcastReceiver {
        private int mExpectedFlags = 0;
@@ -98,6 +102,8 @@ public class BluetoothTestUtils extends Assert {
        private static final int STATE_TURNING_ON_FLAG = 1 << 6;
        private static final int STATE_ON_FLAG = 1 << 7;
        private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
        private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9;
        private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10;

        public BluetoothReceiver(int expectedFlags) {
            super(expectedFlags);
@@ -231,6 +237,9 @@ public class BluetoothTestUtils extends Assert {
                case BluetoothProfile.PAN:
                    mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
                    break;
                case BluetoothProfile.MAP_CLIENT:
                    mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED;
                    break;
                default:
                    mConnectionAction = null;
            }
@@ -308,6 +317,34 @@ public class BluetoothTestUtils extends Assert {
        }
    }


    private class MceSetMessageStatusReceiver extends FlagReceiver {
        private static final int MESSAGE_RECEIVED_FLAG = 1;
        private static final int STATUS_CHANGED_FLAG = 1 << 1;

        public MceSetMessageStatusReceiver(int expectedFlags) {
            super(expectedFlags);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
                String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE);
                assertNotNull(handle);
                setFiredFlag(MESSAGE_RECEIVED_FLAG);
                mMsgHandle = handle;
            } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) {
                int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE);
                assertEquals(result, BluetoothMapClient.RESULT_SUCCESS);
                setFiredFlag(STATUS_CHANGED_FLAG);
            } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) {
                int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE);
                assertEquals(result, BluetoothMapClient.RESULT_SUCCESS);
                setFiredFlag(STATUS_CHANGED_FLAG);
            }
        }
    }

    private BluetoothProfile.ServiceListener mServiceListener =
            new BluetoothProfile.ServiceListener() {
        @Override
@@ -326,6 +363,9 @@ public class BluetoothTestUtils extends Assert {
                    case BluetoothProfile.PAN:
                        mPan = (BluetoothPan) proxy;
                        break;
                    case BluetoothProfile.MAP_CLIENT:
                        mMce = (BluetoothMapClient) proxy;
                        break;
                }
            }
        }
@@ -346,6 +386,9 @@ public class BluetoothTestUtils extends Assert {
                    case BluetoothProfile.PAN:
                        mPan = null;
                        break;
                    case BluetoothProfile.MAP_CLIENT:
                        mMce = null;
                        break;
                }
            }
        }
@@ -362,6 +405,8 @@ public class BluetoothTestUtils extends Assert {
    private BluetoothHeadset mHeadset = null;
    private BluetoothHidHost mInput = null;
    private BluetoothPan mPan = null;
    private BluetoothMapClient mMce = null;
    private String mMsgHandle = null;

    /**
     * Creates a utility instance for testing Bluetooth.
@@ -898,7 +943,7 @@ public class BluetoothTestUtils extends Assert {
     * @param adapter The BT adapter.
     * @param device The remote device.
     * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
     * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
     * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}..
     * @param methodName The method name to printed in the logs.  If null, will be
     * "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
     */
@@ -941,6 +986,8 @@ public class BluetoothTestUtils extends Assert {
                    assertTrue(((BluetoothHeadset)proxy).connect(device));
                } else if (profile == BluetoothProfile.HID_HOST) {
                    assertTrue(((BluetoothHidHost)proxy).connect(device));
                } else if (profile == BluetoothProfile.MAP_CLIENT) {
                    assertTrue(((BluetoothMapClient)proxy).connect(device));
                }
                break;
            default:
@@ -1016,6 +1063,8 @@ public class BluetoothTestUtils extends Assert {
                    assertTrue(((BluetoothHeadset)proxy).disconnect(device));
                } else if (profile == BluetoothProfile.HID_HOST) {
                    assertTrue(((BluetoothHidHost)proxy).disconnect(device));
                } else if (profile == BluetoothProfile.MAP_CLIENT) {
                    assertTrue(((BluetoothMapClient)proxy).disconnect(device));
                }
                break;
            case BluetoothProfile.STATE_DISCONNECTED:
@@ -1373,6 +1422,89 @@ public class BluetoothTestUtils extends Assert {
        }
    }

    public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) {
        int mask;
        String methodName = "getUnreadMessage";

        if (!adapter.isEnabled()) {
            fail(String.format("%s bluetooth not enabled", methodName));
        }

        if (!adapter.getBondedDevices().contains(device)) {
            fail(String.format("%s device not paired", methodName));
        }

        mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT);
        assertNotNull(mMce);

        if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
            fail(String.format("%s device is not connected", methodName));
        }

        mMsgHandle = null;
        mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG;
        MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask);
        assertTrue(mMce.getUnreadMessages(device));

        long s = System.currentTimeMillis();
        while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) {
            if ((receiver.getFiredFlags() & mask) == mask) {
                writeOutput(String.format("%s completed", methodName));
                removeReceiver(receiver);
                return;
            }
            sleep(POLL_TIME);
        }
        int firedFlags = receiver.getFiredFlags();
        removeReceiver(receiver);
        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
                methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask));
    }

    /**
     * Set a message to read/unread/deleted/undeleted
     */
    public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) {
        int mask;
        String methodName = "setMessageStatus";

        if (!adapter.isEnabled()) {
            fail(String.format("%s bluetooth not enabled", methodName));
        }

        if (!adapter.getBondedDevices().contains(device)) {
            fail(String.format("%s device not paired", methodName));
        }

        mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT);
        assertNotNull(mMce);

        if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
            fail(String.format("%s device is not connected", methodName));
        }

        assertNotNull(mMsgHandle);
        mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG;
        MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask);

        assertTrue(mMce.setMessageStatus(device, mMsgHandle, status));

        long s = System.currentTimeMillis();
        while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) {
            if ((receiver.getFiredFlags() & mask) == mask) {
                writeOutput(String.format("%s completed", methodName));
                removeReceiver(receiver);
                return;
            }
            sleep(POLL_TIME);
        }

        int firedFlags = receiver.getFiredFlags();
        removeReceiver(receiver);
        fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
                methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask));
    }

    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
        IntentFilter filter = new IntentFilter();
        for (String action: actions) {
@@ -1408,7 +1540,8 @@ public class BluetoothTestUtils extends Assert {
        String[] actions = {
                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
                BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED};
                BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED,
                BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED};
        ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
                expectedFlags);
        addReceiver(receiver, actions);
@@ -1430,6 +1563,16 @@ public class BluetoothTestUtils extends Assert {
        return receiver;
    }

    private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device,
            int expectedFlags) {
        String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED,
            BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED,
            BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED};
        MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags);
        addReceiver(receiver, actions);
        return receiver;
    }

    private void removeReceiver(BroadcastReceiver receiver) {
        mContext.unregisterReceiver(receiver);
        mReceivers.remove(receiver);
@@ -1456,6 +1599,10 @@ public class BluetoothTestUtils extends Assert {
                if (mPan != null) {
                    return mPan;
                }
            case BluetoothProfile.MAP_CLIENT:
                if (mMce != null) {
                    return mMce;
                }
                break;
            default:
                return null;
@@ -1483,6 +1630,11 @@ public class BluetoothTestUtils extends Assert {
                    sleep(POLL_TIME);
                }
                return mPan;
            case BluetoothProfile.MAP_CLIENT:
                while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
                    sleep(POLL_TIME);
                }
                return mMce;
            default:
                return null;
        }