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

Commit 79e0da28 authored by tim peng's avatar tim peng Committed by Android (Google) Code Review
Browse files

Merge "Implement naive rule of device order on seamless transfer"

parents c8cbd5f9 a8b826c0
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package com.android.settingslib.media;

import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;

@@ -33,6 +35,13 @@ public class BluetoothMediaDevice extends MediaDevice {
    BluetoothMediaDevice(Context context, CachedBluetoothDevice device) {
        super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
        mCachedDevice = device;
        initDeviceRecord();
        buildConnectedState(device);
    }

    private void buildConnectedState(CachedBluetoothDevice device) {
        mIsConnected = device.isActiveDevice(BluetoothProfile.A2DP)
                || device.isActiveDevice(BluetoothProfile.HEARING_AID);
    }

    @Override
@@ -51,10 +60,16 @@ public class BluetoothMediaDevice extends MediaDevice {
        return MediaDeviceUtils.getId(mCachedDevice);
    }

    @Override
    public void notifyConnectedChanged() {
        buildConnectedState(mCachedDevice);
    }

    @Override
    public void connect() {
        //TODO(b/117129183): add callback to notify LocalMediaManager connection state.
        mIsConnected = mCachedDevice.setActive();
        super.connect();
        Log.d(TAG, "connect() device : " + getName() + ", is selected : " + mIsConnected);
    }

@@ -70,4 +85,18 @@ public class BluetoothMediaDevice extends MediaDevice {
    public CachedBluetoothDevice getCachedDevice() {
        return mCachedDevice;
    }

    @Override
    protected boolean isCarKitDevice() {
        final BluetoothClass bluetoothClass = mCachedDevice.getDevice().getBluetoothClass();
        if (bluetoothClass != null) {
            switch (bluetoothClass.getDeviceClass()) {
                // Both are common CarKit class
                case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
                case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
                    return true;
            }
        }
        return false;
    }
}
+50 −22
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settingslib.media;
import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.util.Log;

@@ -30,6 +31,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
@@ -39,8 +41,8 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall

    private static final String TAG = "BluetoothMediaManager";

    private final DeviceAttributeChangeCallback mCachedDeviceCallback =
            new DeviceAttributeChangeCallback();
    private final DeviceProfileNotReadyObserverCallback mObserverCallback =
            new DeviceProfileNotReadyObserverCallback();

    private LocalBluetoothManager mLocalBluetoothManager;
    private LocalBluetoothProfileManager mProfileManager;
@@ -48,6 +50,10 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
    private MediaDevice mLastAddedDevice;
    private MediaDevice mLastRemovedDevice;

    private boolean mIsA2dpProfileReady = false;
    private boolean mIsHearingAidProfileReady = false;
    private Collection<CachedBluetoothDevice> mCachedDevices;

    BluetoothMediaManager(Context context, LocalBluetoothManager localBluetoothManager,
            Notification notification) {
        super(context, notification);
@@ -62,6 +68,18 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
        mLocalBluetoothManager.getEventManager().registerCallback(this);
        buildBluetoothDeviceList();
        dispatchDeviceListAdded();

        // The profile may not ready when calling startScan().
        // Device status are all disconnected since profiles are not ready to connected.
        // In this case, we observe all devices in CachedDeviceManager.
        // When one of these device is connected to profile, will call buildBluetoothDeviceList()
        // again to find the connected devices.
        if (!mIsA2dpProfileReady && !mIsHearingAidProfileReady) {
            mCachedDevices = mLocalBluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
            for (CachedBluetoothDevice device : mCachedDevices) {
                device.registerCallback(mObserverCallback);
            }
        }
    }

    private void buildBluetoothDeviceList() {
@@ -75,6 +93,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
            Log.w(TAG, "addConnectedA2dpDevices() a2dp profile is null!");
            return;
        }

        final List<BluetoothDevice> devices = a2dpProfile.getConnectedDevices();
        final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
                mLocalBluetoothManager.getCachedDeviceManager();
@@ -95,6 +114,8 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
                addMediaDevice(cachedDevice);
            }
        }

        mIsA2dpProfileReady = a2dpProfile.isProfileReady();
    }

    private void addConnectedHearingAidDevices() {
@@ -103,6 +124,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
            Log.w(TAG, "addConnectedA2dpDevices() hap profile is null!");
            return;
        }

        final List<Long> devicesHiSyncIds = new ArrayList<>();
        final List<BluetoothDevice> devices = hapProfile.getConnectedDevices();
        final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
@@ -128,13 +150,14 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
                addMediaDevice(cachedDevice);
            }
        }

        mIsHearingAidProfileReady = hapProfile.isProfileReady();
    }

    private void addMediaDevice(CachedBluetoothDevice cachedDevice) {
        MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
        if (mediaDevice == null) {
            mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice);
            cachedDevice.registerCallback(mCachedDeviceCallback);
            mLastAddedDevice = mediaDevice;
            mMediaDevices.add(mediaDevice);
        }
@@ -143,16 +166,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
    @Override
    public void stopScan() {
        mLocalBluetoothManager.getEventManager().unregisterCallback(this);
        unregisterCachedDeviceCallback();
    }

    private void unregisterCachedDeviceCallback() {
        for (MediaDevice device : mMediaDevices) {
            if (device instanceof BluetoothMediaDevice) {
                ((BluetoothMediaDevice) device).getCachedDevice()
                        .unregisterCallback(mCachedDeviceCallback);
            }
        }
    }

    @Override
@@ -164,8 +177,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
            final List<MediaDevice> removeDevicesList = new ArrayList<>();
            for (MediaDevice device : mMediaDevices) {
                if (device instanceof BluetoothMediaDevice) {
                    ((BluetoothMediaDevice) device).getCachedDevice()
                            .unregisterCallback(mCachedDeviceCallback);
                    removeDevicesList.add(device);
                }
            }
@@ -185,7 +196,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
    private boolean isCachedDeviceConnected(CachedBluetoothDevice cachedDevice) {
        final boolean isConnectedHearingAidDevice = cachedDevice.isConnectedHearingAidDevice();
        final boolean isConnectedA2dpDevice = cachedDevice.isConnectedA2dpDevice();
        Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice.getName()
        Log.d(TAG, "isCachedDeviceConnected() cachedDevice : " + cachedDevice
                + ", is hearing aid connected : " + isConnectedHearingAidDevice
                + ", is a2dp connected : " + isConnectedA2dpDevice);

@@ -210,7 +221,6 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
    private void removeMediaDevice(CachedBluetoothDevice cachedDevice) {
        final MediaDevice mediaDevice = findMediaDevice(MediaDeviceUtils.getId(cachedDevice));
        if (mediaDevice != null) {
            cachedDevice.unregisterCallback(mCachedDeviceCallback);
            mLastRemovedDevice = mediaDevice;
            mMediaDevices.remove(mediaDevice);
        }
@@ -226,7 +236,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
    @Override
    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
            int bluetoothProfile) {
        Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice.getName()
        Log.d(TAG, "onProfileConnectionStateChanged() device: " + cachedDevice
                + ", state: " + state + ", bluetoothProfile: " + bluetoothProfile);

        if (isCachedDeviceConnected(cachedDevice)) {
@@ -240,8 +250,7 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall

    @Override
    public void onAclConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
        Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice.getName()
                + ", state: " + state);
        Log.d(TAG, "onAclConnectionStateChanged() device: " + cachedDevice + ", state: " + state);

        if (isCachedDeviceConnected(cachedDevice)) {
            addMediaDevice(cachedDevice);
@@ -252,10 +261,29 @@ public class BluetoothMediaManager extends MediaManager implements BluetoothCall
        }
    }

    class DeviceAttributeChangeCallback implements CachedBluetoothDevice.Callback {
    @Override
    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
        Log.d(TAG, "onActiveDeviceChanged : device : "
                + activeDevice + ", profile : " + bluetoothProfile);
        if (BluetoothProfile.HEARING_AID == bluetoothProfile
                || BluetoothProfile.A2DP == bluetoothProfile) {
            final String id = activeDevice == null
                    ? PhoneMediaDevice.ID : MediaDeviceUtils.getId(activeDevice);
            dispatchActiveDeviceChanged(id);
        }
    }

    class DeviceProfileNotReadyObserverCallback implements CachedBluetoothDevice.Callback {

        @Override
        public void onDeviceAttributesChanged() {
            dispatchDeviceAttributesChanged();
            if (!mIsA2dpProfileReady && !mIsHearingAidProfileReady) {
                for (CachedBluetoothDevice device : mCachedDevices) {
                    device.unregisterCallback(mObserverCallback);
                }
                buildBluetoothDeviceList();
                dispatchDeviceListAdded();
            }
        }
    }
}
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 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.settingslib.media;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * ConnectionRecordManager represents the sharedPreferences operation on device usage record
 */
public class ConnectionRecordManager {
    private static final Object sInstanceSync = new Object();
    private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device";
    private static final String SHARED_PREFERENCES_NAME = "seamless_transfer_record";
    private static final String TAG = "ConnectionRecordManager";
    private static ConnectionRecordManager sInstance;

    private String mLastSelectedDevice;

    /**
     * Get an {@code ConnectionRecordManager} instance (create one if necessary).
     */
    public static ConnectionRecordManager getInstance() {
        synchronized (sInstanceSync) {
            if (sInstance == null) {
                sInstance = new ConnectionRecordManager();
            }
        }
        return sInstance;
    }

    private SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
    }

    /**
     * Get connection record from sharedPreferences
     *
     * @param id a unique device Id
     * @return the the usage result
     */
    public synchronized int fetchConnectionRecord(Context context, String id) {
        return getSharedPreferences(context).getInt(id, 0);
    }

    /**
     * Get the last selected device from sharedPreferences
     */
    public synchronized void fetchLastSelectedDevice(Context context) {
        mLastSelectedDevice = getSharedPreferences(context).getString(KEY_LAST_SELECTED_DEVICE,
                null);
    }

    /**
     * Set device usage time and last selected device in sharedPreference
     *
     * @param id a unique device Id
     * @param record usage times
     */
    public synchronized void setConnectionRecord(Context context, String id, int record) {
        final SharedPreferences.Editor editor = getSharedPreferences(context).edit();
        // Update used times
        mLastSelectedDevice = id;
        editor.putInt(mLastSelectedDevice, record);
        // Update last used device
        editor.putString(KEY_LAST_SELECTED_DEVICE, mLastSelectedDevice);
        editor.apply();
    }

    /**
     * @return the last selected device
     */
    public synchronized String getLastSelectedDevice() {
        return mLastSelectedDevice;
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.settingslib.media;

import android.content.Context;
import android.widget.Toast;

import androidx.mediarouter.media.MediaRouter;

@@ -33,6 +34,7 @@ public class InfoMediaDevice extends MediaDevice {
    InfoMediaDevice(Context context, MediaRouter.RouteInfo info) {
        super(context, MediaDeviceType.TYPE_CAST_DEVICE);
        mRouteInfo = info;
        initDeviceRecord();
    }

    @Override
@@ -51,15 +53,23 @@ public class InfoMediaDevice extends MediaDevice {
        return MediaDeviceUtils.getId(mRouteInfo);
    }

    @Override
    public void notifyConnectedChanged() {
        //TODO(b/117129183): check mIsConnected state
    }

    @Override
    public void connect() {
        //TODO(b/117129183): use MediaController2 to transfer media
        mIsConnected = true;
        super.connect();
        //mIsConnected = true;
        Toast.makeText(mContext, "This is cast device !", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void disconnect() {
        //TODO(b/117129183): disconnected last select device
        mIsConnected = false;
        //mIsConnected = false;
    }
}
+53 −14
Original line number Diff line number Diff line
@@ -28,15 +28,20 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * LocalMediaManager provide interface to get MediaDevice list and transfer media to MediaDevice.
 */
public class LocalMediaManager implements BluetoothCallback {

    private static final Comparator<MediaDevice> COMPARATOR = Comparator.naturalOrder();
    private static final String TAG = "LocalMediaManager";

    public static final String NOTIFICATION_EXTRA = "notification_extra";
    public static final String NOTIFICATION_PACKAGE_NAME = "notification_package_name";

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({MediaDeviceState.STATE_CONNECTED,
            MediaDeviceState.STATE_CONNECTING,
@@ -56,7 +61,6 @@ public class LocalMediaManager implements BluetoothCallback {
    private InfoMediaManager mInfoMediaManager;

    private LocalBluetoothManager mLocalBluetoothManager;
    private MediaDevice mLastConnectedDevice;
    private MediaDevice mPhoneDevice;

    /**
@@ -96,29 +100,43 @@ public class LocalMediaManager implements BluetoothCallback {
     * @param connectDevice the MediaDevice
     */
    public void connectDevice(MediaDevice connectDevice) {
        if (connectDevice == mLastConnectedDevice) {
        final MediaDevice currentDevice = getCurrentConnectedDevice();
        final MediaDevice device =
                MediaDeviceUtils.findMediaDevice(mMediaDevices, connectDevice.getId());
        if (device != null && currentDevice != null
                && device.getId().equals(currentDevice.getId())) {
            return;
        }

        if (mLastConnectedDevice != null) {
            mLastConnectedDevice.disconnect();
        //TODO(b/117129183): For demo, will remove check connectDevice is InfoMediaDevice.
        if (currentDevice != null && !(connectDevice instanceof InfoMediaDevice)) {
            currentDevice.disconnect();
        }

        connectDevice.connect();
        if (connectDevice.isConnected()) {
            mLastConnectedDevice = connectDevice;
        }
        device.connect();

        final int state = connectDevice.isConnected()
        final int state = device.isConnected()
                ? MediaDeviceState.STATE_CONNECTED
                : MediaDeviceState.STATE_DISCONNECTED;
        dispatchSelectedDeviceStateChanged(connectDevice, state);
        dispatchSelectedDeviceStateChanged(mMediaDevices, device, state);
    }

    void dispatchSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state) {
    private MediaDevice getCurrentConnectedDevice() {
        for (MediaDevice device : mMediaDevices) {
            if (device.isConnected()) {
                return device;
            }
        }
        Log.w(TAG, "getCurrentConnectedDevice() cannot find current connected device !");
        return null;
    }

    void dispatchSelectedDeviceStateChanged(List<MediaDevice> mMediaDevices, MediaDevice device,
            @MediaDeviceState int state) {
        synchronized (mCallbacks) {
            for (DeviceCallback callback : mCallbacks) {
                callback.onSelectedDeviceStateChanged(device, state);
                callback.onSelectedDeviceStateChanged(new ArrayList<>(mMediaDevices), device,
                        state);
            }
        }
    }
@@ -153,6 +171,7 @@ public class LocalMediaManager implements BluetoothCallback {

    void dispatchDeviceListUpdate() {
        synchronized (mCallbacks) {
            Collections.sort(mMediaDevices, COMPARATOR);
            for (DeviceCallback callback : mCallbacks) {
                callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
            }
@@ -206,6 +225,25 @@ public class LocalMediaManager implements BluetoothCallback {
        public void onDeviceAttributesChanged() {
            dispatchDeviceListUpdate();
        }

        @Override
        public void onActiveDeviceChanged(String id) {
            final MediaDevice currentDevice = getCurrentConnectedDevice();
            final MediaDevice connectDevice = MediaDeviceUtils.findMediaDevice(mMediaDevices, id);

            if (connectDevice != null && currentDevice != null
                    && connectDevice.getId().equals(currentDevice.getId())) {
                return;
            }
            if (currentDevice != null) {
                currentDevice.notifyConnectedChanged();
            }
            if (connectDevice != null) {
                connectDevice.notifyConnectedChanged();
            }

            dispatchDeviceListUpdate();
        }
    }


@@ -229,6 +267,7 @@ public class LocalMediaManager implements BluetoothCallback {
         * {@link MediaDeviceState#STATE_CONNECTING},
         * {@link MediaDeviceState#STATE_DISCONNECTED}
         */
        void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
        void onSelectedDeviceStateChanged(List<MediaDevice> devices, MediaDevice device,
                @MediaDeviceState int state);
    }
}
Loading