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

Commit af019036 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "towards MR2: update to new backend" into rvc-dev am: 19f6945f

Change-Id: Id2d628fecb6bdcb83c88d771977cefe044164b0c
parents 5a54c3c4 19f6945f
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.media

import android.content.Context

import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager

import javax.inject.Inject

/**
 * Factory to create [LocalMediaManager] objects.
 */
class LocalMediaManagerFactory @Inject constructor(
    private val context: Context,
    private val localBluetoothManager: LocalBluetoothManager?
) {
    /** Creates a [LocalMediaManager] for the given package. */
    fun create(packageName: String): LocalMediaManager {
        return InfoMediaManager(context, packageName, null, localBluetoothManager).run {
            LocalMediaManager(context, localBluetoothManager, this, packageName)
        }
    }
}
+58 −135
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;

@@ -56,14 +55,11 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;

import com.android.settingslib.Utils;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;

import org.jetbrains.annotations.NotNull;
@@ -77,7 +73,6 @@ import java.util.concurrent.Executor;
 */
public class MediaControlPanel {
    private static final String TAG = "MediaControlPanel";
    @Nullable private final LocalMediaManager mLocalMediaManager;

    // Button IDs for QS controls
    static final int[] ACTION_IDS = {
@@ -100,7 +95,6 @@ public class MediaControlPanel {
    private MediaSession.Token mToken;
    private MediaController mController;
    private int mBackgroundColor;
    private MediaDevice mDevice;
    protected ComponentName mServiceComponent;
    private boolean mIsRegistered = false;
    private List<KeyFrames> mKeyFrames;
@@ -113,7 +107,6 @@ public class MediaControlPanel {
    public static final String MEDIA_PREFERENCE_KEY = "browser_components";
    private SharedPreferences mSharedPrefs;
    private boolean mCheckedForResumption = false;
    private boolean mIsRemotePlayback;
    private QSMediaBrowser mQSMediaBrowser;

    private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@@ -122,7 +115,6 @@ public class MediaControlPanel {
            Log.d(TAG, "session destroyed");
            mController.unregisterCallback(mSessionCallback);
            clearControls();
            makeInactive();
        }
        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
@@ -130,31 +122,6 @@ public class MediaControlPanel {
            if (s == PlaybackState.STATE_NONE) {
                Log.d(TAG, "playback state change will trigger resumption, state=" + state);
                clearControls();
                makeInactive();
            }
        }
    };

    private final LocalMediaManager.DeviceCallback mDeviceCallback =
            new LocalMediaManager.DeviceCallback() {
        @Override
        public void onDeviceListUpdate(List<MediaDevice> devices) {
            if (mLocalMediaManager == null) {
                return;
            }
            MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
            // Check because this can be called several times while changing devices
            if (mDevice == null || !mDevice.equals(currentDevice)) {
                mDevice = currentDevice;
                updateDevice(mDevice);
            }
        }

        @Override
        public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
            if (mDevice == null || !mDevice.equals(device)) {
                mDevice = device;
                updateDevice(mDevice);
            }
        }
    };
@@ -162,16 +129,13 @@ public class MediaControlPanel {
    /**
     * Initialize a new control panel
     * @param context
     * @param routeManager Manager used to listen for device change events.
     * @param foregroundExecutor foreground executor
     * @param backgroundExecutor background executor, used for processing artwork
     * @param activityStarter activity starter
     */
    public MediaControlPanel(Context context, @Nullable LocalMediaManager routeManager,
            Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
            ActivityStarter activityStarter) {
    public MediaControlPanel(Context context, Executor foregroundExecutor,
            DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) {
        mContext = context;
        mLocalMediaManager = routeManager;
        mForegroundExecutor = foregroundExecutor;
        mBackgroundExecutor = backgroundExecutor;
        mActivityStarter = activityStarter;
@@ -183,7 +147,6 @@ public class MediaControlPanel {
        if (mSeekBarObserver != null) {
            mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
        }
        makeInactive();
    }

    private void loadDimens() {
@@ -318,11 +281,9 @@ public class MediaControlPanel {
        artistText.setText(data.getArtist());

        // Transfer chip
        if (mLocalMediaManager != null) {
        mViewHolder.getSeamless().setVisibility(View.VISIBLE);
        setVisibleAndAlpha(collapsedSet, R.id.media_seamless, true /*visible */);
        setVisibleAndAlpha(expandedSet, R.id.media_seamless, true /*visible */);
            updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
        mViewHolder.getSeamless().setOnClickListener(v -> {
            final Intent intent = new Intent()
                    .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
@@ -332,16 +293,55 @@ public class MediaControlPanel {
            mActivityStarter.startActivity(intent, false, true /* dismissShade */,
                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        });
        } else {
            Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
        }
        final boolean isRemotePlayback;
        PlaybackInfo playbackInfo = mController.getPlaybackInfo();
        if (playbackInfo != null) {
            mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
            isRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
        } else {
            Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
            mIsRemotePlayback = false;
            isRemotePlayback = false;
        }

        ImageView iconView = mViewHolder.getSeamlessIcon();
        TextView deviceName = mViewHolder.getSeamlessText();

        // Update the outline color
        RippleDrawable bkgDrawable = (RippleDrawable) mViewHolder.getSeamless().getBackground();
        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
        rect.setStroke(2, deviceName.getCurrentTextColor());
        rect.setColor(Color.TRANSPARENT);

        if (isRemotePlayback) {
            mViewHolder.getSeamless().setEnabled(false);
            // TODO(b/156875717): setEnabled should cause the alpha to change.
            mViewHolder.getSeamless().setAlpha(0.38f);
            iconView.setImageResource(R.drawable.ic_hardware_speaker);
            iconView.setVisibility(View.VISIBLE);
            deviceName.setText(R.string.media_seamless_remote_device);
        } else if (data.getDevice() != null && data.getDevice().getIcon() != null
                && data.getDevice().getName() != null) {
            mViewHolder.getSeamless().setEnabled(true);
            mViewHolder.getSeamless().setAlpha(1f);
            Drawable icon = data.getDevice().getIcon();
            iconView.setVisibility(View.VISIBLE);

            if (icon instanceof AdaptiveIcon) {
                AdaptiveIcon aIcon = (AdaptiveIcon) icon;
                aIcon.setBackgroundColor(mBackgroundColor);
                iconView.setImageDrawable(aIcon);
            } else {
                iconView.setImageDrawable(icon);
            }
            deviceName.setText(data.getDevice().getName());
        } else {
            // Reset to default
            Log.w(TAG, "device is null. Not binding output chip.");
            mViewHolder.getSeamless().setEnabled(true);
            mViewHolder.getSeamless().setAlpha(1f);
            iconView.setVisibility(View.GONE);
            deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
        }

        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
        // Media controls
        int i = 0;
@@ -382,8 +382,6 @@ public class MediaControlPanel {
        // Set up long press menu
        // TODO: b/156036025 bring back media guts

        makeActive();

        // Update both constraint sets to regenerate the animation.
        mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
        mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
@@ -514,60 +512,6 @@ public class MediaControlPanel {
        return (state.getState() == PlaybackState.STATE_PLAYING);
    }

    /**
     * Update the current device information
     * @param device device information to display
     */
    private void updateDevice(MediaDevice device) {
        mForegroundExecutor.execute(() -> {
            updateChipInternal(device);
        });
    }

    private void updateChipInternal(MediaDevice device) {
        if (mViewHolder == null) {
            return;
        }
        ImageView iconView = mViewHolder.getSeamlessIcon();
        TextView deviceName = mViewHolder.getSeamlessText();

        // Update the outline color
        LinearLayout viewLayout = (LinearLayout) mViewHolder.getSeamless();
        RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
        GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
        rect.setStroke(2, deviceName.getCurrentTextColor());
        rect.setColor(Color.TRANSPARENT);

        if (mIsRemotePlayback) {
            mViewHolder.getSeamless().setEnabled(false);
            mViewHolder.getSeamless().setAlpha(0.38f);
            iconView.setImageResource(R.drawable.ic_hardware_speaker);
            iconView.setVisibility(View.VISIBLE);
            deviceName.setText(R.string.media_seamless_remote_device);
        } else if (device != null) {
            mViewHolder.getSeamless().setEnabled(true);
            mViewHolder.getSeamless().setAlpha(1f);
            Drawable icon = device.getIcon();
            iconView.setVisibility(View.VISIBLE);

            if (icon instanceof AdaptiveIcon) {
                AdaptiveIcon aIcon = (AdaptiveIcon) icon;
                aIcon.setBackgroundColor(mBackgroundColor);
                iconView.setImageDrawable(aIcon);
            } else {
                iconView.setImageDrawable(icon);
            }
            deviceName.setText(device.getName());
        } else {
            // Reset to default
            Log.d(TAG, "device is null. Not binding output chip.");
            mViewHolder.getSeamless().setEnabled(true);
            mViewHolder.getSeamless().setAlpha(1f);
            iconView.setVisibility(View.GONE);
            deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
        }
    }

    /**
     * Puts controls into a resumption state if possible, or calls removePlayer if no component was
     * found that could resume playback
@@ -642,27 +586,6 @@ public class MediaControlPanel {
        set.setAlpha(actionId, visible ? 1.0f : 0.0f);
    }

    private void makeActive() {
        Assert.isMainThread();
        if (!mIsRegistered) {
            if (mLocalMediaManager != null) {
                mLocalMediaManager.registerCallback(mDeviceCallback);
                mLocalMediaManager.startScan();
            }
            mIsRegistered = true;
        }
    }

    private void makeInactive() {
        Assert.isMainThread();
        if (mIsRegistered) {
            if (mLocalMediaManager != null) {
                mLocalMediaManager.stopScan();
                mLocalMediaManager.unregisterCallback(mDeviceCallback);
            }
            mIsRegistered = false;
        }
    }
    /**
     * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
     * component to the list of resumption components
+8 −1
Original line number Diff line number Diff line
@@ -34,7 +34,8 @@ data class MediaData(
    val actionsToShowInCompact: List<Int>,
    val packageName: String?,
    val token: MediaSession.Token?,
    val clickIntent: PendingIntent?
    val clickIntent: PendingIntent?,
    val device: MediaDeviceData?
)

/** State of a media action. */
@@ -43,3 +44,9 @@ data class MediaAction(
    val intent: PendingIntent?,
    val contentDescription: CharSequence?
)

/** State of the media device. */
data class MediaDeviceData(
    val icon: Drawable?,
    val name: String?
)
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.media

import javax.inject.Inject
import javax.inject.Singleton

/**
 * Combines updates from [MediaDataManager] with [MediaDeviceManager].
 */
@Singleton
class MediaDataCombineLatest @Inject constructor(
    private val dataSource: MediaDataManager,
    private val deviceSource: MediaDeviceManager
) {
    private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
    private val entries: MutableMap<String, Pair<MediaData?, MediaDeviceData?>> = mutableMapOf()

    init {
        dataSource.addListener(object : MediaDataManager.Listener {
            override fun onMediaDataLoaded(key: String, data: MediaData) {
                entries[key] = data to entries[key]?.second
                update(key)
            }
            override fun onMediaDataRemoved(key: String) {
                remove(key)
            }
        })
        deviceSource.addListener(object : MediaDeviceManager.Listener {
            override fun onMediaDeviceChanged(key: String, data: MediaDeviceData?) {
                entries[key] = entries[key]?.first to data
                update(key)
            }
            override fun onKeyRemoved(key: String) {
                remove(key)
            }
        })
    }

    /**
     * Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
     */
    fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)

    /**
     * Remove a listener registered with addListener.
     */
    fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)

    private fun update(key: String) {
        val (entry, device) = entries[key] ?: null to null
        if (entry != null && device != null) {
            val data = entry.copy(device = device)
            listeners.forEach {
                it.onMediaDataLoaded(key, data)
            }
        }
    }

    private fun remove(key: String) {
        entries.remove(key)?.let {
            listeners.forEach {
                it.onMediaDataRemoved(key)
            }
        }
    }
}
+15 −18
Original line number Diff line number Diff line
@@ -55,7 +55,19 @@ private const val LUMINOSITY_THRESHOLD = 0.05f
private const val SATURATION_MULTIPLIER = 0.8f

private val LOADING = MediaData(false, 0, null, null, null, null, null,
        emptyList(), emptyList(), null, null, null)
        emptyList(), emptyList(), null, null, null, null)

fun isMediaNotification(sbn: StatusBarNotification): Boolean {
    if (!sbn.notification.hasMediaSession()) {
        return false
    }
    val notificationStyle = sbn.notification.notificationStyle
    if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
            Notification.MediaStyle::class.java.equals(notificationStyle)) {
        return true
    }
    return false
}

/**
 * A class that facilitates management and loading of Media Data, ready for binding.
@@ -72,7 +84,7 @@ class MediaDataManager @Inject constructor(
    private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()

    fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
        if (isMediaNotification(sbn)) {
        if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
            if (!mediaEntries.containsKey(key)) {
                mediaEntries.put(key, LOADING)
            }
@@ -204,7 +216,7 @@ class MediaDataManager @Inject constructor(
        foregroundExecutor.execute {
            onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
                    artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
                    notif.contentIntent))
                    notif.contentIntent, null))
        }
    }

@@ -270,21 +282,6 @@ class MediaDataManager @Inject constructor(
        }
    }

    private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
        if (!Utils.useQsMediaPlayer(context)) {
            return false
        }
        if (!sbn.notification.hasMediaSession()) {
            return false
        }
        val notificationStyle = sbn.notification.notificationStyle
        if (Notification.DecoratedMediaCustomViewStyle::class.java.equals(notificationStyle) ||
                Notification.MediaStyle::class.java.equals(notificationStyle)) {
            return true
        }
        return false
    }

    /**
     * Are there any media notifications active?
     */
Loading