android 9.0 GMS 修改google 语音助手需求-- EEA版本需求

对于EEA版本的需求 应该是按HOME出花瓣的现象的.代码也同步到我的下载资源里面,需要的可以下载,对比过去,会快一点

 

From dbc202e769d3c3e9f0c30124d2857770fa85ab41 Mon Sep 17 00:00:00 2001
From: Lance Chang <jinwoong@google.com>
Date: Wed, 05 Jul 2017 19:55:22 -0700
Subject: [PATCH] Home button animation sample code for Android O

This CL implements the home button animation on top of AOSP SystemUI
codebase for Android OEM partners. This change MUST NOT be merged to
any internal branch.

Bug: 34622877
Test: lunch aosp target && make
Change-Id: Iea114eb6a82a26b83258fbec2ad1e2fc324ff6ae
---

diff --git a/packages/SystemUI/opa/res/drawable/halo.xml b/packages/SystemUI/opa/res/drawable/halo.xml
new file mode 100644
index 0000000..c9a95f5
--- /dev/null
+++ b/packages/SystemUI/opa/res/drawable/halo.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="ring"
+    android:innerRadius="@dimen/halo_inner_radius"
+    android:thickness="@dimen/halo_thickness"
+    android:useLevel="false">
+
+    <solid android:color="@android:color/white" />
+
+    <size
+        android:height="@dimen/halo_diameter"
+        android:width="@dimen/halo_diameter" />
+</shape>
diff --git a/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_blue.xml b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_blue.xml
new file mode 100644
index 0000000..7e5a66c
--- /dev/null
+++ b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_blue.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/opa_dot_diam"
+        android:height="@dimen/opa_dot_diam" />
+    <solid
+        android:color="#FF4285F4" />
+</shape>
diff --git a/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_green.xml b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_green.xml
new file mode 100644
index 0000000..d11b906
--- /dev/null
+++ b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_green.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/opa_dot_diam"
+        android:height="@dimen/opa_dot_diam" />
+    <solid
+        android:color="#FF34A853" />
+</shape>
diff --git a/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_red.xml b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_red.xml
new file mode 100644
index 0000000..4483b0f
--- /dev/null
+++ b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_red.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/opa_dot_diam"
+        android:height="@dimen/opa_dot_diam" />
+    <solid
+        android:color="#FFEA4335" />
+</shape>
diff --git a/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_yellow.xml b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_yellow.xml
new file mode 100644
index 0000000..141ff8e
--- /dev/null
+++ b/packages/SystemUI/opa/res/drawable/ic_sysbar_opa_yellow.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/opa_dot_diam"
+        android:height="@dimen/opa_dot_diam" />
+    <solid
+        android:color="#FFFBBC05" />
+</shape>
diff --git a/packages/SystemUI/opa/res/layout/home.xml b/packages/SystemUI/opa/res/layout/home.xml
new file mode 100644
index 0000000..90bc1c5
--- /dev/null
+++ b/packages/SystemUI/opa/res/layout/home.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<com.google.android.systemui.OpaLayout
+    android:id="@+id/home"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res-auto"
+    android:layout_width="@dimen/navigation_key_width"
+    android:layout_height="match_parent"
+    android:layout_weight="0"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:paddingEnd="@dimen/navigation_key_padding"
+    android:paddingStart="@dimen/navigation_key_padding">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <ImageView
+            android:id="@+id/red"
+            style="@style/DotStyle"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_sysbar_opa_red"/>
+
+        <ImageView
+            android:id="@+id/blue"
+            style="@style/DotStyle"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_sysbar_opa_blue" />
+
+        <ImageView
+            android:id="@+id/green"
+            style="@style/DotStyle"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_sysbar_opa_green" />
+
+        <ImageView
+            android:id="@+id/yellow"
+            style="@style/DotStyle"
+            android:importantForAccessibility="no"
+            android:src="@drawable/ic_sysbar_opa_yellow" />
+
+    </RelativeLayout>
+
+    <ImageView
+        android:id="@+id/white"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+	android:importantForAccessibility="no" />
+
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/home_button"
+        android:layout_width="@dimen/navigation_key_width"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:contentDescription="@string/accessibility_home"
+        android:scaleType="center"
+        systemui:keyCode="3" />
+
+    <ImageView
+        android:id="@+id/halo"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:importantForAccessibility="no"
+        android:src="@drawable/halo" />
+
+</com.google.android.systemui.OpaLayout>
diff --git a/packages/SystemUI/opa/res/values/config.xml b/packages/SystemUI/opa/res/values/config.xml
new file mode 100644
index 0000000..e47c50c
--- /dev/null
+++ b/packages/SystemUI/opa/res/values/config.xml
@@ -0,0 +1,4 @@
+<resources>
+    <!-- SystemUIFactory component -->
+    <string name="config_systemUIFactoryComponent" translatable="false">com.google.android.systemui.SystemUIGoogleFactory</string>
+</resources>
diff --git a/packages/SystemUI/opa/res/values/dimens.xml b/packages/SystemUI/opa/res/values/dimens.xml
new file mode 100644
index 0000000..ee7ee9e
--- /dev/null
+++ b/packages/SystemUI/opa/res/values/dimens.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<resources>
+    <!-- The inner radius of the halo. -->
+    <dimen name="halo_inner_radius">10dp</dimen>
+
+    <!-- The thickness of the halo. -->
+    <dimen name="halo_thickness">1dp</dimen>
+
+    <!-- The diameter of the halo. This is 2*(halo_inner_radius + halo_thickness). -->
+    <dimen name="halo_diameter">22dp</dimen>
+
+    <!-- The diameter of the opa dots -->
+    <dimen name="opa_dot_diam">10dp</dimen>
+
+    <!-- The translation for diamond -->
+    <dimen name="opa_diamond_translation">16dp</dimen>
+
+    <!-- The y translation into line for red and yellow -->
+    <dimen name="opa_line_y_translation">16dp</dimen>
+
+    <!-- The x translation into line for red and yellow -->
+    <dimen name="opa_line_x_trans_ry">15dp</dimen>
+
+    <!-- The x translation into line for green and blue -->
+    <dimen name="opa_line_x_trans_bg">30dp</dimen>
+
+    <!-- The x collapse for blue and green -->
+    <dimen name="opa_line_x_collapse_bg">46dp</dimen>
+
+    <!-- The x collapse for red and yellow -->
+    <dimen name="opa_line_x_collapse_ry">15dp</dimen>
+</resources>
diff --git a/packages/SystemUI/opa/res/values/styles.xml b/packages/SystemUI/opa/res/values/styles.xml
new file mode 100644
index 0000000..1f0b2eb
--- /dev/null
+++ b/packages/SystemUI/opa/res/values/styles.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="DotStyle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:layout_centerVertical">true</item>
+    </style>
+</resources>
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/AssistManagerGoogle.java b/packages/SystemUI/opa/src/com/google/android/systemui/AssistManagerGoogle.java
new file mode 100644
index 0000000..b10a67a
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/AssistManagerGoogle.java
@@ -0,0 +1,108 @@
+package com.google.android.systemui;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+/**
+ * GoogleSystemUI-specific flavor of AssistManager.
+ */
+public class AssistManagerGoogle extends AssistManager {
+
+    private final ContentResolver mContentResolver;
+    private final ContentObserver mContentObserver = new AssistantSettingsObserver();
+    private final OpaEnableDispatcher mOpaEnableDispatcher;
+    private final AssistantStateReceiver mEnableReceiver = new AssistantStateReceiver();
+
+    private final KeyguardUpdateMonitorCallback mUserSwitchCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onUserSwitching(int userId) {
+                    updateAssistantEnabledState();
+                    // Unregister and re-register observer for current user when user switches.
+                    unregisterSettingsObserver();
+                    registerSettingsObserver();
+                    // Unregister and re-register the opa enabled for current user
+                    unregisterEnableReceiver();
+                    registerEnableReceiver(userId);
+                }
+            };
+
+    public AssistManagerGoogle(DeviceProvisionedController controller, Context context) {
+        super(controller, context);
+        mContentResolver = context.getContentResolver();
+        mOpaEnableDispatcher = new OpaEnableDispatcher(context, mAssistUtils);
+        KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUserSwitchCallback);
+        // Register Assistant Settings observer
+        registerSettingsObserver();
+        registerEnableReceiver(UserHandle.USER_CURRENT);
+    }
+
+    @Override
+    public boolean shouldShowOrb() {
+        return false;
+    }
+
+    /**
+     * Register AssistantStateReceiver for userId.
+     */
+    private void registerEnableReceiver(int userId) {
+        mContext.registerReceiverAsUser(mEnableReceiver, new UserHandle(userId),
+                new IntentFilter(mEnableReceiver.OPA_ENABLE_ACTION), null, null);
+    }
+
+    /**
+     * Unregister AssistantStateReceiver
+     */
+    private void unregisterEnableReceiver() {
+        mContext.unregisterReceiver(mEnableReceiver);
+    }
+
+    /**
+     * Update Assistant enabled state depending on cached value.
+     */
+    private void updateAssistantEnabledState() {
+        boolean isEnabled = UserSettingsUtils.load(mContentResolver);
+        mOpaEnableDispatcher.dispatchOpaEnabled(isEnabled);
+    }
+
+    /**
+     * Register AssistantSettingsObserver for current user.
+     */
+    private void registerSettingsObserver() {
+        mContentResolver.registerContentObserver(Secure.getUriFor(Secure.ASSISTANT),
+                false /* notifyForDescendants */, mContentObserver,
+                KeyguardUpdateMonitor.getCurrentUser());
+    }
+
+    /**
+     * Unregister AssistantSettingsObserver
+     */
+    private void unregisterSettingsObserver() {
+        mContentResolver.unregisterContentObserver(mContentObserver);
+    }
+
+    /**
+     * Content observer that watches for changes to assistant-related settings.
+     */
+    private class AssistantSettingsObserver extends ContentObserver {
+        public AssistantSettingsObserver() {
+            super(new Handler());
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            updateAssistantEnabledState();
+        }
+    }
+}
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/AssistantStateReceiver.java b/packages/SystemUI/opa/src/com/google/android/systemui/AssistantStateReceiver.java
new file mode 100644
index 0000000..f6b2924
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/AssistantStateReceiver.java
@@ -0,0 +1,26 @@
+package com.google.android.systemui;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.internal.app.AssistUtils;
+
+/**
+ * A receiver that receives a broadcast to tell SystemUI whether or not to enable OPA.
+ */
+public class AssistantStateReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "AssistantStateReceiver";
+    public static final String OPA_ENABLE_ACTION = "com.google.android.systemui.OPA_ENABLED";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final boolean enabled = intent.getBooleanExtra("OPA_ENABLED", false);
+        Log.i(TAG, "Received " + intent + " with enabled = " + enabled);
+
+        UserSettingsUtils.save(context.getContentResolver(), enabled);
+        new OpaEnableDispatcher(context, new AssistUtils(context)).dispatchOpaEnabled(enabled);
+    }
+}
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/OpaEnableDispatcher.java b/packages/SystemUI/opa/src/com/google/android/systemui/OpaEnableDispatcher.java
new file mode 100644
index 0000000..ded823b
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/OpaEnableDispatcher.java
@@ -0,0 +1,54 @@
+package com.google.android.systemui;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.view.View;
+
+import com.android.internal.app.AssistUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.statusbar.phone.ButtonDispatcher;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to dispatch the current OPA enabled state to the UI.
+ */
+public class OpaEnableDispatcher {
+
+    private final Context mContext;
+    private final AssistUtils mAssistUtils;
+    private static final String OPA_COMPONENT_NAME = "com.google.android.googlequicksearchbox/" +
+        "com.google.android.voiceinteraction.GsaVoiceInteractionService";
+
+    public OpaEnableDispatcher(Context context, AssistUtils assistUtils) {
+        mContext = context;
+        mAssistUtils = assistUtils;
+    }
+
+    public void dispatchOpaEnabled(boolean enabled) {
+        dispatchUnchecked(enabled && isGsaCurrentAssistant());
+    }
+
+    private void dispatchUnchecked(boolean enabled) {
+        StatusBar bar = ((SystemUIApplication) mContext.getApplicationContext()).getComponent(
+                StatusBar.class);
+        if (bar == null) {
+            return;
+        }
+        ButtonDispatcher homeDispatcher = bar.getNavigationBarView().getHomeButton();
+        ArrayList<View> views = homeDispatcher.getViews();
+        for (int i = 0; i < views.size(); ++i) {
+            View v =  views.get(i);
+            ((OpaLayout) v).setOpaEnabled(enabled);
+        }
+    }
+
+    private boolean isGsaCurrentAssistant() {
+        ComponentName assistant = mAssistUtils.getAssistComponentForUser(
+                KeyguardUpdateMonitor.getCurrentUser());
+        return assistant != null
+                && OPA_COMPONENT_NAME.equals(assistant.flattenToString());
+    }
+}
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/OpaLayout.java b/packages/SystemUI/opa/src/com/google/android/systemui/OpaLayout.java
new file mode 100644
index 0000000..b00cedb
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/OpaLayout.java
@@ -0,0 +1,826 @@
+package com.google.android.systemui;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.MotionEvent;
+import android.view.RenderNodeAnimator;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.phone.NavBarButtonProvider.ButtonInterface;
+import com.android.systemui.statusbar.policy.KeyButtonDrawable;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+/**
+ * Custom ViewGroup intended to replace the Home Button when OPA is enabled.
+ * Implements long-press and tap animations.
+ */
+public class OpaLayout extends FrameLayout implements ButtonInterface {
+    private static final String TAG = "OpaLayout";
+
+    /** Minimum time to spend in diamond animation. */
+    private static final int MIN_DIAMOND_DURATION = 100;
+    /** Length of time to spend in the diamond animation. */
+    private static final int DIAMOND_ANIMATION_DURATION = 200;
+    /** Length of time to spend shrinking the halo. */
+    private static final int HALO_ANIMATION_DURATION = 100;
+    /** Length of time to spend changing Y for straight line animation. */
+    private static final int LINE_ANIMATION_DURATION_Y = 133;
+    /** Length of time to spend changing X for straight line animation. */
+    private static final int LINE_ANIMATION_DURATION_X = 225;
+    /** Length of time for collapse animation for green and blue. */
+    private static final int COLLAPSE_ANIMATION_DURATION_BG = 100;
+    /** Length of time for collapse animation for yellow and red. */
+    private static final int COLLAPSE_ANIMATION_DURATION_RY = 83;
+    /** Length of time for dots fullsize animation. */
+    private static final int DOTS_RESIZE_DURATION = 200;
+    /** Length of time for home button resize animation. */
+    private static final int HOME_RESIZE_DURATION = 83;
+    /** Length of time for home reappear animation. */
+    private static final int HOME_REAPPEAR_DURATION = 150;
+    /** Length of time for retract animation. */
+    private static final int RETRACT_ANIMATION_DURATION = 300;
+    private static final int RETRACT_ALPHA_OFFSET = 50;
+    private static final int ALPHA_ANIMATION_LENGTH = 50;
+
+    /** Offset between the start of collapse animation and the start of the home reappear. */
+    private static final int HOME_REAPPEAR_ANIMATION_OFFSET = 33;
+
+    /** Scale factor for dots size change during diamond animation. */
+    private static final float DIAMOND_DOTS_SCALE_FACTOR = 0.8f;
+    /** Scale factor for home size change during diamond animation. */
+    private static final float DIAMOND_HOME_SCALE_FACTOR = 0.625f;
+    /** Scale factor for halo size change during diamond animation. */
+    private static final float HALO_SCALE_FACTOR = 10.0f/21.0f;
+
+    /** Standard 80/40 interpolator */
+    private final Interpolator mFastOutSlowInInterpolator = Interpolators.FAST_OUT_SLOW_IN;
+    /** 0/80 path interpolator for home button disappearance. */
+    private final Interpolator mHomeDisappearInterpolator = new PathInterpolator(.65f, 0f ,1f ,1f);
+    /** Outgoing interpolator for the collapse animation. */
+    private final Interpolator mCollapseInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
+    /** 100/40 interpolator for dot resize to full size. */
+    private final Interpolator mDotsFullSizeInterpolator = new PathInterpolator(0.4f, 0f, 0f, 1f);
+    /** 100/40 interpolator for retract animation. */
+    private final Interpolator mRetractInterpolator = new PathInterpolator(0.4f, 0f, 0f, 1f);
+    /** Interpolator for diamond animation. */
+    private final Interpolator mDiamondInterpolator = new PathInterpolator(.2f,0f,.2f,1f);
+
+    private static final int ANIMATION_STATE_NONE = 0;
+    private static final int ANIMATION_STATE_DIAMOND = 1;
+    private static final int ANIMATION_STATE_RETRACT = 2;
+    private static final int ANIMATION_STATE_OTHER = 3;
+
+    /** Starts line animation on long press. */
+    private final Runnable mCheckLongPress = new Runnable() {
+        @Override
+        public void run() {
+            if (mIsPressed) {
+                mLongClicked = true;
+            }
+        }
+    };
+
+    /** Starts retract animation before diamond animation has completed. */
+    private final Runnable mRetract = new Runnable() {
+        @Override
+        public void run() {
+            // Cancel the diamond animation. We want to start retract from the current state.
+            cancelCurrentAnimation();
+            startRetractAnimation();
+        }
+    };
+
+    // Time that the animation started.
+    private long mStartTime;
+
+    private View mBlue;
+    private View mRed;
+    private View mYellow;
+    private View mGreen;
+    private ImageView mWhite;
+    private ImageView mHalo;
+    private KeyButtonView mHome;
+
+    private View mTop;
+    private View mBottom;
+    private View mRight;
+    private View mLeft;
+
+    private int mAnimationState = ANIMATION_STATE_NONE;
+
+    private final ArraySet<Animator> mCurrentAnimators = new ArraySet<>();
+    private final ArrayList<View> mAnimatedViews = new ArrayList<>();
+
+    private boolean mIsVertical;
+    private boolean mLongClicked;
+    /** Track pressed state locally. */
+    private boolean mIsPressed;
+    private boolean mOpaEnabled;
+
+    public OpaLayout(@NonNull Context context) {
+        super(context);
+    }
+
+    public OpaLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public OpaLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public OpaLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mBlue = findViewById(R.id.blue);
+        mRed = findViewById(R.id.red);
+        mYellow = findViewById(R.id.yellow);
+        mGreen = findViewById(R.id.green);
+        mWhite = (ImageView) findViewById(R.id.white);
+        mHalo = (ImageView) findViewById(R.id.halo);
+        mHome = (KeyButtonView) findViewById(R.id.home_button);
+        mHalo.setImageDrawable(KeyButtonDrawable.create(
+                new ContextThemeWrapper(getContext(), R.style.DualToneLightTheme)
+                        .getDrawable(R.drawable.halo),
+                new ContextThemeWrapper(getContext(), R.style.DualToneDarkTheme)
+                        .getDrawable(R.drawable.halo)));
+
+        mAnimatedViews.add(mBlue);
+        mAnimatedViews.add(mRed);
+        mAnimatedViews.add(mYellow);
+        mAnimatedViews.add(mGreen);
+        mAnimatedViews.add(mWhite);
+        mAnimatedViews.add(mHalo);
+        skipToStartingValue();
+
+        setOpaEnabled(UserSettingsUtils.load(getContext().getContentResolver()));
+    }
+
+    @Override
+    public void setOnLongClickListener(@Nullable OnLongClickListener l) {
+        // Make sure that the collapse animation is synced with the long click.
+        mHome.setOnLongClickListener(v -> {
+            l.onLongClick(mHome);
+            return true;
+        });
+    }
+
+    @Override
+    public void setOnTouchListener(OnTouchListener l) {
+        mHome.setOnTouchListener(l);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        // Do nothing if opa is not enabled or animators are disabled.
+        if (!mOpaEnabled || !ValueAnimator.areAnimatorsEnabled()) {
+            return false;
+        }
+        final int action = ev.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                // If an animation is in progress, do not allow it to be interrupted by another
+                // down event UNLESS the current animation is a retract (that returns the dots
+                // to a resting state), in which case we finish it immediately and start the new
+                // animation.
+                if (!mCurrentAnimators.isEmpty()) {
+                    if (mAnimationState == ANIMATION_STATE_RETRACT) {
+                        endCurrentAnimation();
+                    } else {
+                        return false;
+                    }
+                }
+                mStartTime = SystemClock.elapsedRealtime();
+                mLongClicked = false;
+                mIsPressed = true;
+                startDiamondAnimation();
+                removeCallbacks(mCheckLongPress);
+                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                if (mAnimationState == ANIMATION_STATE_DIAMOND) {
+                    // Enforce the minimum duration.
+                    final long targetTime = MIN_DIAMOND_DURATION -
+                            (SystemClock.elapsedRealtime() - mStartTime);
+                    removeCallbacks(mRetract);
+                    postDelayed(mRetract, targetTime);
+                    removeCallbacks(mCheckLongPress);
+                    return false;
+                }
+                final boolean doRetract = mIsPressed && !mLongClicked;
+                mIsPressed = false;
+                if (doRetract) {
+                    mRetract.run();
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void setImageDrawable(@Nullable Drawable drawable) {
+        mWhite.setImageDrawable(drawable);
+    }
+
+    @Override
+    public void abortCurrentGesture() {
+        mHome.abortCurrentGesture();
+    }
+
+    private void startDiamondAnimation() {
+        if (isAttachedToWindow()) {
+            mCurrentAnimators.clear();
+            mCurrentAnimators.addAll(getDiamondAnimatorSet());
+            mAnimationState = ANIMATION_STATE_DIAMOND;
+            startAll(mCurrentAnimators);
+        } else {
+            skipToStartingValue();
+        }
+    }
+
+    private void startRetractAnimation() {
+        if (isAttachedToWindow()) {
+            mCurrentAnimators.clear();
+            mCurrentAnimators.addAll(getRetractAnimatorSet());
+            mAnimationState = ANIMATION_STATE_RETRACT;
+            startAll(mCurrentAnimators);
+        } else {
+            skipToStartingValue();
+        }
+    }
+
+    private void startLineAnimation() {
+        if (isAttachedToWindow()) {
+            mCurrentAnimators.clear();
+            mCurrentAnimators.addAll(getLineAnimatorSet());
+            mAnimationState = ANIMATION_STATE_OTHER;
+            startAll(mCurrentAnimators);
+        } else {
+            skipToStartingValue();
+        }
+    }
+
+    private void startCollapseAnimation() {
+        if (isAttachedToWindow()) {
+            mCurrentAnimators.clear();
+            mCurrentAnimators.addAll(getCollapseAnimatorSet());
+            mAnimationState = ANIMATION_STATE_OTHER;
+            startAll(mCurrentAnimators);
+        } else {
+            skipToStartingValue();
+        }
+    }
+
+    /**
+     * @return A RenderNodeAnimator that scales the x of the view by factor.
+     */
+    private Animator getScaleAnimatorX(View v, float factor, int duration,
+            Interpolator interpolator) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.SCALE_X, factor);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    /**
+     * @return A RenderNodeAnimator that scales the y of the view by factor.
+     */
+    private Animator getScaleAnimatorY(View v, float factor, int duration,
+            Interpolator interpolator) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.SCALE_Y, factor);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    /**
+     * @return A RenderNodeAnimator that translates the view deltaX from its current x.
+     */
+    private Animator getDeltaAnimatorX(View v, Interpolator interpolator, float deltaX,
+            int duration) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.X, v.getX() + deltaX);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    /**
+     * @return A RenderNodeAnimator that translates the view deltaY from its current y.
+     */
+    private Animator getDeltaAnimatorY(View v, Interpolator interpolator, float deltaY,
+            int duration) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.Y, v.getY() + deltaY);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    /**
+     * @return a RenderNodeAnimator that moves the view back to its initial inflated X-position.
+     */
+    private Animator getTranslationAnimatorX(View v, Interpolator interpolator, int duration) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.TRANSLATION_X, 0);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    /**
+     * @return a RenderNodeAnimator that moves the view back to its initial inflated Y-position.
+     */
+    private Animator getTranslationAnimatorY(View v, Interpolator interpolator, int duration) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.TRANSLATION_Y, 0);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        return animator;
+    }
+
+    private Animator getAlphaAnimator(View v, float alpha, int duration,
+                                      Interpolator interpolator) {
+        return getAlphaAnimator(v, alpha, duration, 0 /* startDelay */, interpolator);
+    }
+
+    /**
+     * @return a RenderNodeAnimator that moves the view back to its initial inflated Y-position.
+     */
+    private Animator getAlphaAnimator(View v, float alpha, int duration, int startDelay,
+                                      Interpolator interpolator) {
+        final RenderNodeAnimator animator = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, alpha);
+        animator.setTarget(v);
+        animator.setInterpolator(interpolator);
+        animator.setDuration(duration);
+        animator.setStartDelay(startDelay);
+        return animator;
+    }
+
+    private void startAll(ArraySet<Animator> animators) {
+        for (int i = animators.size() - 1; i >= 0; i--) {
+            animators.valueAt(i).start();
+        }
+    }
+
+    /**
+     * Get the pixel value of the given resource dimension (in dp).
+     */
+    private float getPxVal(int id) {
+        return getResources().getDimensionPixelOffset(id);
+    }
+
+    private ArraySet<Animator> getDiamondAnimatorSet() {
+        final ArraySet<Animator> animators = new ArraySet<>();
+
+        // Animate top
+        animators.add(getDeltaAnimatorY(mTop, mDiamondInterpolator,
+                -getPxVal(R.dimen.opa_diamond_translation), DIAMOND_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mTop, DIAMOND_DOTS_SCALE_FACTOR, DIAMOND_ANIMATION_DURATION,
+                mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mTop, DIAMOND_DOTS_SCALE_FACTOR, DIAMOND_ANIMATION_DURATION,
+                mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mTop, 1.0f,
+                ALPHA_ANIMATION_LENGTH, Interpolators.LINEAR));
+
+        // Animate bottom
+        animators.add(getDeltaAnimatorY(mBottom, mDiamondInterpolator,
+                getPxVal(R.dimen.opa_diamond_translation), DIAMOND_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mBottom, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mBottom, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mBottom, 1.0f,
+                ALPHA_ANIMATION_LENGTH, Interpolators.LINEAR));
+
+        // Animate left
+        animators.add(getDeltaAnimatorX(mLeft, mDiamondInterpolator,
+                -getPxVal(R.dimen.opa_diamond_translation), DIAMOND_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mLeft, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mLeft, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mLeft, 1.0f,
+                ALPHA_ANIMATION_LENGTH, Interpolators.LINEAR));
+
+        // Animate right
+        animators.add(getDeltaAnimatorX(mRight, mDiamondInterpolator,
+                getPxVal(R.dimen.opa_diamond_translation), DIAMOND_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mRight, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mRight, DIAMOND_DOTS_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mRight, 1.0f,
+                ALPHA_ANIMATION_LENGTH, Interpolators.LINEAR));
+
+        // Animate home
+        animators.add(getScaleAnimatorX(mWhite, DIAMOND_HOME_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mWhite, DIAMOND_HOME_SCALE_FACTOR,
+                DIAMOND_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+
+        // Animate halo
+        animators.add(getScaleAnimatorX(mHalo, HALO_SCALE_FACTOR,
+                HALO_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mHalo, HALO_SCALE_FACTOR,
+                HALO_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mHalo, 0.0f, HALO_ANIMATION_DURATION,
+                mFastOutSlowInInterpolator));
+
+        getLongestAnim(animators).addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCurrentAnimators.clear();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                startLineAnimation();
+            }
+        });
+        return animators;
+    }
+
+    private ArraySet<Animator> getRetractAnimatorSet() {
+        final ArraySet<Animator> animators = new ArraySet<>();
+
+        // We don't need separate landscape and portrait cases for this animation since we're
+        // animating in both x and y directions in either case (just in case we start retraction
+        // after the diamond is complete).
+
+        // Animate red
+        animators.add(getTranslationAnimatorX(mRed, mRetractInterpolator, RETRACT_ANIMATION_DURATION));
+        animators.add(getTranslationAnimatorY(mRed, mRetractInterpolator, RETRACT_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mRed, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getScaleAnimatorY(mRed, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getAlphaAnimator(mRed, 0.0f, ALPHA_ANIMATION_LENGTH,
+                RETRACT_ALPHA_OFFSET, Interpolators.LINEAR));
+
+        // Animate blue
+        animators.add(
+                getTranslationAnimatorX(mBlue, mRetractInterpolator, RETRACT_ANIMATION_DURATION));
+        animators.add(
+                getTranslationAnimatorY(mBlue, mRetractInterpolator, RETRACT_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mBlue, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getScaleAnimatorY(mBlue, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getAlphaAnimator(mBlue, 0.0f, ALPHA_ANIMATION_LENGTH,
+                RETRACT_ALPHA_OFFSET, Interpolators.LINEAR));
+
+
+        // Animate green
+        animators.add(getTranslationAnimatorX(mGreen, mRetractInterpolator,
+                RETRACT_ANIMATION_DURATION));
+        animators.add(getTranslationAnimatorY(mGreen, mRetractInterpolator,
+                RETRACT_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mGreen, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getScaleAnimatorY(mGreen, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getAlphaAnimator(mGreen, 0.0f, ALPHA_ANIMATION_LENGTH,
+                RETRACT_ALPHA_OFFSET, Interpolators.LINEAR));
+
+        // Animate yellow
+        animators.add(getTranslationAnimatorX(mYellow, mRetractInterpolator,
+                RETRACT_ANIMATION_DURATION));
+        animators.add(getTranslationAnimatorY(mYellow, mRetractInterpolator,
+                RETRACT_ANIMATION_DURATION));
+        animators.add(getScaleAnimatorX(mYellow, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getScaleAnimatorY(mYellow, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getAlphaAnimator(mYellow, 0.0f, ALPHA_ANIMATION_LENGTH,
+                RETRACT_ALPHA_OFFSET, Interpolators.LINEAR));
+
+        // Animate home
+        animators.add(getScaleAnimatorX(mWhite, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+        animators.add(getScaleAnimatorY(mWhite, 1.0f,
+                RETRACT_ANIMATION_DURATION, mRetractInterpolator));
+
+        // Animate halo
+        animators.add(getScaleAnimatorX(mHalo, 1.0f,
+                RETRACT_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getScaleAnimatorY(mHalo, 1.0f,
+                RETRACT_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+        animators.add(getAlphaAnimator(mHalo, 1.0f,
+                RETRACT_ANIMATION_DURATION, mFastOutSlowInInterpolator));
+
+        getLongestAnim(animators).addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mCurrentAnimators.clear();
+                mAnimationState = ANIMATION_STATE_NONE;
+                skipToStartingValue();
+            }
+        });
+        return animators;
+    }
+
+    private ArraySet<Animator> getCollapseAnimatorSet() {
+        final ArraySet<Animator> animators = new ArraySet<>();
+
+        // Animate red
+        animators.add(mIsVertical
+                ? getDeltaAnimatorY(mRed, mCollapseInterpolator,
+                -getPxVal(R.dimen.opa_line_x_collapse_ry), COLLAPSE_ANIMATION_DURATION_RY)
+                : getDeltaAnimatorX(mRed, mCollapseInterpolator,
+                getPxVal(R.dimen.opa_line_x_collapse_ry), COLLAPSE_ANIMATION_DURATION_RY));
+        animators.add(getScaleAnimatorX(mRed, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getScaleAnimatorY(mRed, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getAlphaAnimator(mRed, 0.0f, ALPHA_ANIMATION_LENGTH,
+                HOME_REAPPEAR_ANIMATION_OFFSET, Interpolators.LINEAR));
+
+        // Animate blue
+        animators.add(mIsVertical
+                ? getDeltaAnimatorY(mBlue, mCollapseInterpolator,
+                -getPxVal(R.dimen.opa_line_x_collapse_bg), COLLAPSE_ANIMATION_DURATION_BG)
+                : getDeltaAnimatorX(mBlue, mCollapseInterpolator,
+                getPxVal(R.dimen.opa_line_x_collapse_bg), COLLAPSE_ANIMATION_DURATION_BG));
+        animators.add(getScaleAnimatorX(mBlue, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getScaleAnimatorY(mBlue, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getAlphaAnimator(mBlue, 0.0f, ALPHA_ANIMATION_LENGTH,
+                HOME_REAPPEAR_ANIMATION_OFFSET, Interpolators.LINEAR));
+
+        // Animate yellow
+        animators.add(mIsVertical
+                ? getDeltaAnimatorY(mYellow, mCollapseInterpolator,
+                getPxVal(R.dimen.opa_line_x_collapse_ry), COLLAPSE_ANIMATION_DURATION_RY)
+                : getDeltaAnimatorX(mYellow, mCollapseInterpolator,
+                -getPxVal(R.dimen.opa_line_x_collapse_ry), COLLAPSE_ANIMATION_DURATION_RY));
+        animators.add(getScaleAnimatorX(mYellow, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getScaleAnimatorY(mYellow, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getAlphaAnimator(mYellow, 0.0f, ALPHA_ANIMATION_LENGTH,
+                HOME_REAPPEAR_ANIMATION_OFFSET, Interpolators.LINEAR));
+
+        // Animate green
+        animators.add(mIsVertical
+                ? getDeltaAnimatorY(mGreen, mCollapseInterpolator,
+                getPxVal(R.dimen.opa_line_x_collapse_bg), COLLAPSE_ANIMATION_DURATION_BG)
+                : getDeltaAnimatorX(mGreen, mCollapseInterpolator,
+                -getPxVal(R.dimen.opa_line_x_collapse_bg), COLLAPSE_ANIMATION_DURATION_BG));
+        animators.add(getScaleAnimatorX(mGreen, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getScaleAnimatorY(mGreen, 1.0f, DOTS_RESIZE_DURATION,
+                mDotsFullSizeInterpolator));
+        animators.add(getAlphaAnimator(mGreen, 0.0f, ALPHA_ANIMATION_LENGTH,
+                HOME_REAPPEAR_ANIMATION_OFFSET, Interpolators.LINEAR));
+
+        // Animate home and halo reappearance after a delay.
+        final Animator homeScaleX = getScaleAnimatorX(mWhite, 1.0f, HOME_REAPPEAR_DURATION,
+                mFastOutSlowInInterpolator);
+        final Animator homeScaleY = getScaleAnimatorY(mWhite, 1.0f, HOME_REAPPEAR_DURATION,
+                mFastOutSlowInInterpolator);
+        final Animator haloScaleX = getScaleAnimatorX(mHalo, 1.0f, HOME_REAPPEAR_DURATION,
+                mFastOutSlowInInterpolator);
+        final Animator haloScaleY = getScaleAnimatorY(mHalo, 1.0f, HOME_REAPPEAR_DURATION,
+                mFastOutSlowInInterpolator);
+        final Animator haloAlpha = getAlphaAnimator(mHalo, 1.0f, HOME_REAPPEAR_DURATION,
+                mFastOutSlowInInterpolator);
+        homeScaleX.setStartDelay(HOME_REAPPEAR_ANIMATION_OFFSET);
+        homeScaleY.setStartDelay(HOME_REAPPEAR_ANIMATION_OFFSET);
+        haloScaleX.setStartDelay(HOME_REAPPEAR_ANIMATION_OFFSET);
+        haloScaleY.setStartDelay(HOME_REAPPEAR_ANIMATION_OFFSET);
+        haloAlpha.setStartDelay(HOME_REAPPEAR_ANIMATION_OFFSET);
+        animators.add(homeScaleX);
+        animators.add(homeScaleY);
+        animators.add(haloScaleX);
+        animators.add(haloScaleY);
+        animators.add(haloAlpha);
+
+        getLongestAnim(animators).addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mCurrentAnimators.clear();
+                mAnimationState = ANIMATION_STATE_NONE;
+                skipToStartingValue();
+            }
+        });
+        return animators;
+    }
+
+    private ArraySet<Animator> getLineAnimatorSet() {
+        final ArraySet<Animator> animators = new ArraySet<>();
+        if (mIsVertical) {
+            // Animate red
+            animators.add(getDeltaAnimatorY(mRed, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_x_trans_ry), LINE_ANIMATION_DURATION_X));
+            animators.add(getDeltaAnimatorX(mRed, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_y_translation), LINE_ANIMATION_DURATION_Y));
+
+            // Animate blue
+            animators.add(getDeltaAnimatorY(mBlue, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_x_trans_bg), LINE_ANIMATION_DURATION_X));
+
+            //Animate yellow
+            animators.add(getDeltaAnimatorY(mYellow, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_x_trans_ry), LINE_ANIMATION_DURATION_X));
+            animators.add(getDeltaAnimatorX(mYellow, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_y_translation), LINE_ANIMATION_DURATION_Y));
+
+            // Animate green
+            animators.add(getDeltaAnimatorY(mGreen, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_x_trans_bg), LINE_ANIMATION_DURATION_X));
+        } else {
+            // Animate red
+            animators.add(getDeltaAnimatorX(mRed, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_x_trans_ry), LINE_ANIMATION_DURATION_X));
+            animators.add(getDeltaAnimatorY(mRed, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_y_translation), LINE_ANIMATION_DURATION_Y));
+
+            // Animate blue
+            animators.add(getDeltaAnimatorX(mBlue, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_x_trans_bg), LINE_ANIMATION_DURATION_X));
+
+            //Animate yellow
+            animators.add(getDeltaAnimatorX(mYellow, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_x_trans_ry), LINE_ANIMATION_DURATION_X));
+            animators.add(getDeltaAnimatorY(mYellow, mFastOutSlowInInterpolator,
+                    -getPxVal(R.dimen.opa_line_y_translation), LINE_ANIMATION_DURATION_Y));
+
+            // Animate green
+            animators.add(getDeltaAnimatorX(mGreen, mFastOutSlowInInterpolator,
+                    getPxVal(R.dimen.opa_line_x_trans_bg), LINE_ANIMATION_DURATION_X));
+        }
+
+        // Animate home and halo
+        animators.add(getScaleAnimatorX(mWhite, 0.0f, HOME_RESIZE_DURATION,
+                mHomeDisappearInterpolator));
+        animators.add(getScaleAnimatorY(mWhite, 0.0f, HOME_RESIZE_DURATION,
+                mHomeDisappearInterpolator));
+        animators.add(getScaleAnimatorX(mHalo, 0.0f, HOME_RESIZE_DURATION,
+                mHomeDisappearInterpolator));
+        animators.add(getScaleAnimatorY(mHalo, 0.0f, HOME_RESIZE_DURATION,
+                mHomeDisappearInterpolator));
+
+        getLongestAnim(animators).addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                startCollapseAnimation();
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCurrentAnimators.clear();
+            }
+        });
+        return animators;
+    }
+
+    public void setOpaEnabled(boolean enabled) {
+        Log.i(TAG, "Setting opa enabled to " + enabled);
+        mOpaEnabled = enabled;
+
+        final int visibility = enabled ? VISIBLE : INVISIBLE;
+        mBlue.setVisibility(visibility);
+        mRed.setVisibility(visibility);
+        mYellow.setVisibility(visibility);
+        mGreen.setVisibility(visibility);
+        mHalo.setVisibility(visibility);
+    }
+
+    /**
+     * Cancels the current animation, stopping it in its tracks.
+     */
+    private void cancelCurrentAnimation() {
+        if (!mCurrentAnimators.isEmpty()) {
+            // Do not finish animation since we want to start retract from current state. We only
+            // need to remove the listeners so the animation doesn't jump to the end.
+            for (int i = mCurrentAnimators.size() - 1; i >= 0; i--) {
+                final Animator a = mCurrentAnimators.valueAt(i);
+                a.removeAllListeners();
+                a.cancel();
+            }
+            mCurrentAnimators.clear();
+            mAnimationState = ANIMATION_STATE_NONE;
+        }
+    }
+
+    /**
+     * Ends the current animation, winding it to the end values.
+     */
+    private void endCurrentAnimation() {
+        if (!mCurrentAnimators.isEmpty()) {
+            for (int i = mCurrentAnimators.size() - 1; i >= 0; i--) {
+                final Animator a = mCurrentAnimators.valueAt(i);
+                a.removeAllListeners();
+                a.end();
+            }
+            mCurrentAnimators.clear();
+            mAnimationState = ANIMATION_STATE_NONE;
+        }
+    }
+
+    /**
+     * @return the animator with the longest {@link Animator#getTotalDuration} from the set
+     */
+    private Animator getLongestAnim(ArraySet<Animator> animators) {
+        long longestDuration = Long.MIN_VALUE;
+        Animator longestAnim = null;
+        for (int i = animators.size() - 1; i >= 0; i--) {
+            Animator a = animators.valueAt(i);
+            if (a.getTotalDuration() > longestDuration) {
+                longestAnim = a;
+                longestDuration = a.getTotalDuration();
+            }
+        }
+        return longestAnim;
+    }
+
+    /**
+     * Forcibly moves views to their starting position and values.
+     */
+    private void skipToStartingValue() {
+        final int size = mAnimatedViews.size();
+        View v;
+        for (int i = 0; i < size; ++i) {
+            v = mAnimatedViews.get(i);
+            v.setScaleY(1.0f);
+            v.setScaleX(1.0f);
+            v.setTranslationY(0);
+            v.setTranslationX(0);
+            v.setAlpha(0.0f);
+        }
+
+        mHalo.setAlpha(1.0f);
+        mWhite.setAlpha(1.0f);
+
+        mAnimationState = ANIMATION_STATE_NONE;
+    }
+
+    @Override
+    public void setVertical(boolean vertical) {
+        mIsVertical = vertical;
+
+        if (mIsVertical) {
+            mTop = mGreen;
+            mBottom = mBlue;
+            mRight = mYellow;
+            mLeft = mRed;
+        } else {
+            mTop = mRed;
+            mBottom = mYellow;
+            mLeft = mBlue;
+            mRight = mGreen;
+        }
+    }
+
+    @Override
+    public void setCarMode(boolean carMode) {
+        setOpaEnabled(!carMode);
+    }
+
+    @Override
+    public void setDarkIntensity(float intensity) {
+        if (mWhite.getDrawable() instanceof KeyButtonDrawable) {
+            ((KeyButtonDrawable) mWhite.getDrawable()).setDarkIntensity(intensity);
+        }
+        ((KeyButtonDrawable) mHalo.getDrawable()).setDarkIntensity(intensity);
+
+        // Since we reuse the same drawable for multiple views, we need to invalidate the view
+        // manually.
+        mWhite.invalidate();
+        mHalo.invalidate();
+
+        mHome.setDarkIntensity(intensity);
+    }
+}
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/SystemUIGoogleFactory.java b/packages/SystemUI/opa/src/com/google/android/systemui/SystemUIGoogleFactory.java
new file mode 100644
index 0000000..a7463f1
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/SystemUIGoogleFactory.java
@@ -0,0 +1,23 @@
+package com.google.android.systemui;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+/**
+ * Factory for Google specific behavior classes.
+ */
+public class SystemUIGoogleFactory extends SystemUIFactory {
+
+    @Override
+    public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+            Context context) {
+        providers.put(AssistManager.class, () -> new AssistManagerGoogle(
+                Dependency.get(DeviceProvisionedController.class), context));
+    }
+}
diff --git a/packages/SystemUI/opa/src/com/google/android/systemui/UserSettingsUtils.java b/packages/SystemUI/opa/src/com/google/android/systemui/UserSettingsUtils.java
new file mode 100644
index 0000000..5a0dcf7
--- /dev/null
+++ b/packages/SystemUI/opa/src/com/google/android/systemui/UserSettingsUtils.java
@@ -0,0 +1,25 @@
+package com.google.android.systemui;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+
+/**
+ * A utility class to read/write the most recent status of Assistant from/to the persistent store.
+ */
+public class UserSettingsUtils {
+    private static final String OPA_ENABLED_SETTING = "systemui.google.opa_enabled";
+
+    public static void save(ContentResolver cr, boolean enabled) {
+        final int user = KeyguardUpdateMonitor.getCurrentUser();
+        final int en = enabled ? 1 : 0;
+        Settings.Secure.putIntForUser(cr, OPA_ENABLED_SETTING, en, user);
+    }
+
+    public static boolean load(ContentResolver cr) {
+        final int user = KeyguardUpdateMonitor.getCurrentUser();
+        final int en = Settings.Secure.getIntForUser(cr, OPA_ENABLED_SETTING, 0, user);
+        return en != 0;
+    }
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 8cc2ce2..e480b59 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -15,6 +15,7 @@
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.car.CarSystemUIFactory
 -keep class com.android.systemui.SystemUIFactory
+-keep class com.google.android.systemui.SystemUIGoogleFactory
 
 -keepclassmembers class ** {
     public void onBusEvent(**);

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值