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

Commit ebf69036 authored by Winson Chung's avatar Winson Chung
Browse files

Move the overflow ui to the bubble window

- An activity always runs on the main looper of the process
  and this simplifies threading since all bubble data will
  run on the shell thread
- The BubbleExpandedView will not initialize the task view
  when mIsOverflow is set.  We still snapshot the overflow
  view to keep the existing selection animation logic the
  same between overflow/task bubbles

Bug: 161979899
Test: atest WMShellUnitTests
Test: atest SytemUITests

Change-Id: If3632a3030c58c9ec334afcb06aac4c51e28938b
parent 7b956dd6
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
  ~ limitations under the License
  -->

<LinearLayout
<com.android.wm.shell.bubbles.BubbleOverflowContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bubble_overflow_container"
    android:layout_width="match_parent"
@@ -34,8 +34,8 @@

    <LinearLayout
        android:id="@+id/bubble_overflow_empty_state"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/bubble_overflow_empty_state_padding"
        android:paddingRight="@dimen/bubble_overflow_empty_state_padding"
        android:orientation="vertical"
@@ -69,4 +69,4 @@
            android:paddingBottom="@dimen/bubble_empty_overflow_subtitle_padding"
            android:gravity="center"/>
    </LinearLayout>
</LinearLayout>
</com.android.wm.shell.bubbles.BubbleOverflowContainerView>
+53 −32
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;

import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -37,14 +36,16 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
@@ -77,6 +78,7 @@ public class BubbleExpandedView extends LinearLayout {

    private AlphaOptimizedButton mSettingsIcon;
    private TaskView mTaskView;
    private BubbleOverflowContainerView mOverflowView;

    private int mTaskId = INVALID_TASK_ID;

@@ -125,6 +127,7 @@ public class BubbleExpandedView extends LinearLayout {
            if (mDestroyed || mInitialized) {
                return;
            }

            // Custom options so there is no activity transition animation
            ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
                    0 /* enterResId */, 0 /* exitResId */);
@@ -307,15 +310,25 @@ public class BubbleExpandedView extends LinearLayout {
     * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need
     * to be called after view inflate.
     */
    void initialize(BubbleController controller, BubbleStackView stackView) {
    void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) {
        mController = controller;
        mStackView = stackView;
        mIsOverflow = isOverflow;
        mPositioner = mController.getPositioner();

        if (mIsOverflow) {
            mOverflowView = (BubbleOverflowContainerView) LayoutInflater.from(getContext()).inflate(
                    R.layout.bubble_overflow_container, null /* root */);
            mOverflowView.setBubbleController(mController);
            mExpandedViewContainer.addView(mOverflowView);
            bringChildToFront(mOverflowView);
            mSettingsIcon.setVisibility(GONE);
        } else {
            mTaskView = new TaskView(mContext, mController.getTaskOrganizer());
            mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
            mExpandedViewContainer.addView(mTaskView);
            bringChildToFront(mTaskView);
        mTaskView.setListener(mContext.getMainExecutor(), mTaskViewListener);
        mPositioner = mController.getPositioner();
        }
    }

    void updateDimensions() {
@@ -390,6 +403,17 @@ public class BubbleExpandedView extends LinearLayout {
    /** Return a GraphicBuffer with the contents of the task view surface. */
    @Nullable
    SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
        if (mIsOverflow) {
            // For now, just snapshot the view and return it as a hw buffer so that the animation
            // code for both the tasks and overflow can be the same
            Picture p = new Picture();
            mOverflowView.draw(
                    p.beginRecording(mOverflowView.getWidth(), mOverflowView.getHeight()));
            p.endRecording();
            Bitmap snapshot = Bitmap.createBitmap(p);
            return new SurfaceControl.ScreenshotHardwareBuffer(snapshot.getHardwareBuffer(),
                    snapshot.getColorSpace(), false /* containsSecureLayers */);
        }
        if (mTaskView == null || mTaskView.getSurfaceControl() == null) {
            return null;
        }
@@ -400,6 +424,11 @@ public class BubbleExpandedView extends LinearLayout {
    }

    int[] getTaskViewLocationOnScreen() {
        if (mIsOverflow) {
            // This is only used for animating away the surface when switching bubbles, just use the
            // view location on screen for now to allow us to use the same animation code with tasks
            return mOverflowView.getLocationOnScreen();
        }
        if (mTaskView != null) {
            return mTaskView.getLocationOnScreen();
        } else {
@@ -450,10 +479,7 @@ public class BubbleExpandedView extends LinearLayout {
        final float alpha = visibility ? 1f : 0f;

        mPointerView.setAlpha(alpha);
        if (mTaskView == null) {
            return;
        }
        if (alpha != mTaskView.getAlpha()) {
        if (mTaskView != null) {
            mTaskView.setAlpha(alpha);
        }
    }
@@ -467,19 +493,6 @@ public class BubbleExpandedView extends LinearLayout {
        return mTaskId;
    }

    public void setOverflow(boolean overflow) {
        mIsOverflow = overflow;

        Intent target = new Intent(mContext, BubbleOverflowActivity.class);
        target.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
        Bundle extras = new Bundle();
        extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mController));
        target.putExtras(extras);
        mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
                target, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        mSettingsIcon.setVisibility(GONE);
    }

    /**
     * Sets the bubble used to populate this view.
     */
@@ -511,7 +524,8 @@ public class BubbleExpandedView extends LinearLayout {

            if (isNew) {
                mPendingIntent = mBubble.getBubbleIntent();
                if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
                if ((mPendingIntent != null || mBubble.hasMetadataShortcutId())
                        && mTaskView != null) {
                    setContentVisibility(false);
                    mTaskView.setVisibility(VISIBLE);
                }
@@ -552,13 +566,19 @@ public class BubbleExpandedView extends LinearLayout {
            desiredHeight = Math.max(desiredHeight, mMinHeight);
            float height = Math.min(desiredHeight, getMaxExpandedHeight());
            height = Math.max(height, mMinHeight);
            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
            FrameLayout.LayoutParams lp = mIsOverflow
                    ? (FrameLayout.LayoutParams) mOverflowView.getLayoutParams()
                    : (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
            mNeedsNewHeight = lp.height != height;
            if (!mImeVisible) {
                // If the ime is visible... don't adjust the height because that will cause
                // a configuration change and the ime will be lost.
                lp.height = (int) height;
                if (mIsOverflow) {
                    mOverflowView.setLayoutParams(lp);
                } else {
                    mTaskView.setLayoutParams(lp);
                }
                mNeedsNewHeight = false;
            }
            if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -602,6 +622,9 @@ public class BubbleExpandedView extends LinearLayout {
            updateHeight();
            mTaskView.onLocationChanged();
        }
        if (mIsOverflow) {
            mOverflowView.show();
        }
    }

    /**
@@ -637,8 +660,8 @@ public class BubbleExpandedView extends LinearLayout {

    /**
     * Cleans up anything related to the task and TaskView. If this view should be reused after this
     * method is called, then {@link #initialize(BubbleController, BubbleStackView)} must be invoked
     * first.
     * method is called, then {@link #initialize(BubbleController, BubbleStackView, boolean)} must
     * be invoked first.
     */
    public void cleanUpExpandedState() {
        if (DEBUG_BUBBLE_EXPANDED_VIEW) {
@@ -653,8 +676,6 @@ public class BubbleExpandedView extends LinearLayout {
        }
        if (mTaskView != null) {
            mTaskView.release();
        }
        if (mTaskView != null) {
            removeView(mTaskView);
            mTaskView = null;
        }
+1 −2
Original line number Diff line number Diff line
@@ -65,8 +65,7 @@ class BubbleOverflow(

    /** Call before use and again if cleanUpExpandedState was called.  */
    fun initialize(controller: BubbleController) {
        getExpandedView()?.initialize(controller, controller.stackView)
        getExpandedView()?.setOverflow(true)
        getExpandedView()?.initialize(controller, controller.stackView, true /* isOverflow */)
    }

    fun cleanUpExpandedState() {
+49 −52
Original line number Diff line number Diff line
@@ -29,16 +29,21 @@ import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.os.IBinder;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

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

@@ -50,10 +55,9 @@ import java.util.List;
import java.util.function.Consumer;

/**
 * Activity for showing aged out bubbles.
 * Must be public to be accessible to androidx...AppComponentFactory
 * Container view for showing aged out bubbles.
 */
public class BubbleOverflowActivity extends Activity {
public class BubbleOverflowContainerView extends LinearLayout {
    static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";

    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
@@ -95,37 +99,55 @@ public class BubbleOverflowActivity extends Activity {
        }
    }

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

    public BubbleOverflowContainerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public BubbleOverflowContainerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public BubbleOverflowContainerView(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public void setBubbleController(BubbleController controller) {
        mController = controller;
    }

    public void show() {
        setVisibility(View.VISIBLE);
        updateOverflow();
    }

    public void hide() {
        setVisibility(View.INVISIBLE);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.bubble_overflow_activity);
    protected void onFinishInflate() {
        super.onFinishInflate();

        mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
        mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
        mEmptyStateTitle = findViewById(R.id.bubble_overflow_empty_title);
        mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
        mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);

        Intent intent = getIntent();
        if (intent != null && intent.getExtras() != null) {
            IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
            if (binder instanceof ObjectWrapper) {
                mController = ((ObjectWrapper<BubbleController>) binder).get();
                updateOverflow();
            }
        } else {
            Log.w(TAG, "Bubble overflow activity created without bubble controller!");
        }
    }

    void updateOverflow() {
        Resources res = getResources();
        final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
        mRecyclerView.setLayoutManager(
                new NoScrollGridLayoutManager(getApplicationContext(), columns));
                new NoScrollGridLayoutManager(getContext(), columns));

        DisplayMetrics displayMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        getContext().getDisplay().getMetrics(displayMetrics);

        final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
        final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
@@ -137,7 +159,7 @@ public class BubbleOverflowActivity extends Activity {
                - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
        final int viewHeight = recyclerViewHeight / rows;

        mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
        mAdapter = new BubbleOverflowAdapter(getContext(), mOverflowBubbles,
                mController::promoteBubbleFromOverflow,
                mController.getPositioner(),
                viewWidth, viewHeight);
@@ -146,6 +168,11 @@ public class BubbleOverflowActivity extends Activity {
        mOverflowBubbles.clear();
        mOverflowBubbles.addAll(mController.getOverflowBubbles());
        mAdapter.notifyDataSetChanged();

        // Currently BubbleExpandedView.mExpandedViewContainer is WRAP_CONTENT so use the same
        // width we would use for the recycler view
        LayoutParams lp = (LayoutParams) mEmptyState.getLayoutParams();
        lp.width = recyclerViewWidth;
        updateEmptyStateVisibility();

        mController.setOverflowListener(mDataListener);
@@ -172,12 +199,12 @@ public class BubbleOverflowActivity extends Activity {
                ? res.getDrawable(R.drawable.bubble_ic_empty_overflow_dark)
                : res.getDrawable(R.drawable.bubble_ic_empty_overflow_light));

        findViewById(android.R.id.content)
        findViewById(R.id.bubble_overflow_container)
                .setBackgroundColor(isNightMode
                        ? res.getColor(R.color.bubbles_dark)
                        : res.getColor(R.color.bubbles_light));

        final TypedArray typedArray = getApplicationContext().obtainStyledAttributes(
        final TypedArray typedArray = getContext().obtainStyledAttributes(
                new int[]{android.R.attr.colorBackgroundFloating,
                        android.R.attr.textColorSecondary});
        int bgColor = typedArray.getColor(0, isNightMode ? Color.BLACK : Color.WHITE);
@@ -222,36 +249,6 @@ public class BubbleOverflowActivity extends Activity {
            }
        }
    };

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onRestart() {
        super.onRestart();
    }

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

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    public void onDestroy() {
        super.onDestroy();
    }
}

class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
+1 −1
Original line number Diff line number Diff line
@@ -138,7 +138,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask

                info.expandedView = (BubbleExpandedView) inflater.inflate(
                        R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
                info.expandedView.initialize(controller, stackView);
                info.expandedView.initialize(controller, stackView, false /* isOverflow */);
            }

            if (b.getShortcutInfo() != null) {
Loading