blob: 508262d4ddec6703bf0681d04fb7fae1ab0e0338 [file] [log] [blame]
/*
* 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.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.WindowManager;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
* @hide
*/
public class WirelessChargingAnimation {
public static final long DURATION = 1500;
private static final String TAG = "WirelessChargingView";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final WirelessChargingView mCurrentWirelessChargingView;
private static WirelessChargingView mPreviousWirelessChargingView;
public interface Callback {
void onAnimationStarting();
void onAnimationEnded();
}
/**
* 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 transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
UiEventLogger uiEventLogger) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger);
}
/**
* Creates a wireless charging animation object populated with next view.
*
* @hide
*/
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
@Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
Callback callback, boolean isDozing, UiEventLogger uiEventLogger) {
return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
batteryLevel, callback, isDozing, uiEventLogger);
}
/**
* Show the view for the specified duration.
*/
public void show(long delay) {
if (mCurrentWirelessChargingView == null ||
mCurrentWirelessChargingView.mNextView == null) {
throw new RuntimeException("setView must have been called");
}
if (mPreviousWirelessChargingView != null) {
mPreviousWirelessChargingView.hide(0);
}
mPreviousWirelessChargingView = mCurrentWirelessChargingView;
mCurrentWirelessChargingView.show(delay);
mCurrentWirelessChargingView.hide(delay + 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 final UiEventLogger mUiEventLogger;
private int mGravity;
private WirelessChargingLayout mView;
private WirelessChargingLayout mNextView;
private WindowManager mWM;
private Callback mCallback;
public WirelessChargingView(Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback,
boolean isDozing, UiEventLogger uiEventLogger) {
mCallback = callback;
mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
isDozing);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
mUiEventLogger = uiEventLogger;
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
params.setTitle("Charging Animation");
params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
params.setFitInsetsTypes(0 /* ignore all system bar insets */);
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
params.setTrustedOverlay();
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(long delay) {
if (DEBUG) Slog.d(TAG, "SHOW: " + this);
mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay);
}
public void hide(long duration) {
mHandler.removeMessages(HIDE);
if (DEBUG) Slog.d(TAG, "HIDE: " + this);
mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
}
private void handleShow() {
if (DEBUG) {
Slog.d(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);
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = DURATION;
if (mView.getParent() != null) {
if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
if (DEBUG) Slog.d(TAG, "ADD! " + mView + " in " + this);
try {
if (mCallback != null) {
mCallback.onAnimationStarting();
}
mWM.addView(mView, mParams);
mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED);
} catch (WindowManager.BadTokenException e) {
Slog.d(TAG, "Unable to add wireless charging view. " + e);
}
}
}
private void handleHide() {
if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
if (mView != null) {
if (mView.getParent() != null) {
if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this);
if (mCallback != null) {
mCallback.onAnimationEnded();
}
mWM.removeViewImmediate(mView);
}
mView = null;
}
}
enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "Wireless charging ripple effect played")
WIRELESS_RIPPLE_PLAYED(830);
private final int mInt;
WirelessChargingRippleEvent(int id) {
mInt = id;
}
@Override public int getId() {
return mInt;
}
}
}
}