Merge "Add shrinking animation to the tv privacy chip." into sc-dev am: f137ba9818
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14879258 Change-Id: Id631def3c9a4ff55e5c371a66df78b06f3d0b806
This commit is contained in:
committed by
Automerger Merge Worker
commit
75157237a1
41
packages/SystemUI/res/anim/tv_privacy_chip_collapse.xml
Normal file
41
packages/SystemUI/res/anim/tv_privacy_chip_collapse.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:ordering="together"
|
||||||
|
android:interpolator="@interpolator/tv_privacy_chip_collapse_interpolator"
|
||||||
|
android:duration="@integer/privacy_chip_animation_millis">
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="height"
|
||||||
|
android:valueTo="@dimen/privacy_chip_dot_size"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="marginEnd"
|
||||||
|
android:valueTo="@dimen/privacy_chip_dot_margin_horizontal"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="radius"
|
||||||
|
android:valueTo="@dimen/privacy_chip_dot_radius"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="dotAlpha"
|
||||||
|
android:valueTo="255"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="bgAlpha"
|
||||||
|
android:valueTo="255"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
</set>
|
||||||
41
packages/SystemUI/res/anim/tv_privacy_chip_expand.xml
Normal file
41
packages/SystemUI/res/anim/tv_privacy_chip_expand.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:ordering="together"
|
||||||
|
android:interpolator="@interpolator/tv_privacy_chip_expand_interpolator"
|
||||||
|
android:duration="@integer/privacy_chip_animation_millis">
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="height"
|
||||||
|
android:valueTo="@dimen/privacy_chip_height"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="marginEnd"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="radius"
|
||||||
|
android:valueTo="@dimen/privacy_chip_radius"
|
||||||
|
android:valueType="floatType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="dotAlpha"
|
||||||
|
android:valueTo="255"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="bgAlpha"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
</set>
|
||||||
25
packages/SystemUI/res/anim/tv_privacy_chip_fade_in.xml
Normal file
25
packages/SystemUI/res/anim/tv_privacy_chip_fade_in.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:ordering="together"
|
||||||
|
android:interpolator="@interpolator/tv_privacy_chip_collapse_interpolator"
|
||||||
|
android:duration="@integer/privacy_chip_animation_millis">
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="dotAlpha"
|
||||||
|
android:valueTo="255"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
</set>
|
||||||
29
packages/SystemUI/res/anim/tv_privacy_chip_fade_out.xml
Normal file
29
packages/SystemUI/res/anim/tv_privacy_chip_fade_out.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:ordering="together"
|
||||||
|
android:interpolator="@interpolator/tv_privacy_chip_collapse_interpolator"
|
||||||
|
android:duration="@integer/privacy_chip_animation_millis">
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="dotAlpha"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
<objectAnimator
|
||||||
|
android:propertyName="bgAlpha"
|
||||||
|
android:valueTo="0"
|
||||||
|
android:valueType="intType"/>
|
||||||
|
</set>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ Copyright (C) 2019 The Android Open Source Project
|
~ Copyright (C) 2021 The Android Open Source Project
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
@@ -15,11 +15,8 @@
|
|||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="rectangle">
|
android:controlX1="0.4"
|
||||||
|
android:controlY1="1.00"
|
||||||
<corners android:radius="20dp"/>
|
android:controlX2="0.12"
|
||||||
<solid android:color="@color/tv_audio_recording_indicator_icon_background"/>
|
android:controlY2="1.00"/>
|
||||||
<stroke android:width="1dp" android:color="@color/tv_audio_recording_indicator_stroke"/>
|
|
||||||
|
|
||||||
</shape>
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:controlX1="0.12"
|
||||||
|
android:controlY1="1.00"
|
||||||
|
android:controlX2="0.4"
|
||||||
|
android:controlY2="1.00"/>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!--
|
<!--
|
||||||
~ Copyright (C) 2019 The Android Open Source Project
|
~ Copyright (C) 2021 The Android Open Source Project
|
||||||
~
|
~
|
||||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
~ you may not use this file except in compliance with the License.
|
~ you may not use this file except in compliance with the License.
|
||||||
@@ -20,16 +20,25 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="12dp"
|
android:gravity="center"
|
||||||
android:layout_gravity="center">
|
android:animateLayoutChanges="false"
|
||||||
|
android:padding="@dimen/privacy_chip_margin">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/chip_drawable"
|
||||||
|
android:layout_width="51dp"
|
||||||
|
android:layout_height="@dimen/privacy_chip_height"
|
||||||
|
android:minWidth="@dimen/privacy_chip_dot_bg_width"
|
||||||
|
android:minHeight="@dimen/privacy_chip_dot_bg_height"
|
||||||
|
android:layout_gravity="top|end" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/icons_container"
|
android:id="@+id/icons_container"
|
||||||
android:background="@drawable/tv_rect_shadow_rounded"
|
|
||||||
android:padding="@dimen/privacy_chip_icon_padding"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"/>
|
android:orientation="horizontal"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:animateLayoutChanges="true"
|
||||||
|
android:paddingHorizontal="@dimen/privacy_chip_padding_horizontal" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<fade android:fadingMode="fade_in" />
|
||||||
|
<changeBounds/>
|
||||||
|
</transitionSet>
|
||||||
20
packages/SystemUI/res/transition/tv_privacy_chip_expand.xml
Normal file
20
packages/SystemUI/res/transition/tv_privacy_chip_expand.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright (C) 2021 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.
|
||||||
|
-->
|
||||||
|
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<changeBounds/>
|
||||||
|
<fade android:fadingMode="fade_out" />
|
||||||
|
</transitionSet>
|
||||||
@@ -34,4 +34,7 @@
|
|||||||
<color name="bottom_sheet_button_text_color_focused">#DB202124</color>
|
<color name="bottom_sheet_button_text_color_focused">#DB202124</color>
|
||||||
<color name="bottom_sheet_button_text_color_unfocused">#B5E8EAED</color>
|
<color name="bottom_sheet_button_text_color_unfocused">#B5E8EAED</color>
|
||||||
|
|
||||||
|
<color name="privacy_circle">#5BB974</color> <!-- g400 -->
|
||||||
|
<color name="privacy_icon_tint">#30302A</color>
|
||||||
|
<color name="privacy_chip_dot_bg_tint">#66000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -18,10 +18,6 @@
|
|||||||
<!-- Opacity at which the background for the shutdown UI will be drawn. -->
|
<!-- Opacity at which the background for the shutdown UI will be drawn. -->
|
||||||
<item name="shutdown_scrim_behind_alpha" format="float" type="dimen">1.0</item>
|
<item name="shutdown_scrim_behind_alpha" format="float" type="dimen">1.0</item>
|
||||||
|
|
||||||
<dimen name="privacy_chip_icon_margin">3dp</dimen>
|
|
||||||
<dimen name="privacy_chip_icon_padding">8dp</dimen>
|
|
||||||
<dimen name="privacy_chip_icon_size">13dp</dimen>
|
|
||||||
|
|
||||||
<dimen name="bottom_sheet_padding_horizontal">32dp</dimen>
|
<dimen name="bottom_sheet_padding_horizontal">32dp</dimen>
|
||||||
<dimen name="bottom_sheet_padding_vertical">24dp</dimen>
|
<dimen name="bottom_sheet_padding_vertical">24dp</dimen>
|
||||||
|
|
||||||
@@ -44,4 +40,20 @@
|
|||||||
<dimen name="bottom_sheet_margin">24dp</dimen>
|
<dimen name="bottom_sheet_margin">24dp</dimen>
|
||||||
<dimen name="bottom_sheet_background_blur_radius">120dp</dimen>
|
<dimen name="bottom_sheet_background_blur_radius">120dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="privacy_chip_margin">12dp</dimen>
|
||||||
|
<dimen name="privacy_chip_icon_margin_in_between">9dp</dimen>
|
||||||
|
<dimen name="privacy_chip_padding_horizontal">9dp</dimen>
|
||||||
|
<dimen name="privacy_chip_icon_size">12dp</dimen>
|
||||||
|
<dimen name="privacy_chip_height">24dp</dimen>
|
||||||
|
<dimen name="privacy_chip_min_width">30dp</dimen>
|
||||||
|
<dimen name="privacy_chip_radius">12dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="privacy_chip_dot_size">8dp</dimen>
|
||||||
|
<dimen name="privacy_chip_dot_radius">4dp</dimen>
|
||||||
|
<dimen name="privacy_chip_dot_margin_horizontal">8dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="privacy_chip_dot_bg_width">24dp</dimen>
|
||||||
|
<dimen name="privacy_chip_dot_bg_height">18dp</dimen>
|
||||||
|
<dimen name="privacy_chip_dot_bg_radius">9dp</dimen>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -20,4 +20,6 @@
|
|||||||
Value 81 corresponds to BOTTOM|CENTER_HORIZONTAL.
|
Value 81 corresponds to BOTTOM|CENTER_HORIZONTAL.
|
||||||
Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
|
Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
|
||||||
<integer name="volume_dialog_gravity">21</integer>
|
<integer name="volume_dialog_gravity">21</integer>
|
||||||
|
|
||||||
|
<integer name="privacy_chip_animation_millis">300</integer>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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.privacy.television;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorInflater;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Keep;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.systemui.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drawable that can go from being the background of the privacy icons to a small dot.
|
||||||
|
* The icons are not included.
|
||||||
|
*/
|
||||||
|
public class PrivacyChipDrawable extends Drawable {
|
||||||
|
|
||||||
|
private static final String TAG = PrivacyChipDrawable.class.getSimpleName();
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
private float mWidth;
|
||||||
|
private float mHeight;
|
||||||
|
private float mMarginEnd;
|
||||||
|
private float mRadius;
|
||||||
|
private int mDotAlpha;
|
||||||
|
private int mBgAlpha;
|
||||||
|
|
||||||
|
private float mTargetWidth;
|
||||||
|
private final int mMinWidth;
|
||||||
|
private final int mIconWidth;
|
||||||
|
private final int mIconPadding;
|
||||||
|
private final int mBgWidth;
|
||||||
|
private final int mBgHeight;
|
||||||
|
private final int mBgRadius;
|
||||||
|
private final int mDotSize;
|
||||||
|
|
||||||
|
private final AnimatorSet mFadeIn;
|
||||||
|
private final AnimatorSet mFadeOut;
|
||||||
|
private final AnimatorSet mCollapse;
|
||||||
|
private final AnimatorSet mExpand;
|
||||||
|
private Animator mWidthAnimator;
|
||||||
|
|
||||||
|
private final Paint mChipPaint;
|
||||||
|
private final Paint mBgPaint;
|
||||||
|
|
||||||
|
private boolean mIsRtl;
|
||||||
|
|
||||||
|
private boolean mIsExpanded = true;
|
||||||
|
|
||||||
|
private PrivacyChipDrawableListener mListener;
|
||||||
|
|
||||||
|
interface PrivacyChipDrawableListener {
|
||||||
|
void onFadeOutFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivacyChipDrawable(Context context) {
|
||||||
|
mChipPaint = new Paint();
|
||||||
|
mChipPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mChipPaint.setColor(context.getColor(R.color.privacy_circle));
|
||||||
|
mChipPaint.setAlpha(mDotAlpha);
|
||||||
|
mChipPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
|
mBgPaint = new Paint();
|
||||||
|
mBgPaint.setStyle(Paint.Style.FILL);
|
||||||
|
mBgPaint.setColor(context.getColor(R.color.privacy_chip_dot_bg_tint));
|
||||||
|
mBgPaint.setAlpha(mBgAlpha);
|
||||||
|
mBgPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
|
mBgWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_dot_bg_width);
|
||||||
|
mBgHeight = context.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.privacy_chip_dot_bg_height);
|
||||||
|
mBgRadius = context.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.privacy_chip_dot_bg_radius);
|
||||||
|
|
||||||
|
mMinWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_min_width);
|
||||||
|
mIconWidth = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
|
||||||
|
mIconPadding = context.getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.privacy_chip_icon_margin_in_between);
|
||||||
|
mDotSize = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_dot_size);
|
||||||
|
|
||||||
|
mWidth = mMinWidth;
|
||||||
|
mHeight = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_height);
|
||||||
|
mRadius = context.getResources().getDimensionPixelSize(R.dimen.privacy_chip_radius);
|
||||||
|
|
||||||
|
mExpand = (AnimatorSet) AnimatorInflater.loadAnimator(context,
|
||||||
|
R.anim.tv_privacy_chip_expand);
|
||||||
|
mExpand.setTarget(this);
|
||||||
|
|
||||||
|
mCollapse = (AnimatorSet) AnimatorInflater.loadAnimator(context,
|
||||||
|
R.anim.tv_privacy_chip_collapse);
|
||||||
|
mCollapse.setTarget(this);
|
||||||
|
|
||||||
|
mFadeIn = (AnimatorSet) AnimatorInflater.loadAnimator(context,
|
||||||
|
R.anim.tv_privacy_chip_fade_in);
|
||||||
|
mFadeIn.setTarget(this);
|
||||||
|
|
||||||
|
mFadeOut = (AnimatorSet) AnimatorInflater.loadAnimator(context,
|
||||||
|
R.anim.tv_privacy_chip_fade_out);
|
||||||
|
mFadeOut.setTarget(this);
|
||||||
|
mFadeOut.addListener(new Animator.AnimatorListener() {
|
||||||
|
private boolean mCancelled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
mCancelled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
if (!mCancelled && mListener != null) {
|
||||||
|
if (DEBUG) Log.d(TAG, "Fade-out complete");
|
||||||
|
mListener.onFadeOutFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
mCancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pass null to remove listener.
|
||||||
|
*/
|
||||||
|
public void setListener(@Nullable PrivacyChipDrawableListener listener) {
|
||||||
|
this.mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call once the view that is showing the drawable is visible to start fading the chip in.
|
||||||
|
*/
|
||||||
|
public void startInitialFadeIn() {
|
||||||
|
if (DEBUG) Log.d(TAG, "initial fade-in");
|
||||||
|
mFadeIn.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
Rect bounds = getBounds();
|
||||||
|
|
||||||
|
int centerVertical = (bounds.bottom - bounds.top) / 2;
|
||||||
|
// Dot background
|
||||||
|
RectF bgBounds = new RectF(
|
||||||
|
mIsRtl ? bounds.left : bounds.right - mBgWidth,
|
||||||
|
centerVertical - mBgHeight / 2f,
|
||||||
|
mIsRtl ? bounds.left + mBgWidth : bounds.right,
|
||||||
|
centerVertical + mBgHeight / 2f);
|
||||||
|
if (DEBUG) Log.v(TAG, "bg: " + bgBounds.toShortString());
|
||||||
|
canvas.drawRoundRect(bgBounds, mBgRadius, mBgRadius, mBgPaint);
|
||||||
|
|
||||||
|
// Icon background / dot
|
||||||
|
RectF greenBounds = new RectF(
|
||||||
|
mIsRtl ? bounds.left + mMarginEnd : bounds.right - mWidth - mMarginEnd,
|
||||||
|
centerVertical - mHeight / 2,
|
||||||
|
mIsRtl ? bounds.left + mWidth + mMarginEnd : bounds.right - mMarginEnd,
|
||||||
|
centerVertical + mHeight / 2);
|
||||||
|
if (DEBUG) Log.v(TAG, "green: " + greenBounds.toShortString());
|
||||||
|
canvas.drawRoundRect(greenBounds, mRadius, mRadius, mChipPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateToNewTargetWidth(float width) {
|
||||||
|
if (DEBUG) Log.d(TAG, "new target width: " + width);
|
||||||
|
if (width != mTargetWidth) {
|
||||||
|
mTargetWidth = width;
|
||||||
|
Animator newWidthAnimator = ObjectAnimator.ofFloat(this, "width", mTargetWidth);
|
||||||
|
newWidthAnimator.start();
|
||||||
|
if (mWidthAnimator != null) {
|
||||||
|
mWidthAnimator.cancel();
|
||||||
|
}
|
||||||
|
mWidthAnimator = newWidthAnimator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expand() {
|
||||||
|
if (DEBUG) Log.d(TAG, "expanding");
|
||||||
|
if (mIsExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mIsExpanded = true;
|
||||||
|
|
||||||
|
mExpand.start();
|
||||||
|
mCollapse.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the animation to a dot.
|
||||||
|
*/
|
||||||
|
public void collapse() {
|
||||||
|
if (DEBUG) Log.d(TAG, "collapsing");
|
||||||
|
if (!mIsExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mIsExpanded = false;
|
||||||
|
|
||||||
|
animateToNewTargetWidth(mDotSize);
|
||||||
|
mCollapse.start();
|
||||||
|
mExpand.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fades out the view if 0 icons are to be shown, expands the chip if it has been collapsed and
|
||||||
|
* makes the width of the chip adjust to the amount of icons to be shown.
|
||||||
|
* Should not be called when only the order of the icons was changed as the chip will expand
|
||||||
|
* again without there being any real update.
|
||||||
|
*
|
||||||
|
* @param iconCount Can be 0 to fade out the chip.
|
||||||
|
*/
|
||||||
|
public void updateIcons(int iconCount) {
|
||||||
|
if (DEBUG) Log.d(TAG, "updating icons: " + iconCount);
|
||||||
|
|
||||||
|
// calculate chip size and use it for end value of animation that is specified in code,
|
||||||
|
// not xml
|
||||||
|
if (iconCount == 0) {
|
||||||
|
// fade out if there are no icons
|
||||||
|
mFadeOut.start();
|
||||||
|
|
||||||
|
mWidthAnimator.cancel();
|
||||||
|
mFadeIn.cancel();
|
||||||
|
mExpand.cancel();
|
||||||
|
mCollapse.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mFadeOut.cancel();
|
||||||
|
expand();
|
||||||
|
animateToNewTargetWidth(mMinWidth + (iconCount - 1) * (mIconWidth + mIconPadding));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
setDotAlpha(alpha);
|
||||||
|
setBgAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAlpha() {
|
||||||
|
return mDotAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set alpha value the green part of the chip.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setDotAlpha(int alpha) {
|
||||||
|
if (DEBUG) Log.v(TAG, "dot alpha updated to: " + alpha);
|
||||||
|
mDotAlpha = alpha;
|
||||||
|
mChipPaint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public int getDotAlpha() {
|
||||||
|
return mDotAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set alpha value of the background of the chip.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setBgAlpha(int alpha) {
|
||||||
|
if (DEBUG) Log.v(TAG, "bg alpha updated to: " + alpha);
|
||||||
|
mBgAlpha = alpha;
|
||||||
|
mBgPaint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public int getBgAlpha() {
|
||||||
|
return mBgAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The radius of the green part of the chip, not the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setRadius(float radius) {
|
||||||
|
mRadius = radius;
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The radius of the green part of the chip, not the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public float getRadius() {
|
||||||
|
return mRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of the green part of the chip, not including the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setHeight(float height) {
|
||||||
|
mHeight = height;
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Height of the green part of the chip, not including the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public float getHeight() {
|
||||||
|
return mHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Width of the green part of the chip, not including the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setWidth(float width) {
|
||||||
|
mWidth = width;
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Width of the green part of the chip, not including the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public float getWidth() {
|
||||||
|
return mWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Margin at the end of the green part of the chip, so that it will be placed in the middle of
|
||||||
|
* the rounded rectangle in the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public void setMarginEnd(float marginEnd) {
|
||||||
|
mMarginEnd = marginEnd;
|
||||||
|
invalidateSelf();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Margin at the end of the green part of the chip, so that it will be placed in the
|
||||||
|
* middle of the rounded rectangle in the background.
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
public float getMarginEnd() {
|
||||||
|
return mMarginEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the layout direction.
|
||||||
|
*/
|
||||||
|
public void setRtl(boolean isRtl) {
|
||||||
|
mIsRtl = isRtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,9 +25,10 @@ import android.annotation.IntDef;
|
|||||||
import android.annotation.UiThread;
|
import android.annotation.UiThread;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
|
||||||
import android.graphics.PixelFormat;
|
import android.graphics.PixelFormat;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -38,15 +39,20 @@ import android.view.WindowManager;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.SystemUI;
|
import com.android.systemui.SystemUI;
|
||||||
import com.android.systemui.dagger.SysUISingleton;
|
import com.android.systemui.dagger.SysUISingleton;
|
||||||
import com.android.systemui.privacy.PrivacyChipBuilder;
|
import com.android.systemui.privacy.PrivacyChipBuilder;
|
||||||
import com.android.systemui.privacy.PrivacyItem;
|
import com.android.systemui.privacy.PrivacyItem;
|
||||||
import com.android.systemui.privacy.PrivacyItemController;
|
import com.android.systemui.privacy.PrivacyItemController;
|
||||||
|
import com.android.systemui.privacy.PrivacyType;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -56,9 +62,10 @@ import javax.inject.Inject;
|
|||||||
* recording audio, accessing the camera or accessing the location.
|
* recording audio, accessing the camera or accessing the location.
|
||||||
*/
|
*/
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback {
|
public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemController.Callback,
|
||||||
|
PrivacyChipDrawable.PrivacyChipDrawableListener {
|
||||||
private static final String TAG = "TvOngoingPrivacyChip";
|
private static final String TAG = "TvOngoingPrivacyChip";
|
||||||
static final boolean DEBUG = false;
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
// This title is used in CameraMicIndicatorsPermissionTest and
|
// This title is used in CameraMicIndicatorsPermissionTest and
|
||||||
// RecognitionServiceMicIndicatorTest.
|
// RecognitionServiceMicIndicatorTest.
|
||||||
@@ -68,7 +75,8 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
@IntDef(prefix = {"STATE_"}, value = {
|
@IntDef(prefix = {"STATE_"}, value = {
|
||||||
STATE_NOT_SHOWN,
|
STATE_NOT_SHOWN,
|
||||||
STATE_APPEARING,
|
STATE_APPEARING,
|
||||||
STATE_SHOWN,
|
STATE_EXPANDED,
|
||||||
|
STATE_COLLAPSED,
|
||||||
STATE_DISAPPEARING
|
STATE_DISAPPEARING
|
||||||
})
|
})
|
||||||
public @interface State {
|
public @interface State {
|
||||||
@@ -76,46 +84,58 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
|
|
||||||
private static final int STATE_NOT_SHOWN = 0;
|
private static final int STATE_NOT_SHOWN = 0;
|
||||||
private static final int STATE_APPEARING = 1;
|
private static final int STATE_APPEARING = 1;
|
||||||
private static final int STATE_SHOWN = 2;
|
private static final int STATE_EXPANDED = 2;
|
||||||
private static final int STATE_DISAPPEARING = 3;
|
private static final int STATE_COLLAPSED = 3;
|
||||||
|
private static final int STATE_DISAPPEARING = 4;
|
||||||
|
|
||||||
private static final int ANIMATION_DURATION_MS = 200;
|
private static final int EXPANDED_DURATION_MS = 4000;
|
||||||
|
public final int mAnimationDurationMs;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final PrivacyItemController mPrivacyItemController;
|
private final PrivacyItemController mPrivacyItemController;
|
||||||
|
|
||||||
private View mIndicatorView;
|
private ViewGroup mIndicatorView;
|
||||||
private boolean mViewAndWindowAdded;
|
private boolean mViewAndWindowAdded;
|
||||||
private ObjectAnimator mAnimator;
|
private ObjectAnimator mAnimator;
|
||||||
|
|
||||||
private boolean mMicCameraIndicatorFlagEnabled;
|
private boolean mMicCameraIndicatorFlagEnabled;
|
||||||
private boolean mLocationIndicatorEnabled;
|
private boolean mAllIndicatorsEnabled;
|
||||||
private List<PrivacyItem> mPrivacyItems;
|
|
||||||
|
@NonNull
|
||||||
|
private List<PrivacyItem> mPrivacyItems = Collections.emptyList();
|
||||||
|
|
||||||
private LinearLayout mIconsContainer;
|
private LinearLayout mIconsContainer;
|
||||||
private final int mIconSize;
|
private final int mIconSize;
|
||||||
private final int mIconMarginStart;
|
private final int mIconMarginStart;
|
||||||
|
|
||||||
|
private PrivacyChipDrawable mChipDrawable;
|
||||||
|
|
||||||
|
private final Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final Runnable mCollapseRunnable = this::collapseChip;
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private int mState = STATE_NOT_SHOWN;
|
private int mState = STATE_NOT_SHOWN;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController) {
|
public TvOngoingPrivacyChip(Context context, PrivacyItemController privacyItemController) {
|
||||||
super(context);
|
super(context);
|
||||||
Log.d(TAG, "Privacy chip running without id");
|
if (DEBUG) Log.d(TAG, "Privacy chip running");
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mPrivacyItemController = privacyItemController;
|
mPrivacyItemController = privacyItemController;
|
||||||
|
|
||||||
Resources res = mContext.getResources();
|
Resources res = mContext.getResources();
|
||||||
mIconMarginStart = Math.round(res.getDimension(R.dimen.privacy_chip_icon_margin));
|
mIconMarginStart = Math.round(
|
||||||
|
res.getDimension(R.dimen.privacy_chip_icon_margin_in_between));
|
||||||
mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
|
mIconSize = res.getDimensionPixelSize(R.dimen.privacy_chip_icon_size);
|
||||||
|
|
||||||
|
mAnimationDurationMs = res.getInteger(R.integer.privacy_chip_animation_millis);
|
||||||
|
|
||||||
mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable();
|
mMicCameraIndicatorFlagEnabled = privacyItemController.getMicCameraAvailable();
|
||||||
mLocationIndicatorEnabled = privacyItemController.getLocationAvailable();
|
mAllIndicatorsEnabled = privacyItemController.getAllIndicatorsAvailable();
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled);
|
Log.d(TAG, "micCameraIndicators: " + mMicCameraIndicatorFlagEnabled);
|
||||||
Log.d(TAG, "locationIndicators: " + mLocationIndicatorEnabled);
|
Log.d(TAG, "allIndicators: " + mAllIndicatorsEnabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,69 +145,145 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
|
public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) {
|
||||||
if (DEBUG) Log.d(TAG, "PrivacyItemsChanged");
|
if (DEBUG) Log.d(TAG, "PrivacyItemsChanged");
|
||||||
mPrivacyItems = privacyItems;
|
|
||||||
updateUI();
|
List<PrivacyItem> updatedPrivacyItems = new ArrayList<>(privacyItems);
|
||||||
|
// Never show the location indicator on tv.
|
||||||
|
if (updatedPrivacyItems.removeIf(
|
||||||
|
privacyItem -> privacyItem.getPrivacyType() == PrivacyType.TYPE_LOCATION)) {
|
||||||
|
if (DEBUG) Log.v(TAG, "Removed the location item");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChipDisabled()) {
|
||||||
|
fadeOutIndicator();
|
||||||
|
mPrivacyItems = updatedPrivacyItems;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do they have the same elements? (order doesn't matter)
|
||||||
|
if (updatedPrivacyItems.size() == mPrivacyItems.size()
|
||||||
|
&& mPrivacyItems.containsAll(updatedPrivacyItems)) {
|
||||||
|
if (DEBUG) Log.d(TAG, "List wasn't updated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mPrivacyItems = updatedPrivacyItems;
|
||||||
|
updateChip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateChip() {
|
||||||
|
if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
|
||||||
|
|
||||||
|
if (mPrivacyItems.isEmpty()) {
|
||||||
|
if (DEBUG) Log.d(TAG, "removing indicator (state: " + stateToString(mState) + ")");
|
||||||
|
fadeOutIndicator();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) Log.d(TAG, "Current state: " + stateToString(mState));
|
||||||
|
switch (mState) {
|
||||||
|
case STATE_NOT_SHOWN:
|
||||||
|
createAndShowIndicator();
|
||||||
|
break;
|
||||||
|
case STATE_APPEARING:
|
||||||
|
case STATE_EXPANDED:
|
||||||
|
updateIcons();
|
||||||
|
collapseLater();
|
||||||
|
break;
|
||||||
|
case STATE_COLLAPSED:
|
||||||
|
case STATE_DISAPPEARING:
|
||||||
|
mState = STATE_EXPANDED;
|
||||||
|
updateIcons();
|
||||||
|
animateIconAppearance();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapse the chip EXPANDED_DURATION_MS from now.
|
||||||
|
*/
|
||||||
|
private void collapseLater() {
|
||||||
|
mUiThreadHandler.removeCallbacks(mCollapseRunnable);
|
||||||
|
if (DEBUG) Log.d(TAG, "chip will collapse in " + EXPANDED_DURATION_MS + "ms");
|
||||||
|
mUiThreadHandler.postDelayed(mCollapseRunnable, EXPANDED_DURATION_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collapseChip() {
|
||||||
|
if (DEBUG) Log.d(TAG, "collapseChip");
|
||||||
|
|
||||||
|
if (mState != STATE_EXPANDED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mState = STATE_COLLAPSED;
|
||||||
|
|
||||||
|
if (mChipDrawable != null) {
|
||||||
|
mChipDrawable.collapse();
|
||||||
|
}
|
||||||
|
animateIconDisappearance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFlagMicCameraChanged(boolean flag) {
|
public void onFlagMicCameraChanged(boolean flag) {
|
||||||
if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag);
|
if (DEBUG) Log.d(TAG, "mic/camera indicators enabled: " + flag);
|
||||||
mMicCameraIndicatorFlagEnabled = flag;
|
mMicCameraIndicatorFlagEnabled = flag;
|
||||||
|
updateChipOnFlagChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFlagLocationChanged(boolean flag) {
|
public void onFlagAllChanged(boolean flag) {
|
||||||
if (DEBUG) Log.d(TAG, "location indicators enabled: " + flag);
|
if (DEBUG) Log.d(TAG, "all indicators enabled: " + flag);
|
||||||
mLocationIndicatorEnabled = flag;
|
mAllIndicatorsEnabled = flag;
|
||||||
|
updateChipOnFlagChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateUI() {
|
private boolean isChipDisabled() {
|
||||||
if (DEBUG) Log.d(TAG, mPrivacyItems.size() + " privacy items");
|
return !(mMicCameraIndicatorFlagEnabled || mAllIndicatorsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
if ((mMicCameraIndicatorFlagEnabled || mLocationIndicatorEnabled)
|
private void updateChipOnFlagChanged() {
|
||||||
&& !mPrivacyItems.isEmpty()) {
|
if (isChipDisabled()) {
|
||||||
if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) {
|
fadeOutIndicator();
|
||||||
showIndicator();
|
|
||||||
} else {
|
|
||||||
if (DEBUG) Log.d(TAG, "only updating icons");
|
|
||||||
PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
|
|
||||||
setIcons(builder.generateIcons(), mIconsContainer);
|
|
||||||
mIconsContainer.requestLayout();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
hideIndicatorIfNeeded();
|
updateChip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void hideIndicatorIfNeeded() {
|
private void fadeOutIndicator() {
|
||||||
if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) return;
|
if (mState == STATE_NOT_SHOWN || mState == STATE_DISAPPEARING) return;
|
||||||
|
|
||||||
|
mUiThreadHandler.removeCallbacks(mCollapseRunnable);
|
||||||
|
|
||||||
if (mViewAndWindowAdded) {
|
if (mViewAndWindowAdded) {
|
||||||
mState = STATE_DISAPPEARING;
|
mState = STATE_DISAPPEARING;
|
||||||
animateDisappearance();
|
animateIconDisappearance();
|
||||||
} else {
|
} else {
|
||||||
// Appearing animation has not started yet, as we were still waiting for the View to be
|
// Appearing animation has not started yet, as we were still waiting for the View to be
|
||||||
// laid out.
|
// laid out.
|
||||||
mState = STATE_NOT_SHOWN;
|
mState = STATE_NOT_SHOWN;
|
||||||
removeIndicatorView();
|
removeIndicatorView();
|
||||||
}
|
}
|
||||||
|
if (mChipDrawable != null) {
|
||||||
|
mChipDrawable.updateIcons(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
private void showIndicator() {
|
private void createAndShowIndicator() {
|
||||||
mState = STATE_APPEARING;
|
mState = STATE_APPEARING;
|
||||||
|
|
||||||
|
if (mIndicatorView != null || mViewAndWindowAdded) {
|
||||||
|
removeIndicatorView();
|
||||||
|
}
|
||||||
|
|
||||||
// Inflate the indicator view
|
// Inflate the indicator view
|
||||||
mIndicatorView = LayoutInflater.from(mContext).inflate(
|
mIndicatorView = (ViewGroup) LayoutInflater.from(mContext).inflate(
|
||||||
R.layout.tv_ongoing_privacy_chip, null);
|
R.layout.tv_ongoing_privacy_chip, null);
|
||||||
|
|
||||||
// 1. Set alpha to 0.
|
// 1. Set icon alpha to 0.
|
||||||
// 2. Wait until the window is shown and the view is laid out.
|
// 2. Wait until the window is shown and the view is laid out.
|
||||||
// 3. Start a "fade in" (alpha) animation.
|
// 3. Start a "fade in" (alpha) animation.
|
||||||
mIndicatorView.setAlpha(0f);
|
|
||||||
mIndicatorView
|
mIndicatorView
|
||||||
.getViewTreeObserver()
|
.getViewTreeObserver()
|
||||||
.addOnGlobalLayoutListener(
|
.addOnGlobalLayoutListener(
|
||||||
@@ -196,20 +292,35 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
public void onGlobalLayout() {
|
public void onGlobalLayout() {
|
||||||
// State could have changed to NOT_SHOWN (if all the recorders are
|
// State could have changed to NOT_SHOWN (if all the recorders are
|
||||||
// already gone)
|
// already gone)
|
||||||
if (mState != STATE_APPEARING) return;
|
if (mState != STATE_APPEARING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mViewAndWindowAdded = true;
|
mViewAndWindowAdded = true;
|
||||||
// Remove the observer
|
// Remove the observer
|
||||||
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
|
mIndicatorView.getViewTreeObserver().removeOnGlobalLayoutListener(
|
||||||
this);
|
this);
|
||||||
|
|
||||||
animateAppearance();
|
animateIconAppearance();
|
||||||
|
mChipDrawable.startInitialFadeIn();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
|
||||||
|
== View.LAYOUT_DIRECTION_RTL;
|
||||||
|
if (DEBUG) Log.d(TAG, "is RTL: " + isRtl);
|
||||||
|
|
||||||
|
mChipDrawable = new PrivacyChipDrawable(mContext);
|
||||||
|
mChipDrawable.setListener(this);
|
||||||
|
mChipDrawable.setRtl(isRtl);
|
||||||
|
ImageView chipBackground = mIndicatorView.findViewById(R.id.chip_drawable);
|
||||||
|
if (chipBackground != null) {
|
||||||
|
chipBackground.setImageDrawable(mChipDrawable);
|
||||||
|
}
|
||||||
|
|
||||||
mIconsContainer = mIndicatorView.findViewById(R.id.icons_container);
|
mIconsContainer = mIndicatorView.findViewById(R.id.icons_container);
|
||||||
PrivacyChipBuilder builder = new PrivacyChipBuilder(mContext, mPrivacyItems);
|
mIconsContainer.setAlpha(0f);
|
||||||
setIcons(builder.generateIcons(), mIconsContainer);
|
updateIcons();
|
||||||
|
|
||||||
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
|
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
|
||||||
WRAP_CONTENT,
|
WRAP_CONTENT,
|
||||||
@@ -217,19 +328,19 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
|
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
|
||||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||||
PixelFormat.TRANSLUCENT);
|
PixelFormat.TRANSLUCENT);
|
||||||
layoutParams.gravity = Gravity.TOP | Gravity.END;
|
layoutParams.gravity = Gravity.TOP | (isRtl ? Gravity.LEFT : Gravity.RIGHT);
|
||||||
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
|
layoutParams.setTitle(LAYOUT_PARAMS_TITLE);
|
||||||
layoutParams.packageName = mContext.getPackageName();
|
layoutParams.packageName = mContext.getPackageName();
|
||||||
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
||||||
windowManager.addView(mIndicatorView, layoutParams);
|
windowManager.addView(mIndicatorView, layoutParams);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setIcons(List<Drawable> icons, ViewGroup iconsContainer) {
|
private void updateIcons() {
|
||||||
iconsContainer.removeAllViews();
|
List<Drawable> icons = new PrivacyChipBuilder(mContext, mPrivacyItems).generateIcons();
|
||||||
|
mIconsContainer.removeAllViews();
|
||||||
for (int i = 0; i < icons.size(); i++) {
|
for (int i = 0; i < icons.size(); i++) {
|
||||||
Drawable icon = icons.get(i);
|
Drawable icon = icons.get(i);
|
||||||
icon.mutate().setTint(Color.WHITE);
|
icon.mutate().setTint(mContext.getColor(R.color.privacy_icon_tint));
|
||||||
ImageView imageView = new ImageView(mContext);
|
ImageView imageView = new ImageView(mContext);
|
||||||
imageView.setImageDrawable(icon);
|
imageView.setImageDrawable(icon);
|
||||||
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||||
@@ -241,22 +352,25 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
imageView.setLayoutParams(layoutParams);
|
imageView.setLayoutParams(layoutParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (mChipDrawable != null) {
|
||||||
|
mChipDrawable.updateIcons(icons.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateAppearance() {
|
private void animateIconAppearance() {
|
||||||
animateAlphaTo(1f);
|
animateIconAlphaTo(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateDisappearance() {
|
private void animateIconDisappearance() {
|
||||||
animateAlphaTo(0f);
|
animateIconAlphaTo(0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateAlphaTo(final float endValue) {
|
private void animateIconAlphaTo(float endValue) {
|
||||||
if (mAnimator == null) {
|
if (mAnimator == null) {
|
||||||
if (DEBUG) Log.d(TAG, "set up animator");
|
if (DEBUG) Log.d(TAG, "set up animator");
|
||||||
|
|
||||||
mAnimator = new ObjectAnimator();
|
mAnimator = new ObjectAnimator();
|
||||||
mAnimator.setTarget(mIndicatorView);
|
mAnimator.setTarget(mIconsContainer);
|
||||||
mAnimator.setProperty(View.ALPHA);
|
mAnimator.setProperty(View.ALPHA);
|
||||||
mAnimator.addListener(new AnimatorListenerAdapter() {
|
mAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
boolean mCancelled;
|
boolean mCancelled;
|
||||||
@@ -280,7 +394,7 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
// and then onAnimationEnd(...). We, however, only want to proceed here if the
|
// and then onAnimationEnd(...). We, however, only want to proceed here if the
|
||||||
// animation ended "naturally".
|
// animation ended "naturally".
|
||||||
if (!mCancelled) {
|
if (!mCancelled) {
|
||||||
onAnimationFinished();
|
onIconAnimationFinished();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -289,19 +403,37 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
mAnimator.cancel();
|
mAnimator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
final float currentValue = mIndicatorView.getAlpha();
|
final float currentValue = mIconsContainer.getAlpha();
|
||||||
|
if (currentValue == endValue) {
|
||||||
|
if (DEBUG) Log.d(TAG, "alpha not changing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);
|
if (DEBUG) Log.d(TAG, "animate alpha to " + endValue + " from " + currentValue);
|
||||||
|
|
||||||
mAnimator.setDuration((int) (Math.abs(currentValue - endValue) * ANIMATION_DURATION_MS));
|
mAnimator.setDuration(mAnimationDurationMs);
|
||||||
mAnimator.setFloatValues(endValue);
|
mAnimator.setFloatValues(endValue);
|
||||||
mAnimator.start();
|
mAnimator.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onAnimationFinished() {
|
@Override
|
||||||
if (DEBUG) Log.d(TAG, "onAnimationFinished");
|
public void onFadeOutFinished() {
|
||||||
|
if (DEBUG) Log.d(TAG, "drawable fade-out finished");
|
||||||
|
|
||||||
|
if (mState == STATE_DISAPPEARING) {
|
||||||
|
removeIndicatorView();
|
||||||
|
mState = STATE_NOT_SHOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onIconAnimationFinished() {
|
||||||
|
if (DEBUG) Log.d(TAG, "onAnimationFinished (icon fade)");
|
||||||
|
|
||||||
|
if (mState == STATE_APPEARING || mState == STATE_EXPANDED) {
|
||||||
|
collapseLater();
|
||||||
|
}
|
||||||
|
|
||||||
if (mState == STATE_APPEARING) {
|
if (mState == STATE_APPEARING) {
|
||||||
mState = STATE_SHOWN;
|
mState = STATE_EXPANDED;
|
||||||
} else if (mState == STATE_DISAPPEARING) {
|
} else if (mState == STATE_DISAPPEARING) {
|
||||||
removeIndicatorView();
|
removeIndicatorView();
|
||||||
mState = STATE_NOT_SHOWN;
|
mState = STATE_NOT_SHOWN;
|
||||||
@@ -312,14 +444,39 @@ public class TvOngoingPrivacyChip extends SystemUI implements PrivacyItemControl
|
|||||||
if (DEBUG) Log.d(TAG, "removeIndicatorView");
|
if (DEBUG) Log.d(TAG, "removeIndicatorView");
|
||||||
|
|
||||||
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
|
||||||
if (windowManager != null) {
|
if (windowManager != null && mIndicatorView != null) {
|
||||||
windowManager.removeView(mIndicatorView);
|
windowManager.removeView(mIndicatorView);
|
||||||
}
|
}
|
||||||
|
|
||||||
mIndicatorView = null;
|
mIndicatorView = null;
|
||||||
mAnimator = null;
|
mAnimator = null;
|
||||||
|
|
||||||
|
if (mChipDrawable != null) {
|
||||||
|
mChipDrawable.setListener(null);
|
||||||
|
mChipDrawable = null;
|
||||||
|
}
|
||||||
|
|
||||||
mViewAndWindowAdded = false;
|
mViewAndWindowAdded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in debug logs.
|
||||||
|
*/
|
||||||
|
private String stateToString(@State int state) {
|
||||||
|
switch (state) {
|
||||||
|
case STATE_NOT_SHOWN:
|
||||||
|
return "NOT_SHOWN";
|
||||||
|
case STATE_APPEARING:
|
||||||
|
return "APPEARING";
|
||||||
|
case STATE_EXPANDED:
|
||||||
|
return "EXPANDED";
|
||||||
|
case STATE_COLLAPSED:
|
||||||
|
return "COLLAPSED";
|
||||||
|
case STATE_DISAPPEARING:
|
||||||
|
return "DISAPPEARING";
|
||||||
|
default:
|
||||||
|
return "INVALID";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user