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

Commit fa9e5cd3 authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov Committed by Android (Google) Code Review
Browse files

Merge changes from topic "tv-mic-indicator-refresh"

* changes:
  Make AudioRecordingDisclosureBar animations interruptable
  Update AudioRecordingDisclosureBar animations
  Clean up unused Views in AudioRecordingDisclosureBar
parents df17ab0e 1dbc5865
Loading
Loading
Loading
Loading
+0 −26
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2019 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.
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

  <corners
      android:bottomLeftRadius="8dp"
      android:topLeftRadius="8dp" />
  <solid android:color="@color/tv_audio_recording_indicator_background" />

</shape>
 No newline at end of file
+0 −26
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2019 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.
  -->

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

  <corners
      android:bottomRightRadius="8dp"
      android:topRightRadius="8dp" />
  <solid android:color="@color/tv_audio_recording_indicator_background" />

</shape>
 No newline at end of file
+9 −72
Original line number Diff line number Diff line
@@ -22,27 +22,6 @@
              android:padding="12dp">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <LinearLayout
            android:id="@+id/icon_texts_container"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <FrameLayout
                android:layout_width="wrap_content"
                android:layout_height="match_parent">

                <View
                    android:id="@+id/icon_container_bg"
                    android:layout_width="50dp"
                    android:layout_height="match_parent"
                    android:background="@drawable/tv_rect_dark_left_rounded"/>

                <FrameLayout
                    android:id="@+id/icon_mic"
        android:layout_width="34dp"
        android:layout_height="24dp"
        android:layout_gravity="center"
@@ -52,50 +31,8 @@
            android:layout_width="13dp"
            android:layout_height="13dp"
            android:layout_gravity="center"
                        android:background="@drawable/tv_ic_mic_white"/>
                </FrameLayout>

            </FrameLayout>

            <LinearLayout
                android:id="@+id/texts_container"
                android:layout_width="wrap_content"
                android:layout_height="47dp"
                android:background="@color/tv_audio_recording_indicator_background"
                android:orientation="vertical"
                android:visibility="visible">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="14dp"
                    android:layout_marginTop="10dp"
                    android:layout_marginBottom="1dp"
                    android:text="@string/mic_active"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif"
                    android:textSize="10dp"/>

                <TextView
                    android:id="@+id/text"
                    android:layout_width="wrap_content"
                    android:layout_height="14dp"
                    android:singleLine="true"
                    android:text="SomeApplication accessed your microphone"
                    android:textColor="@android:color/white"
                    android:fontFamily="sans-serif"
                    android:textSize="8dp"/>

            </LinearLayout>

        </LinearLayout>
            android:src="@drawable/tv_ic_mic_white"/>

    </FrameLayout>

    <View
        android:id="@+id/bg_end"
        android:layout_width="12dp"
        android:layout_height="47dp"
        android:background="@drawable/tv_rect_dark_right_rounded"
        android:visibility="visible"/>

</LinearLayout>
+0 −2
Original line number Diff line number Diff line
@@ -22,8 +22,6 @@
    <color name="recents_tv_dismiss_text_color">#7FEEEEEE</color>
    <color name="recents_tv_text_shadow_color">#7F000000</color>

    <!-- Background color for audio recording indicator (G800) -->
    <color name="tv_audio_recording_indicator_background">#FF3C4043</color>
    <color name="tv_audio_recording_indicator_icon_background">#CC000000</color>
    <color name="tv_audio_recording_indicator_stroke">#33FFFFFF</color>

+88 −119
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.UiThread;
@@ -36,7 +35,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.TextView;

import com.android.systemui.R;
import com.android.systemui.statusbar.tv.TvStatusBar;
@@ -83,19 +81,14 @@ public class AudioRecordingDisclosureBar implements
    private static final int STATE_SHOWN = 2;
    private static final int STATE_DISAPPEARING = 3;

    private static final int ANIMATION_DURATION = 600;
    private static final int ANIMATION_DURATION_MS = 200;

    private final Context mContext;
    private boolean mIsEnabled;

    private View mIndicatorView;
    private View mIconTextsContainer;
    private View mIconContainerBg;
    private View mIcon;
    private View mBgEnd;
    private View mTextsContainers;
    private TextView mTextView;
    private boolean mIsLtr;
    private boolean mViewAndWindowAdded;
    private ObjectAnimator mAnimator;

    @State private int mState = STATE_STOPPED;

@@ -190,7 +183,7 @@ public class AudioRecordingDisclosureBar implements
        }

        if (active) {
            showIfNotShown();
            showIfNeeded();
        } else {
            hideIndicatorIfNeeded();
        }
@@ -198,154 +191,133 @@ public class AudioRecordingDisclosureBar implements

    @UiThread
    private void hideIndicatorIfNeeded() {
        // If not STATE_APPEARING, will check whether the indicator should be hidden when the
        // indicator comes to the STATE_SHOWN.
        // If STATE_DISAPPEARING or STATE_SHOWN - nothing else for us to do here.
        if (mState != STATE_SHOWN) return;
        // If STOPPED, NOT_SHOWN or DISAPPEARING - nothing else for us to do here.
        if (mState != STATE_SHOWN && mState != STATE_APPEARING) return;

        // If is in the STATE_SHOWN and there are no active recorders - hide.
        if (!hasActiveRecorders()) {
            hide();
        if (hasActiveRecorders()) {
            return;
        }

        if (mViewAndWindowAdded) {
            mState = STATE_DISAPPEARING;
            animateDisappearance();
        } else {
            // Appearing animation has not started yet, as we were still waiting for the View to be
            // laid out.
            mState = STATE_NOT_SHOWN;
            removeIndicatorView();
        }
    }

    @UiThread
    private void showIfNotShown() {
        if (mState != STATE_NOT_SHOWN) return;
    private void showIfNeeded() {
        // If STOPPED, SHOWN or APPEARING - nothing else for us to do here.
        if (mState != STATE_NOT_SHOWN && mState != STATE_DISAPPEARING) return;

        if (DEBUG) Log.d(TAG, "Showing indicator");

        mIsLtr = mContext.getResources().getConfiguration().getLayoutDirection()
                == View.LAYOUT_DIRECTION_LTR;
        final int prevState = mState;
        mState = STATE_APPEARING;

        if (prevState == STATE_DISAPPEARING) {
            animateAppearance();
            return;
        }

        // Inflate the indicator view
        mIndicatorView = LayoutInflater.from(mContext).inflate(
                R.layout.tv_audio_recording_indicator,
                null);
        mIconTextsContainer = mIndicatorView.findViewById(R.id.icon_texts_container);
        mIconContainerBg = mIconTextsContainer.findViewById(R.id.icon_container_bg);
        mIcon = mIconTextsContainer.findViewById(R.id.icon_mic);
        mTextsContainers = mIconTextsContainer.findViewById(R.id.texts_container);
        mTextView = mTextsContainers.findViewById(R.id.text);
        mBgEnd = mIndicatorView.findViewById(R.id.bg_end);

        mTextsContainers.setVisibility(View.GONE);
        mIconContainerBg.setVisibility(View.GONE);
        mTextView.setVisibility(View.GONE);
        mBgEnd.setVisibility(View.GONE);
        mTextsContainers = null;
        mIconContainerBg = null;
        mTextView = null;
        mBgEnd = null;

        // Initially change the visibility to INVISIBLE, wait until and receives the size and
        // then animate it moving from "off" the screen correctly
        mIndicatorView.setVisibility(View.INVISIBLE);
                R.layout.tv_audio_recording_indicator, null);

        // 1. Set alpha to 0.
        // 2. Wait until the window is shown and the view is laid out.
        // 3. Start a "fade in" (alpha) animation.
        mIndicatorView.setAlpha(0f);
        mIndicatorView
                .getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                if (mState == STATE_STOPPED) {
                                    return;
                                }
                                // State could have changed to NOT_SHOWN (if all the recorders are
                                // already gone) to STOPPED (if the indicator was disabled)
                                if (mState != STATE_APPEARING) return;

                                mViewAndWindowAdded = true;
                                // Remove the observer
                                mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
                                        this);

                                // Now that the width of the indicator has been assigned, we can
                                // move it in from off the screen.
                                final int initialOffset =
                                        (mIsLtr ? 1 : -1) * mIndicatorView.getWidth();
                                final AnimatorSet set = new AnimatorSet();
                                set.setDuration(ANIMATION_DURATION);
                                set.playTogether(
                                        ObjectAnimator.ofFloat(mIndicatorView,
                                                View.TRANSLATION_X, initialOffset, 0),
                                        ObjectAnimator.ofFloat(mIndicatorView, View.ALPHA, 0f,
                                                1f));
                                set.addListener(
                                        new AnimatorListenerAdapter() {
                                            @Override
                                            public void onAnimationStart(Animator animation,
                                                    boolean isReverse) {
                                                if (mState == STATE_STOPPED) return;

                                                // Indicator is INVISIBLE at the moment, change it.
                                                mIndicatorView.setVisibility(View.VISIBLE);
                                            }

                                            @Override
                                            public void onAnimationEnd(Animator animation) {
                                                onAppeared();
                                            }
                                        });
                                set.start();
                                animateAppearance();
                            }
                        });

        final boolean isLtr = mContext.getResources().getConfiguration().getLayoutDirection()
                == View.LAYOUT_DIRECTION_LTR;
        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WRAP_CONTENT,
                WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
        layoutParams.gravity = Gravity.TOP | (mIsLtr ? Gravity.RIGHT : Gravity.LEFT);
        layoutParams.gravity = Gravity.TOP | (isLtr ? Gravity.RIGHT : Gravity.LEFT);
        layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
        layoutParams.packageName = mContext.getPackageName();
        final WindowManager windowManager = (WindowManager) mContext.getSystemService(
                Context.WINDOW_SERVICE);
        windowManager.addView(mIndicatorView, layoutParams);

        mState = STATE_APPEARING;
    }

    @UiThread
    private void hide() {
        if (DEBUG) Log.d(TAG, "Hide indicator");

        final int targetOffset = (mIsLtr ? 1 : -1) * (mIndicatorView.getWidth()
                - (int) mIconTextsContainer.getTranslationX());
        final AnimatorSet set = new AnimatorSet();
        set.playTogether(
                ObjectAnimator.ofFloat(mIndicatorView, View.TRANSLATION_X, targetOffset),
                ObjectAnimator.ofFloat(mIcon, View.ALPHA, 0f));
        set.setDuration(ANIMATION_DURATION);
        set.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        onHidden();
                    }
                });
        set.start();

        mState = STATE_DISAPPEARING;
    private void animateAppearance() {
        animateAlphaTo(1f);
    }

    private void animateDisappearance() {
        animateAlphaTo(0f);
    }

    @UiThread
    private void onAppeared() {
        if (mState == STATE_STOPPED) return;
    private void animateAlphaTo(final float endValue) {
        if (mAnimator == null) {
            if (DEBUG) Log.d(TAG, "set up animator");

        mState = STATE_SHOWN;
            mAnimator = new ObjectAnimator();
            mAnimator.setTarget(mIndicatorView);
            mAnimator.setProperty(View.ALPHA);
            mAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation, boolean isReverse) {
                    if (DEBUG) Log.d(TAG, "onAnimationStart");
                }

        hideIndicatorIfNeeded();
                @Override
                public void onAnimationCancel(Animator animation) {
                    if (DEBUG) Log.d(TAG, "onAnimationCancel");
                }

    @UiThread
    private void onHidden() {
        if (mState == STATE_STOPPED) return;
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (DEBUG) Log.d(TAG, "onAnimationEnd");

                    if (mState == STATE_APPEARING) {
                        mState = STATE_SHOWN;
                    } else if (mState == STATE_DISAPPEARING) {
                        removeIndicatorView();
                        mState = STATE_NOT_SHOWN;

        if (hasActiveRecorders()) {
            // Got new recorders, show again.
            showIfNotShown();
                    }
                }
            });
        } else if (mAnimator.isRunning()) {
            if (DEBUG) Log.d(TAG, "cancel running animation");
            mAnimator.cancel();
        }

        final float currentValue = mIndicatorView.getAlpha();
        if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);

        mAnimator.setDuration((int) (Math.abs(currentValue - endValue) * ANIMATION_DURATION_MS));
        mAnimator.setFloatValues(endValue);
        mAnimator.start();
    }

    private boolean hasActiveRecorders() {
        for (int index = mAudioActivityObservers.length - 1; index >= 0; index--) {
@@ -363,12 +335,9 @@ public class AudioRecordingDisclosureBar implements
        windowManager.removeView(mIndicatorView);

        mIndicatorView = null;
        mIconTextsContainer = null;
        mIconContainerBg = null;
        mIcon = null;
        mTextsContainers = null;
        mTextView = null;
        mBgEnd = null;
        mAnimator = null;

        mViewAndWindowAdded = false;
    }

    private static List<String> splitByComma(String string) {