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

Commit d6d0bdde authored by Adam Powell's avatar Adam Powell
Browse files

Support route grouping in the MediaRouter dialog UI.

Change-Id: Idcae12cedfb7ca13950e7fa45441fba2029a9f68
parent 6a797779
Loading
Loading
Loading
Loading
+371 −22
Original line number Diff line number Diff line
@@ -19,10 +19,12 @@ package com.android.internal.app;
import com.android.internal.R;

import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.MediaRouteActionProvider;
import android.app.MediaRouteButton;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup;
@@ -34,10 +36,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

/**
 * This class implements the route chooser dialog for {@link MediaRouter}.
@@ -49,14 +55,30 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
    private static final String TAG = "MediaRouteChooserDialogFragment";
    public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment";

    private static final int[] ITEM_LAYOUTS = new int[] {
        R.layout.media_route_list_item_top_header,
        R.layout.media_route_list_item_section_header,
        R.layout.media_route_list_item
    };

    private static final int[] GROUP_ITEM_LAYOUTS = new int[] {
        R.layout.media_route_list_item_top_header,
        R.layout.media_route_list_item_checkable,
        R.layout.media_route_list_item_collapse_group
    };

    MediaRouter mRouter;
    private int mRouteTypes;

    private LayoutInflater mInflater;
    private LauncherListener mLauncherListener;
    private View.OnClickListener mExtendedSettingsListener;
    private RouteAdapter mAdapter;
    private GroupAdapter mGroupAdapter;
    private ListView mListView;

    static final RouteComparator sComparator = new RouteComparator();

    public MediaRouteChooserDialogFragment() {
        setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
    }
@@ -77,10 +99,15 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
        if (mLauncherListener != null) {
            mLauncherListener.onDetached(this);
        }
        if (mGroupAdapter != null) {
            mRouter.removeCallback(mGroupAdapter);
            mGroupAdapter = null;
        }
        if (mAdapter != null) {
            mRouter.removeCallback(mAdapter);
            mAdapter = null;
        }
        mInflater = null;
        mRouter = null;
    }

@@ -102,6 +129,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mInflater = inflater;
        final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false);
        final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);

@@ -112,7 +140,8 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {

        final ListView list = (ListView) layout.findViewById(R.id.list);
        list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        list.setAdapter(mAdapter = new RouteAdapter(inflater));
        list.setItemsCanFocus(true);
        list.setAdapter(mAdapter = new RouteAdapter());
        list.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
        list.setOnItemClickListener(mAdapter);

@@ -122,11 +151,59 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
        return layout;
    }

    private static final int[] ITEM_LAYOUTS = new int[] {
        R.layout.media_route_list_item_top_header,
        R.layout.media_route_list_item_section_header,
        R.layout.media_route_list_item
    };
    void onExpandGroup(RouteGroup info) {
        mGroupAdapter = new GroupAdapter(info);
        mRouter.addCallback(mRouteTypes, mGroupAdapter);
        mListView.setAdapter(mGroupAdapter);
        mListView.setOnItemClickListener(mGroupAdapter);
        mListView.setItemsCanFocus(false);
        mListView.clearChoices();
        mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
        mGroupAdapter.initCheckedItems();

        getDialog().setCanceledOnTouchOutside(false);
    }

    void onDoneGrouping() {
        mListView.setAdapter(mAdapter);
        mListView.setOnItemClickListener(mAdapter);
        mListView.setItemsCanFocus(true);
        mListView.clearChoices();
        mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);

        mRouter.removeCallback(mGroupAdapter);
        mGroupAdapter = null;

        getDialog().setCanceledOnTouchOutside(true);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new RouteChooserDialog(getActivity(), getTheme());
    }

    @Override
    public void onResume() {
        super.onResume();

        if (mListView != null) {
            if (mGroupAdapter != null) {
                mGroupAdapter.initCheckedItems();
            } else {
                mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
            }
        }
    }

    private static class ViewHolder {
        public TextView text1;
        public TextView text2;
        public ImageView icon;
        public ImageButton expandGroupButton;
        public RouteAdapter.ExpandGroupListener expandGroupListener;
        public int position;
    }

    private class RouteAdapter extends BaseAdapter implements MediaRouter.Callback,
            ListView.OnItemClickListener {
@@ -136,10 +213,8 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {

        private int mSelectedItemPosition;
        private final ArrayList<Object> mItems = new ArrayList<Object>();
        private final LayoutInflater mInflater;

        RouteAdapter(LayoutInflater inflater) {
            mInflater = inflater;
        RouteAdapter() {
            update();
        }

@@ -222,11 +297,29 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
            if (convertView == null) {
                convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false);
                holder = new ViewHolder();
                holder.position = position;
                holder.text1 = (TextView) convertView.findViewById(R.id.text1);
                holder.text2 = (TextView) convertView.findViewById(R.id.text2);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
                holder.expandGroupButton = (ImageButton) convertView.findViewById(
                        R.id.expand_button);
                if (holder.expandGroupButton != null) {
                    holder.expandGroupListener = new ExpandGroupListener();
                    holder.expandGroupButton.setOnClickListener(holder.expandGroupListener);
                }

                final View fview = convertView;
                final ListView list = (ListView) parent;
                final ViewHolder fholder = holder;
                convertView.setOnClickListener(new View.OnClickListener() {
                    @Override public void onClick(View v) {
                        list.performItemClick(fview, fholder.position, 0);
                    }
                });
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
                holder.position = position;
            }

            if (viewType == VIEW_ROUTE) {
@@ -248,6 +341,24 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
                holder.text2.setVisibility(View.VISIBLE);
                holder.text2.setText(status);
            }
            Drawable icon = info.getIconDrawable();
            if (icon != null) {
                // Make sure we have a fresh drawable where it doesn't matter if we mutate it
                icon = icon.getConstantState().newDrawable(getResources());
            }
            holder.icon.setImageDrawable(icon);
            holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE);

            RouteCategory cat = info.getCategory();
            boolean canGroup = false;
            if (cat.isGroupable()) {
                final RouteGroup group = (RouteGroup) info;
                canGroup = group.getRouteCount() > 1 ||
                        getItemViewType(position - 1) == VIEW_ROUTE ||
                        (position < getCount() - 1 && getItemViewType(position + 1) == VIEW_ROUTE);
            }
            holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
            holder.expandGroupListener.position = position;
        }

        void bindHeaderView(int position, ViewHolder holder) {
@@ -306,36 +417,274 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
            mRouter.selectRoute(mRouteTypes, (RouteInfo) item);
            dismiss();
        }

        class ExpandGroupListener implements View.OnClickListener {
            int position;

            @Override
            public void onClick(View v) {
                // Assumption: this is only available for the user to click if we're presenting
                // a groupable category, where every top-level route in the category is a group.
                onExpandGroup((RouteGroup) getItem(position));
            }
        }
    }

    private static class ViewHolder {
        public TextView text1;
        public TextView text2;
    private class GroupAdapter extends BaseAdapter implements MediaRouter.Callback,
            ListView.OnItemClickListener {
        private static final int VIEW_HEADER = 0;
        private static final int VIEW_ROUTE = 1;
        private static final int VIEW_DONE = 2;

        private RouteGroup mPrimary;
        private RouteCategory mCategory;
        private final ArrayList<RouteInfo> mTempList = new ArrayList<RouteInfo>();
        private final ArrayList<RouteInfo> mFlatRoutes = new ArrayList<RouteInfo>();
        private boolean mIgnoreUpdates;

        public GroupAdapter(RouteGroup primary) {
            mPrimary = primary;
            mCategory = primary.getCategory();
            update();
        }

    private class GroupAdapter extends BaseAdapter {
        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return 0;
            return mFlatRoutes.size() + 2;
        }

        @Override
        public int getViewTypeCount() {
            return 3;
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return VIEW_HEADER;
            } else if (position == getCount() - 1) {
                return VIEW_DONE;
            }
            return VIEW_ROUTE;
        }

        void update() {
            if (mIgnoreUpdates) return;
            mFlatRoutes.clear();
            mCategory.getRoutes(mTempList);

            // Unpack groups and flatten for presentation
            final int topCount = mTempList.size();
            for (int i = 0; i < topCount; i++) {
                final RouteInfo route = mTempList.get(i);
                final RouteGroup group = route.getGroup();
                if (group == route) {
                    // This is a group, unpack it.
                    final int groupCount = group.getRouteCount();
                    for (int j = 0; j < groupCount; j++) {
                        final RouteInfo innerRoute = group.getRouteAt(j);
                        mFlatRoutes.add(innerRoute);
                    }
                } else {
                    mFlatRoutes.add(route);
                }
            }
            mTempList.clear();

            // Sort by name. This will keep the route positions relatively stable even though they
            // will be repeatedly added and removed.
            Collections.sort(mFlatRoutes, sComparator);
            notifyDataSetChanged();
        }

        void initCheckedItems() {
            if (mIgnoreUpdates) return;
            mListView.clearChoices();
            int count = mFlatRoutes.size();
            for (int i = 0; i < count; i++){
                final RouteInfo route = mFlatRoutes.get(i);
                if (route.getGroup() == mPrimary) {
                    mListView.setItemChecked(i + 1, true);
                }
            }
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return null;
            if (position == 0) {
                return mCategory;
            } else if (position == getCount() - 1) {
                return null; // Done
            }
            return mFlatRoutes.get(position - 1);
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return 0;
            return position;
        }

        @Override
        public boolean areAllItemsEnabled() {
            return false;
        }

        @Override
        public boolean isEnabled(int position) {
            return position > 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // TODO Auto-generated method stub
            return null;
            final int viewType = getItemViewType(position);

            ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(GROUP_ITEM_LAYOUTS[viewType], parent, false);
                holder = new ViewHolder();
                holder.position = position;
                holder.text1 = (TextView) convertView.findViewById(R.id.text1);
                holder.text2 = (TextView) convertView.findViewById(R.id.text2);
                holder.icon = (ImageView) convertView.findViewById(R.id.icon);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
                holder.position = position;
            }

            if (viewType == VIEW_ROUTE) {
                bindItemView(position, holder);
            } else if (viewType == VIEW_HEADER) {
                bindHeaderView(position, holder);
            }

            return convertView;
        }

        void bindItemView(int position, ViewHolder holder) {
            RouteInfo info = (RouteInfo) getItem(position);
            holder.text1.setText(info.getName());
            final CharSequence status = info.getStatus();
            if (TextUtils.isEmpty(status)) {
                holder.text2.setVisibility(View.GONE);
            } else {
                holder.text2.setVisibility(View.VISIBLE);
                holder.text2.setText(status);
            }
        }

        void bindHeaderView(int position, ViewHolder holder) {
            holder.text1.setText(mCategory.getName());
        }

        @Override
        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
        }

        @Override
        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
        }

        @Override
        public void onRouteAdded(MediaRouter router, RouteInfo info) {
            update();
            initCheckedItems();
        }

        @Override
        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
            if (info == mPrimary) {
                // Can't keep grouping, clean it up.
                onDoneGrouping();
            } else {
                update();
                initCheckedItems();
            }
        }

        @Override
        public void onRouteChanged(MediaRouter router, RouteInfo info) {
            update();
        }

        @Override
        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) {
            update();
            initCheckedItems();
        }

        @Override
        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
            update();
            initCheckedItems();
        }

        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            if (getItemViewType(position) == VIEW_DONE) {
                onDoneGrouping();
                return;
            }

            final ListView lv = (ListView) parent;
            final RouteInfo route = mFlatRoutes.get(position - 1);
            final boolean checked = lv.isItemChecked(position);

            mIgnoreUpdates = true;
            RouteGroup oldGroup = route.getGroup();
            if (checked && oldGroup != mPrimary) {
                // Assumption: in a groupable category oldGroup will never be null.
                oldGroup.removeRoute(route);

                // If the group is now empty, remove the group too.
                if (oldGroup.getRouteCount() == 0) {
                    if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) {
                        // Old group was selected but is now empty. Select the group
                        // we're manipulating since that's where the last route went.
                        mRouter.selectRoute(mRouteTypes, mPrimary);
                    }
                    mRouter.removeRouteInt(oldGroup);
                }

                mPrimary.addRoute(route);
            } else if (!checked) {
                if (mPrimary.getRouteCount() > 1) {
                    mPrimary.removeRoute(route);

                    // In a groupable category this will add the route into its own new group.
                    mRouter.addRouteInt(route);
                } else {
                    // We're about to remove the last route.
                    // Don't let this happen, as it would be silly.
                    // Turn the checkmark back on again. Silly user!
                    lv.setItemChecked(position, true);
                }
            }
            mIgnoreUpdates = false;
            update();
            initCheckedItems();
        }
    }

    static class RouteComparator implements Comparator<RouteInfo> {
        @Override
        public int compare(RouteInfo lhs, RouteInfo rhs) {
            return lhs.getName().toString().compareTo(rhs.getName().toString());
        }
    }

    class RouteChooserDialog extends Dialog {
        public RouteChooserDialog(Context context, int theme) {
            super(context, theme);
        }

        @Override
        public void onBackPressed() {
            if (mGroupAdapter != null) {
                onDoneGrouping();
            } else {
                super.onBackPressed();
            }
        }
    }
}
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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.internal.view;

import com.android.internal.R;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.Checkable;
import android.widget.CheckBox;
import android.widget.LinearLayout;

public class CheckableLinearLayout extends LinearLayout implements Checkable {
    private CheckBox mCheckBox;

    public CheckableLinearLayout(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    public CheckableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mCheckBox = (CheckBox) findViewById(R.id.check);
    }

    @Override
    public void setChecked(boolean checked) {
        mCheckBox.setChecked(checked);
    }

    @Override
    public boolean isChecked() {
        return mCheckBox.isChecked();
    }

    @Override
    public void toggle() {
        mCheckBox.toggle();
    }
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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.internal.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageButton;

public class ImageButtonNoParentPress extends ImageButton {

    public ImageButtonNoParentPress(Context context) {
        super(context);
    }

    public ImageButtonNoParentPress(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ImageButtonNoParentPress(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setPressed(boolean pressed) {
        // Normally parents propagate pressed state to their children.
        // We don't want that to happen here; only press if our parent isn't.
        super.setPressed(((ViewGroup) getParent()).isPressed() ? false : pressed);
    }
}
+933 B
Loading image diff...
+984 B
Loading image diff...
Loading