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

Commit 10ab47af authored by timhypeng's avatar timhypeng
Browse files

Add MediaOutputAdapter for Media device list

-Build device item layout by different cases
-Provide UI component for different operations
-Add MediaOutputBaseAdapter for common method
-Add MediaOutputAdapterTest for unit test

Bug: 155822415
Test: atest MediaOutputAdapterTest
Change-Id: Ida509ed401320b3665d7a5f29f4df5db096ff29c
parent 6c143511
Loading
Loading
Loading
Loading
+112 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ 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.
  -->

<FrameLayout
    android:id="@+id/device_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="64dp">

    <FrameLayout
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="16dp">
        <ImageView
            android:id="@+id/title_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"/>
    </FrameLayout>

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="68dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:textColor="?android:attr/textColorPrimary"
        android:textSize="14sp"/>

    <RelativeLayout
        android:id="@+id/two_line_layout"
        android:layout_width="wrap_content"
        android:layout_height="48dp"
        android:layout_marginStart="52dp"
        android:layout_marginEnd="69dp"
        android:layout_marginTop="10dp">
        <TextView
            android:id="@+id/two_line_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="15dp"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="?android:attr/textColorPrimary"
            android:textSize="14sp"/>
        <TextView
            android:id="@+id/subtitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="15dp"
            android:layout_marginBottom="7dp"
            android:layout_alignParentBottom="true"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="?android:attr/textColorSecondary"
            android:textSize="12sp"
            android:fontFamily="roboto-regular"
            android:visibility="gone"/>
        <ProgressBar
            android:id="@+id/volume_indeterminate_progress"
            style="@*android:style/Widget.Material.ProgressBar.Horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="15dp"
            android:layout_marginBottom="1dp"
            android:layout_alignParentBottom="true"
            android:indeterminate="true"
            android:indeterminateOnly="true"
            android:visibility="gone"/>
        <SeekBar
            android:id="@+id/volume_seekbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"/>
    </RelativeLayout>

    <View
        android:layout_width="1dp"
        android:layout_height="36dp"
        android:layout_marginEnd="68dp"
        android:layout_gravity="right|center_vertical"
        android:background="?android:attr/listDivider"
        android:visibility="gone"/>

    <ImageView
        android:id="@+id/end_icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_gravity="right|center_vertical"
        android:layout_marginEnd="24dp"
        android:visibility="gone"/>
</FrameLayout>
 No newline at end of file
+163 −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.dialog;

import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;

import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;

import com.android.settingslib.Utils;
import com.android.settingslib.media.LocalMediaManager.MediaDeviceState;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;

import java.util.List;

/**
 * Adapter for media output dialog.
 */
public class MediaOutputAdapter extends MediaOutputBaseAdapter {

    private static final String TAG = "MediaOutputAdapter";
    private static final int PAIR_NEW = 1;

    public MediaOutputAdapter(MediaOutputController controller) {
        super(controller);
    }

    @Override
    public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
            int viewType) {
        super.onCreateViewHolder(viewGroup, viewType);

        return new MediaDeviceViewHolder(mHolderView);
    }

    @Override
    public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
        if (mController.isZeroMode() && position == (mController.getMediaDevices().size())) {
            viewHolder.onBind(PAIR_NEW);
        } else if (position < (mController.getMediaDevices().size())) {
            viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position));
        } else {
            Log.d(TAG, "Incorrect position: " + position);
        }
    }

    @Override
    public int getItemCount() {
        if (mController.isZeroMode()) {
            // Add extra one for "pair new"
            return mController.getMediaDevices().size() + 1;
        }
        return mController.getMediaDevices().size();
    }

    void onItemClick(MediaDevice device) {
        mController.connectDevice(device);
        device.setState(MediaDeviceState.STATE_CONNECTING);
        notifyDataSetChanged();
    }

    void onItemClick(int customizedItem) {
        if (customizedItem == PAIR_NEW) {
            mController.launchBluetoothPairing();
        }
    }

    @Override
    CharSequence getItemTitle(MediaDevice device) {
        if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
                && !device.isConnected()) {
            final CharSequence deviceName = device.getName();
            // Append status to title only for the disconnected Bluetooth device.
            final SpannableString spannableTitle = new SpannableString(
                    mContext.getString(R.string.media_output_dialog_disconnected, deviceName));
            spannableTitle.setSpan(new ForegroundColorSpan(
                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorSecondary)),
                    deviceName.length(),
                    spannableTitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE);
            return spannableTitle;
        }
        return super.getItemTitle(device);
    }

    class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {

        MediaDeviceViewHolder(View view) {
            super(view);
        }

        @Override
        void onBind(MediaDevice device) {
            super.onBind(device);
            if (mController.isTransferring()) {
                if (device.getState() == MediaDeviceState.STATE_CONNECTING
                        && !mController.hasAdjustVolumeUserRestriction()) {
                    setTwoLineLayout(device, true);
                    mProgressBar.setVisibility(View.VISIBLE);
                    mSeekBar.setVisibility(View.GONE);
                    mSubTitleText.setVisibility(View.GONE);
                } else {
                    setSingleLineLayout(getItemTitle(device), false);
                }
            } else {
                // Set different layout for each device
                if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                    setTwoLineLayout(device, false);
                    mSubTitleText.setVisibility(View.VISIBLE);
                    mSeekBar.setVisibility(View.GONE);
                    mProgressBar.setVisibility(View.GONE);
                    mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
                    mFrameLayout.setOnClickListener(v -> onItemClick(device));
                } else if (!mController.hasAdjustVolumeUserRestriction()
                        && isCurrentConnected(device)) {
                    setTwoLineLayout(device, true);
                    mSeekBar.setVisibility(View.VISIBLE);
                    mProgressBar.setVisibility(View.GONE);
                    mSubTitleText.setVisibility(View.GONE);
                    initSeekbar(device);
                } else {
                    setSingleLineLayout(getItemTitle(device), false);
                    mFrameLayout.setOnClickListener(v -> onItemClick(device));
                }
            }
        }

        @Override
        void onBind(int customizedItem) {
            if (customizedItem == PAIR_NEW) {
                setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
                        false);
                final Drawable d = mContext.getDrawable(R.drawable.ic_add);
                d.setColorFilter(new PorterDuffColorFilter(
                        Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
                mTitleIcon.setImageDrawable(d);
                mFrameLayout.setOnClickListener(v -> onItemClick(PAIR_NEW));
            }
        }
    }
}
+165 −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.dialog;

import android.content.Context;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;

/**
 * Base adapter for media output dialog.
 */
public abstract class MediaOutputBaseAdapter extends
        RecyclerView.Adapter<MediaOutputBaseAdapter.MediaDeviceBaseViewHolder> {

    private static final String FONT_SELECTED_TITLE = "sans-serif-medium";
    private static final String FONT_TITLE = "sans-serif";

    final MediaOutputController mController;

    private boolean mIsDragging;

    Context mContext;
    View mHolderView;

    public MediaOutputBaseAdapter(MediaOutputController controller) {
        mController = controller;
        mIsDragging = false;
    }

    @Override
    public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
            int viewType) {
        mContext = viewGroup.getContext();
        mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
                viewGroup, false);

        return null;
    }

    CharSequence getItemTitle(MediaDevice device) {
        return device.getName();
    }

    boolean isCurrentConnected(MediaDevice device) {
        return TextUtils.equals(device.getId(),
                mController.getCurrentConnectedMediaDevice().getId());
    }

    boolean isDragging() {
        return mIsDragging;
    }

    /**
     * ViewHolder for binding device view.
     */
    abstract class MediaDeviceBaseViewHolder extends RecyclerView.ViewHolder {
        final FrameLayout mFrameLayout;
        final TextView mTitleText;
        final TextView mTwoLineTitleText;
        final TextView mSubTitleText;
        final ImageView mTitleIcon;
        final ImageView mEndIcon;
        final ProgressBar mProgressBar;
        final SeekBar mSeekBar;
        final RelativeLayout mTwoLineLayout;

        MediaDeviceBaseViewHolder(View view) {
            super(view);
            mFrameLayout = view.requireViewById(R.id.device_container);
            mTitleText = view.requireViewById(R.id.title);
            mSubTitleText = view.requireViewById(R.id.subtitle);
            mTwoLineLayout = view.requireViewById(R.id.two_line_layout);
            mTwoLineTitleText = view.requireViewById(R.id.two_line_title);
            mTitleIcon = view.requireViewById(R.id.title_icon);
            mEndIcon = view.requireViewById(R.id.end_icon);
            mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
            mSeekBar = view.requireViewById(R.id.volume_seekbar);
        }

        void onBind(MediaDevice device) {
            mTitleIcon.setImageIcon(mController.getDeviceIconCompat(device).toIcon(mContext));
        }

        void onBind(int customizedItem) { }

        void setSingleLineLayout(CharSequence title, boolean bFocused) {
            mTitleText.setVisibility(View.VISIBLE);
            mTwoLineLayout.setVisibility(View.GONE);
            mTitleText.setText(title);
            if (bFocused) {
                mTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
            } else {
                mTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
            }
        }

        void setTwoLineLayout(MediaDevice device, boolean bFocused) {
            mTitleText.setVisibility(View.GONE);
            mTwoLineLayout.setVisibility(View.VISIBLE);
            mTwoLineTitleText.setText(getItemTitle(device));
            if (bFocused) {
                mTwoLineTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE,
                        Typeface.NORMAL));
            } else {
                mTwoLineTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
            }
        }

        void initSeekbar(MediaDevice device) {
            mSeekBar.setMax(device.getMaxVolume());
            mSeekBar.setMin(0);
            if (mSeekBar.getProgress() != device.getCurrentVolume()) {
                mSeekBar.setProgress(device.getCurrentVolume());
            }
            mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    if (device == null || !fromUser) {
                        return;
                    }
                    mController.adjustVolume(device, progress);
                }

                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {
                    mIsDragging = true;
                }

                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {
                    mIsDragging = false;
                }
            });
        }
    }
}
+200 −0

File added.

Preview size limit exceeded, changes collapsed.