1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.charging; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.graphics.PixelFormat; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.util.Log; 27 import android.util.Slog; 28 import android.view.Gravity; 29 import android.view.WindowManager; 30 31 import com.android.internal.logging.UiEvent; 32 import com.android.internal.logging.UiEventLogger; 33 import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape; 34 35 /** 36 * A WirelessChargingAnimation is a view containing view + animation for wireless charging. 37 * @hide 38 */ 39 public class WirelessChargingAnimation { 40 public static final int UNKNOWN_BATTERY_LEVEL = -1; 41 public static final long DURATION = 1500; 42 private static final String TAG = "WirelessChargingView"; 43 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 44 45 private final WirelessChargingView mCurrentWirelessChargingView; 46 private static WirelessChargingView mPreviousWirelessChargingView; 47 48 public interface Callback { onAnimationStarting()49 void onAnimationStarting(); onAnimationEnded()50 void onAnimationEnded(); 51 } 52 53 /** 54 * Constructs an empty WirelessChargingAnimation object. If looper is null, 55 * Looper.myLooper() is used. Must set 56 * {@link WirelessChargingAnimation#mCurrentWirelessChargingView} 57 * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}. 58 * @hide 59 */ WirelessChargingAnimation(@onNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger)60 private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, 61 int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, 62 RippleShape rippleShape, UiEventLogger uiEventLogger) { 63 mCurrentWirelessChargingView = new WirelessChargingView(context, looper, 64 transmittingBatteryLevel, batteryLevel, callback, isDozing, 65 rippleShape, uiEventLogger); 66 } 67 68 /** 69 * Creates a wireless charging animation object populated with next view. 70 * 71 * @hide 72 */ makeWirelessChargingAnimation(@onNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger)73 public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, 74 @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, 75 Callback callback, boolean isDozing, RippleShape rippleShape, 76 UiEventLogger uiEventLogger) { 77 return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel, 78 batteryLevel, callback, isDozing, rippleShape, uiEventLogger); 79 } 80 81 /** 82 * Creates a charging animation object using mostly default values for non-dozing and unknown 83 * battery level without charging number shown. 84 */ makeChargingAnimationWithNoBatteryLevel( @onNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger)85 public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel( 86 @NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger) { 87 return makeWirelessChargingAnimation(context, null, 88 UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false, 89 rippleShape, uiEventLogger); 90 } 91 92 /** 93 * Show the view for the specified duration. 94 */ show(long delay)95 public void show(long delay) { 96 if (mCurrentWirelessChargingView == null || 97 mCurrentWirelessChargingView.mNextView == null) { 98 throw new RuntimeException("setView must have been called"); 99 } 100 101 if (mPreviousWirelessChargingView != null) { 102 mPreviousWirelessChargingView.hide(0); 103 } 104 105 mPreviousWirelessChargingView = mCurrentWirelessChargingView; 106 mCurrentWirelessChargingView.show(delay); 107 mCurrentWirelessChargingView.hide(delay + DURATION); 108 } 109 110 private static class WirelessChargingView { 111 private static final int SHOW = 0; 112 private static final int HIDE = 1; 113 114 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); 115 private final Handler mHandler; 116 private final UiEventLogger mUiEventLogger; 117 118 private int mGravity; 119 private WirelessChargingLayout mView; 120 private WirelessChargingLayout mNextView; 121 private WindowManager mWM; 122 private Callback mCallback; 123 WirelessChargingView(Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger)124 public WirelessChargingView(Context context, @Nullable Looper looper, 125 int transmittingBatteryLevel, int batteryLevel, Callback callback, 126 boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger) { 127 mCallback = callback; 128 mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, 129 isDozing, rippleShape); 130 mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; 131 mUiEventLogger = uiEventLogger; 132 133 final WindowManager.LayoutParams params = mParams; 134 params.height = WindowManager.LayoutParams.MATCH_PARENT; 135 params.width = WindowManager.LayoutParams.MATCH_PARENT; 136 params.format = PixelFormat.TRANSLUCENT; 137 params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; 138 params.setTitle("Charging Animation"); 139 params.layoutInDisplayCutoutMode = 140 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 141 params.setFitInsetsTypes(0 /* ignore all system bar insets */); 142 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 143 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 144 params.setTrustedOverlay(); 145 146 if (looper == null) { 147 // Use Looper.myLooper() if looper is not specified. 148 looper = Looper.myLooper(); 149 if (looper == null) { 150 throw new RuntimeException( 151 "Can't display wireless animation on a thread that has not called " 152 + "Looper.prepare()"); 153 } 154 } 155 156 mHandler = new Handler(looper, null) { 157 @Override 158 public void handleMessage(Message msg) { 159 switch (msg.what) { 160 case SHOW: { 161 handleShow(); 162 break; 163 } 164 case HIDE: { 165 handleHide(); 166 // Don't do this in handleHide() because it is also invoked by 167 // handleShow() 168 mNextView = null; 169 break; 170 } 171 } 172 } 173 }; 174 } 175 show(long delay)176 public void show(long delay) { 177 if (DEBUG) Slog.d(TAG, "SHOW: " + this); 178 mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay); 179 } 180 hide(long duration)181 public void hide(long duration) { 182 mHandler.removeMessages(HIDE); 183 184 if (DEBUG) Slog.d(TAG, "HIDE: " + this); 185 mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration); 186 } 187 handleShow()188 private void handleShow() { 189 if (DEBUG) { 190 Slog.d(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" 191 + mNextView); 192 } 193 194 if (mView != mNextView) { 195 // remove the old view if necessary 196 handleHide(); 197 mView = mNextView; 198 Context context = mView.getContext().getApplicationContext(); 199 String packageName = mView.getContext().getOpPackageName(); 200 if (context == null) { 201 context = mView.getContext(); 202 } 203 mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 204 mParams.packageName = packageName; 205 mParams.hideTimeoutMilliseconds = DURATION; 206 207 if (mView.getParent() != null) { 208 if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); 209 mWM.removeView(mView); 210 } 211 if (DEBUG) Slog.d(TAG, "ADD! " + mView + " in " + this); 212 213 try { 214 if (mCallback != null) { 215 mCallback.onAnimationStarting(); 216 } 217 mWM.addView(mView, mParams); 218 mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED); 219 } catch (WindowManager.BadTokenException e) { 220 Slog.d(TAG, "Unable to add wireless charging view. " + e); 221 } 222 } 223 } 224 handleHide()225 private void handleHide() { 226 if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 227 if (mView != null) { 228 if (mView.getParent() != null) { 229 if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); 230 if (mCallback != null) { 231 mCallback.onAnimationEnded(); 232 } 233 mWM.removeViewImmediate(mView); 234 } 235 236 mView = null; 237 } 238 } 239 240 enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum { 241 @UiEvent(doc = "Wireless charging ripple effect played") 242 WIRELESS_RIPPLE_PLAYED(830); 243 244 private final int mInt; WirelessChargingRippleEvent(int id)245 WirelessChargingRippleEvent(int id) { 246 mInt = id; 247 } 248 getId()249 @Override public int getId() { 250 return mInt; 251 } 252 } 253 } 254 } 255