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

Commit ae79ab9d authored by Beverly's avatar Beverly
Browse files

Basic workspace wireless charging animation

(No animation on ambient or lockscreen yet)

Test: manual (testing w/o wireless charger, set DEBUG_WIRELESS=true)
Bug: 67598445
Change-Id: Ifeef72805930187b8f78e72fd44bd7da25724e0c
parent 9712788a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ oneway interface IStatusBar
    void animateCollapsePanels();
    void togglePanel();

    void showChargingAnimation(int batteryLevel);

    /**
     * Notifies the status bar of a System UI visibility flag change.
     *
+56 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
** Copyright 2018, 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.
*/
-->

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Circle animation -->
    <com.android.systemui.charging.WirelessChargingView
        android:id="@+id/wireless_charging_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:elevation="4dp"/>

    <!-- Text inside circle -->
    <LinearLayout
        android:id="@+id/wireless_charging_text_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/wireless_charging_percentage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textSize="32sp"
            android:textColor="?attr/wallpaperTextColor"/>

        <TextView
            android:id="@+id/wireless_charging_secondary_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="?attr/wallpaperTextColor"/>
    </LinearLayout>

</FrameLayout>
 No newline at end of file
+8 −0
Original line number Diff line number Diff line
@@ -893,4 +893,12 @@
    <dimen name="fingerprint_dialog_icon_size">44dp</dimen>
    <dimen name="fingerprint_dialog_fp_icon_size">60dp</dimen>
    <dimen name="fingerprint_dialog_animation_translation_offset">350dp</dimen>

    <!-- WirelessCharging Animation values -->
    <!-- Starting text size of batteryLevel for wireless charging animation -->
    <dimen name="config_batteryLevelTextSizeStart" format="float">5.0</dimen>
    <!-- Ending text size of batteryLevel for wireless charging animation -->
    <dimen name="config_batteryLevelTextSizeEnd" format="float">32.0</dimen>
    <!-- Wireless Charging battery level text animation duration -->
    <integer name="config_batteryLevelTextAnimationDuration">400</integer>
</resources>
+213 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.charging;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Slog;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;

/**
 * A WirelessChargingAnimation is a view containing view + animation for wireless charging.
 * @hide
 */
public class WirelessChargingAnimation {

    public static final long DURATION = 1400;
    private static final String TAG = "WirelessChargingView";
    private static final boolean LOCAL_LOGV = false;

    private final WirelessChargingView mCurrentWirelessChargingView;
    private static WirelessChargingView mPreviousWirelessChargingView;

    /**
     * Constructs an empty WirelessChargingAnimation object.  If looper is null,
     * Looper.myLooper() is used.  Must set
     * {@link WirelessChargingAnimation#mCurrentWirelessChargingView}
     * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}.
     * @hide
     */
    public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, int
            batteryLevel) {
        mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
                batteryLevel);
    }

    /**
     * Creates a wireless charging animation object populated with next view.
     * @hide
     */
    public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
            @Nullable Looper looper, int batteryLevel) {
        return new WirelessChargingAnimation(context, looper, batteryLevel);
    }

    /**
     * Show the view for the specified duration.
     */
    public void show() {
        if (mCurrentWirelessChargingView == null ||
                mCurrentWirelessChargingView.mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        if (mPreviousWirelessChargingView != null) {
            mPreviousWirelessChargingView.hide(0);
        }

        mPreviousWirelessChargingView = mCurrentWirelessChargingView;
        mCurrentWirelessChargingView.show();
        mCurrentWirelessChargingView.hide(DURATION);
    }

    private static class WirelessChargingView {
        private static final int SHOW = 0;
        private static final int HIDE = 1;

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        private final Handler mHandler;

        private int mGravity;

        private View mView;
        private View mNextView;
        private WindowManager mWM;

        public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel) {
            mNextView = new WirelessChargingLayout(context, batteryLevel);
            mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;

            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;

            params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
            params.setTitle("Charging Animation");
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_DIM_BEHIND;

            params.dimAmount = .3f;

            if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            "Can't display wireless animation on a thread that has not called "
                                    + "Looper.prepare()");
                }
            }

            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            handleShow();
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            break;
                        }
                    }
                }
            };
        }

        public void show() {
            if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(SHOW).sendToTarget();
        }

        public void hide(long duration) {
            if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
        }

        private void handleShow() {
            if (LOCAL_LOGV) {
                Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
                        + mNextView);
            }

            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity,
                        config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = DURATION;

                if (mView.getParent() != null) {
                    if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);

                try {
                    mWM.addView(mView, mParams);
                } catch (WindowManager.BadTokenException e) {
                    Slog.d(TAG, "Unable to add wireless charging view. " + e);
                }
            }
        }

        private void handleHide() {
            if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                if (mView.getParent() != null) {
                    if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeViewImmediate(mView);
                }

                mView = null;
            }
        }
    }
}
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.charging;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.android.systemui.R;

import java.text.NumberFormat;

/**
 * @hide
 */
public class WirelessChargingLayout extends FrameLayout {
    private final static int UNKNOWN_BATTERY_LEVEL = -1;

    public WirelessChargingLayout(Context context) {
        super(context);
        init(context, null);
    }

    public WirelessChargingLayout(Context context, int batterylLevel) {
        super(context);
        init(context, null, batterylLevel);
    }

    public WirelessChargingLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context c, AttributeSet attrs) {
        init(c, attrs, -1);
    }

    private void init(Context c, AttributeSet attrs, int batteryLevel) {
        final int mBatteryLevel = batteryLevel;

        inflate(c, R.layout.wireless_charging_layout, this);

        // where the circle animation occurs:
        final WirelessChargingView mChargingView = findViewById(R.id.wireless_charging_view);

        // amount of battery:
        final TextView mPercentage = findViewById(R.id.wireless_charging_percentage);

        // (optional) time until full charge if available
        final TextView mSecondaryText = findViewById(R.id.wireless_charging_secondary_text);

        if (batteryLevel != UNKNOWN_BATTERY_LEVEL) {
            mPercentage.setText(NumberFormat.getPercentInstance().format(mBatteryLevel / 100f));

            ValueAnimator animator = ObjectAnimator.ofFloat(mPercentage, "textSize",
                    getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeStart),
                    getContext().getResources().getFloat(R.dimen.config_batteryLevelTextSizeEnd));

            animator.setDuration((long) getContext().getResources().getInteger(
                    R.integer.config_batteryLevelTextAnimationDuration));
            animator.start();
        }
    }
}
Loading