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

Commit d86a7f4c authored by Adam Powell's avatar Adam Powell Committed by Android Git Automerger
Browse files

am 1901a9e1: am 40ce70c9: am 11af1878: Merge "Add animation and positional...

am 1901a9e1: am 40ce70c9: am 11af1878: Merge "Add animation and positional stability to intent chooser UI" into mnc-dr-dev

* commit '1901a9e1':
  Add animation and positional stability to intent chooser UI
parents a7036ce1 1901a9e1
Loading
Loading
Loading
Loading
+238 −35
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.internal.app;

import android.animation.ObjectAnimator;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
@@ -29,6 +31,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -46,13 +49,18 @@ import android.service.chooser.ChooserTargetService;
import android.service.chooser.IChooserTargetResult;
import android.service.chooser.IChooserTargetService;
import android.text.TextUtils;
import android.util.FloatProperty;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ListView;
@@ -80,6 +88,7 @@ public class ChooserActivity extends ResolverActivity {
    private Intent mReferrerFillInIntent;

    private ChooserListAdapter mChooserListAdapter;
    private ChooserRowAdapter mChooserRowAdapter;

    private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();

@@ -253,7 +262,9 @@ public class ChooserActivity extends ResolverActivity {
            boolean alwaysUseOption) {
        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
        mChooserListAdapter = (ChooserListAdapter) adapter;
        adapterView.setAdapter(new ChooserRowAdapter(mChooserListAdapter));
        mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
        mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
        adapterView.setAdapter(mChooserRowAdapter);
        if (listView != null) {
            listView.setItemsCanFocus(true);
        }
@@ -913,19 +924,103 @@ public class ChooserActivity extends ResolverActivity {
        }
    }

    static class RowScale {
        private static final int DURATION = 400;

        float mScale;
        ChooserRowAdapter mAdapter;
        private final ObjectAnimator mAnimator;

        public static final FloatProperty<RowScale> PROPERTY =
                new FloatProperty<RowScale>("scale") {
            @Override
            public void setValue(RowScale object, float value) {
                object.mScale = value;
                object.mAdapter.notifyDataSetChanged();
            }

            @Override
            public Float get(RowScale object) {
                return object.mScale;
            }
        };

        public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
            mAdapter = adapter;
            mScale = from;
            if (from == to) {
                mAnimator = null;
                return;
            }

            mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
        }

        public RowScale setInterpolator(Interpolator interpolator) {
            if (mAnimator != null) {
                mAnimator.setInterpolator(interpolator);
            }
            return this;
        }

        public float get() {
            return mScale;
        }

        public void startAnimation() {
            if (mAnimator != null) {
                mAnimator.start();
            }
        }

        public void cancelAnimation() {
            if (mAnimator != null) {
                mAnimator.cancel();
            }
        }
    }

    class ChooserRowAdapter extends BaseAdapter {
        private ChooserListAdapter mChooserListAdapter;
        private final LayoutInflater mLayoutInflater;
        private final int mColumnCount = 4;
        private RowScale[] mServiceTargetScale;
        private final Interpolator mInterpolator;

        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
            mChooserListAdapter = wrappedAdapter;
            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);

            mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
                    android.R.interpolator.decelerate_quint);

            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
                @Override
                public void onChanged() {
                    super.onChanged();
                    final int rcount = getServiceTargetRowCount();
                    if (mServiceTargetScale == null
                            || mServiceTargetScale.length != rcount) {
                        RowScale[] old = mServiceTargetScale;
                        int oldRCount = old != null ? old.length : 0;
                        mServiceTargetScale = new RowScale[rcount];
                        if (old != null && rcount > 0) {
                            System.arraycopy(old, 0, mServiceTargetScale, 0,
                                    Math.min(old.length, rcount));
                        }

                        for (int i = rcount; i < oldRCount; i++) {
                            old[i].cancelAnimation();
                        }

                        for (int i = oldRCount; i < rcount; i++) {
                            final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
                                    .setInterpolator(mInterpolator);
                            mServiceTargetScale[i] = rs;
                            rs.startAnimation();
                        }
                    }

                    notifyDataSetChanged();
                }

@@ -933,19 +1028,43 @@ public class ChooserActivity extends ResolverActivity {
                public void onInvalidated() {
                    super.onInvalidated();
                    notifyDataSetInvalidated();
                    if (mServiceTargetScale != null) {
                        for (RowScale rs : mServiceTargetScale) {
                            rs.cancelAnimation();
                        }
                    }
                }
            });
        }

        private float getRowScale(int rowPosition) {
            final int start = getCallerTargetRowCount();
            final int end = start + getServiceTargetRowCount();
            if (rowPosition >= start && rowPosition < end) {
                return mServiceTargetScale[rowPosition - start].get();
            }
            return 1.f;
        }

        @Override
        public int getCount() {
            return (int) (
                    Math.ceil((float) mChooserListAdapter.getCallerTargetCount() / mColumnCount)
                    + Math.ceil((float) mChooserListAdapter.getServiceTargetCount() / mColumnCount)
                    getCallerTargetRowCount()
                    + getServiceTargetRowCount()
                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
            );
        }

        public int getCallerTargetRowCount() {
            return (int) Math.ceil(
                    (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
        }

        public int getServiceTargetRowCount() {
            return (int) Math.ceil(
                    (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
        }

        @Override
        public Object getItem(int position) {
            // We have nothing useful to return here.
@@ -959,33 +1078,67 @@ public class ChooserActivity extends ResolverActivity {

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final View[] holder;
            final RowViewHolder holder;
            if (convertView == null) {
                holder = createViewHolder(parent);
            } else {
                holder = (View[]) convertView.getTag();
                holder = (RowViewHolder) convertView.getTag();
            }
            bindViewHolder(position, holder);

            // We keep the actual list item view as the last item in the holder array
            return holder[mColumnCount];
            return holder.row;
        }

        View[] createViewHolder(ViewGroup parent) {
            final View[] holder = new View[mColumnCount + 1];

        RowViewHolder createViewHolder(ViewGroup parent) {
            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
                    parent, false);
            final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

            for (int i = 0; i < mColumnCount; i++) {
                holder[i] = mChooserListAdapter.createView(row);
                row.addView(holder[i]);
                final View v = mChooserListAdapter.createView(row);
                v.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        startSelected(holder.itemIndex, false, true);
                    }
                });
                v.setOnLongClickListener(new OnLongClickListener() {
                    @Override
                    public boolean onLongClick(View v) {
                        showAppDetails(
                                mChooserListAdapter.resolveInfoForPosition(holder.itemIndex, true));
                        return true;
                    }
                });
                row.addView(v);
                holder.cells[i] = v;

                // Force height to be a given so we don't have visual disruption during scaling.
                LayoutParams lp = v.getLayoutParams();
                v.measure(spec, spec);
                if (lp == null) {
                    lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
                    row.setLayoutParams(lp);
                } else {
                    lp.height = v.getMeasuredHeight();
                }
            }

            // Pre-measure so we can scale later.
            holder.measure();
            LayoutParams lp = row.getLayoutParams();
            if (lp == null) {
                lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
                row.setLayoutParams(lp);
            } else {
                lp.height = holder.measuredRowHeight;
            }
            row.setTag(holder);
            holder[mColumnCount] = row;
            return holder;
        }

        void bindViewHolder(int rowPosition, View[] holder) {
        void bindViewHolder(int rowPosition, RowViewHolder holder) {
            final int start = getFirstRowPosition(rowPosition);
            final int startType = mChooserListAdapter.getPositionTargetType(start);

@@ -994,34 +1147,26 @@ public class ChooserActivity extends ResolverActivity {
                end--;
            }

            final ViewGroup row = (ViewGroup) holder[mColumnCount];

            if (startType == ChooserListAdapter.TARGET_SERVICE) {
                row.setBackgroundColor(getColor(R.color.chooser_service_row_background_color));
                holder.row.setBackgroundColor(
                        getColor(R.color.chooser_service_row_background_color));
            } else {
                row.setBackground(null);
                holder.row.setBackgroundColor(Color.TRANSPARENT);
            }

            final int oldHeight = holder.row.getLayoutParams().height;
            holder.row.getLayoutParams().height = Math.max(1,
                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
            if (holder.row.getLayoutParams().height != oldHeight) {
                holder.row.requestLayout();
            }

            for (int i = 0; i < mColumnCount; i++) {
                final View v = holder[i];
                final View v = holder.cells[i];
                if (start + i <= end) {
                    v.setVisibility(View.VISIBLE);
                    final int itemIndex = start + i;
                    mChooserListAdapter.bindView(itemIndex, v);
                    v.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            startSelected(itemIndex, false, true);
                        }
                    });
                    v.setOnLongClickListener(new OnLongClickListener() {
                        @Override
                        public boolean onLongClick(View v) {
                            showAppDetails(
                                    mChooserListAdapter.resolveInfoForPosition(itemIndex, true));
                            return true;
                        }
                    });
                    holder.itemIndex = start + i;
                    mChooserListAdapter.bindView(holder.itemIndex, v);
                } else {
                    v.setVisibility(View.GONE);
                }
@@ -1048,6 +1193,24 @@ public class ChooserActivity extends ResolverActivity {
        }
    }

    static class RowViewHolder {
        final View[] cells;
        final ViewGroup row;
        int measuredRowHeight;
        int itemIndex;

        public RowViewHolder(ViewGroup row, int cellCount) {
            this.row = row;
            this.cells = new View[cellCount];
        }

        public void measure() {
            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
            row.measure(spec, spec);
            measuredRowHeight = row.getMeasuredHeight();
        }
    }

    static class ChooserTargetServiceConnection implements ServiceConnection {
        private final DisplayResolveInfo mOriginalTarget;
        private ComponentName mConnectedComponent;
@@ -1199,4 +1362,44 @@ public class ChooserActivity extends ResolverActivity {
            mSelectedTarget = null;
        }
    }

    class OffsetDataSetObserver extends DataSetObserver {
        private final AbsListView mListView;
        private int mCachedViewType = -1;
        private View mCachedView;

        public OffsetDataSetObserver(AbsListView listView) {
            mListView = listView;
        }

        @Override
        public void onChanged() {
            if (mResolverDrawerLayout == null) {
                return;
            }

            final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
            int offset = 0;
            for (int i = 0; i < chooserTargetRows; i++)  {
                final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
                final int vt = mChooserRowAdapter.getItemViewType(pos);
                if (vt != mCachedViewType) {
                    mCachedView = null;
                }
                final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
                int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;

                offset += (int) (height * mChooserRowAdapter.getRowScale(pos) * chooserTargetRows);

                if (vt >= 0) {
                    mCachedViewType = vt;
                    mCachedView = v;
                } else {
                    mCachedViewType = -1;
                }
            }

            mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
        }
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -103,6 +103,8 @@ public class ResolverActivity extends Activity {
    private ResolverComparator mResolverComparator;
    private PickTargetOptionRequest mPickOptionRequest;

    protected ResolverDrawerLayout mResolverDrawerLayout;

    private boolean mRegistered;
    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
        @Override public void onSomePackagesChanged() {
@@ -253,6 +255,7 @@ public class ResolverActivity extends Activity {
            if (isVoiceInteraction()) {
                rdl.setCollapsed(false);
            }
            mResolverDrawerLayout = rdl;
        }

        if (title == null) {
@@ -1570,7 +1573,10 @@ public class ResolverActivity extends Activity {

        private void onBindView(View view, TargetInfo info) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final CharSequence label = info.getDisplayLabel();
            if (!TextUtils.equals(holder.text.getText(), label)) {
                holder.text.setText(info.getDisplayLabel());
            }
            if (showsExtendedInfo(info)) {
                holder.text2.setVisibility(View.VISIBLE);
                holder.text2.setText(info.getExtendedInfo());
+90 −21
Original line number Diff line number Diff line
@@ -69,6 +69,12 @@ public class ResolverDrawerLayout extends ViewGroup {
    private int mCollapsibleHeight;
    private int mUncollapsibleHeight;

    /**
     * The height in pixels of reserved space added to the top of the collapsed UI;
     * e.g. chooser targets
     */
    private int mCollapsibleHeightReserved;

    private int mTopOffset;

    private boolean mIsDragging;
@@ -153,12 +159,62 @@ public class ResolverDrawerLayout extends ViewGroup {
        }
    }

    public void setCollapsibleHeightReserved(int heightPixels) {
        final int oldReserved = mCollapsibleHeightReserved;
        mCollapsibleHeightReserved = heightPixels;

        final int dReserved = mCollapsibleHeightReserved - oldReserved;
        if (dReserved != 0 && mIsDragging) {
            mLastTouchY -= dReserved;
        }

        final int oldCollapsibleHeight = mCollapsibleHeight;
        mCollapsibleHeight = Math.max(mCollapsibleHeight, getMaxCollapsedHeight());

        if (updateCollapseOffset(oldCollapsibleHeight, !isDragging())) {
            return;
        }

        invalidate();
    }

    private boolean isMoving() {
        return mIsDragging || !mScroller.isFinished();
    }

    private boolean isDragging() {
        return mIsDragging || getNestedScrollAxes() == SCROLL_AXIS_VERTICAL;
    }

    private boolean updateCollapseOffset(int oldCollapsibleHeight, boolean remainClosed) {
        if (oldCollapsibleHeight == mCollapsibleHeight) {
            return false;
        }

        if (isLaidOut()) {
            final boolean isCollapsedOld = mCollapseOffset != 0;
            if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
                    && mCollapseOffset == oldCollapsibleHeight)) {
                // Stay closed even at the new height.
                mCollapseOffset = mCollapsibleHeight;
            } else {
                mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
            }
            final boolean isCollapsedNew = mCollapseOffset != 0;
            if (isCollapsedOld != isCollapsedNew) {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
            }
        } else {
            // Start out collapsed at first unless we restored state for otherwise
            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
        }
        return true;
    }

    private int getMaxCollapsedHeight() {
        return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight;
        return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight)
                + mCollapsibleHeightReserved;
    }

    public void setOnDismissedListener(OnDismissedListener listener) {
@@ -676,7 +732,7 @@ public class ResolverDrawerLayout extends ViewGroup {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp.alwaysShow && child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
                heightUsed += getHeightUsed(child);
            }
        }

@@ -688,7 +744,7 @@ public class ResolverDrawerLayout extends ViewGroup {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (!lp.alwaysShow && child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed);
                heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin;
                heightUsed += getHeightUsed(child);
            }
        }

@@ -697,28 +753,41 @@ public class ResolverDrawerLayout extends ViewGroup {
                heightUsed - alwaysShowHeight - getMaxCollapsedHeight());
        mUncollapsibleHeight = heightUsed - mCollapsibleHeight;

        if (isLaidOut()) {
            final boolean isCollapsedOld = mCollapseOffset != 0;
            if (oldCollapsibleHeight < mCollapsibleHeight
                    && mCollapseOffset == oldCollapsibleHeight) {
                // Stay closed even at the new height.
                mCollapseOffset = mCollapsibleHeight;
            } else {
                mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
        updateCollapseOffset(oldCollapsibleHeight, !isDragging());

        mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;

        setMeasuredDimension(sourceWidth, heightSize);
    }
            final boolean isCollapsedNew = mCollapseOffset != 0;
            if (isCollapsedOld != isCollapsedNew) {
                notifyViewAccessibilityStateChangedIfNeeded(
                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);

    private int getHeightUsed(View child) {
        // This method exists because we're taking a fast path at measuring ListViews that
        // lets us get away with not doing the more expensive wrap_content measurement which
        // imposes double child view measurement costs. If we're looking at a ListView, we can
        // check against the lowest child view plus padding and margin instead of the actual
        // measured height of the ListView. This lets the ListView hang off the edge when
        // all of the content would fit on-screen.

        int heightUsed = child.getMeasuredHeight();
        if (child instanceof AbsListView) {
            final AbsListView lv = (AbsListView) child;
            final int lvPaddingBottom = lv.getPaddingBottom();

            int lowest = 0;
            for (int i = 0, N = lv.getChildCount(); i < N; i++) {
                final int bottom = lv.getChildAt(i).getBottom() + lvPaddingBottom;
                if (bottom > lowest) {
                    lowest = bottom;
                }
        } else {
            // Start out collapsed at first unless we restored state for otherwise
            mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
            }

        mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset;
            if (lowest < heightUsed) {
                heightUsed = lowest;
            }
        }

        setMeasuredDimension(sourceWidth, heightSize);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        return lp.topMargin + heightUsed + lp.bottomMargin;
    }

    @Override
+1 −1
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@

    <ListView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/resolver_list"
            android:clipToPadding="false"
            android:scrollbarStyle="outsideOverlay"
+2 −1
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@
              android:minLines="2"
              android:maxLines="2"
              android:gravity="top|center_horizontal"
              android:ellipsize="marquee" />
              android:ellipsize="marquee"
              android:visibility="gone" />
</LinearLayout>