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 34 /** 35 * A WirelessChargingAnimation is a view containing view + animation for wireless charging. 36 * @hide 37 */ 38 public class WirelessChargingAnimation { 39 40 public static final long DURATION = 1500; 41 private static final String TAG = "WirelessChargingView"; 42 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 43 44 private final WirelessChargingView mCurrentWirelessChargingView; 45 private static WirelessChargingView mPreviousWirelessChargingView; 46 47 public interface Callback { onAnimationStarting()48 void onAnimationStarting(); onAnimationEnded()49 void onAnimationEnded(); 50 } 51 52 /** 53 * Constructs an empty WirelessChargingAnimation object. If looper is null, 54 * Looper.myLooper() is used. Must set 55 * {@link WirelessChargingAnimation#mCurrentWirelessChargingView} 56 * before calling {@link #show} - can be done through {@link #makeWirelessChargingAnimation}. 57 * @hide 58 */ WirelessChargingAnimation(@onNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, UiEventLogger uiEventLogger)59 public WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper, 60 int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, 61 UiEventLogger uiEventLogger) { 62 mCurrentWirelessChargingView = new WirelessChargingView(context, looper, 63 transmittingBatteryLevel, batteryLevel, callback, isDozing, uiEventLogger); 64 } 65 66 /** 67 * Creates a wireless charging animation object populated with next view. 68 * 69 * @hide 70 */ makeWirelessChargingAnimation(@onNull Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, UiEventLogger uiEventLogger)71 public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context, 72 @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, 73 Callback callback, boolean isDozing, UiEventLogger uiEventLogger) { 74 return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel, 75 batteryLevel, callback, isDozing, uiEventLogger); 76 } 77 78 /** 79 * Show the view for the specified duration. 80 */ show(long delay)81 public void show(long delay) { 82 if (mCurrentWirelessChargingView == null || 83 mCurrentWirelessChargingView.mNextView == null) { 84 throw new RuntimeException("setView must have been called"); 85 } 86 87 if (mPreviousWirelessChargingView != null) { 88 mPreviousWirelessChargingView.hide(0); 89 } 90 91 mPreviousWirelessChargingView = mCurrentWirelessChargingView; 92 mCurrentWirelessChargingView.show(delay); 93 mCurrentWirelessChargingView.hide(delay + DURATION); 94 } 95 96 private static class WirelessChargingView { 97 private static final int SHOW = 0; 98 private static final int HIDE = 1; 99 100 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); 101 private final Handler mHandler; 102 private final UiEventLogger mUiEventLogger; 103 104 private int mGravity; 105 private WirelessChargingLayout mView; 106 private WirelessChargingLayout mNextView; 107 private WindowManager mWM; 108 private Callback mCallback; 109 WirelessChargingView(Context context, @Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing, UiEventLogger uiEventLogger)110 public WirelessChargingView(Context context, @Nullable Looper looper, 111 int transmittingBatteryLevel, int batteryLevel, Callback callback, 112 boolean isDozing, UiEventLogger uiEventLogger) { 113 mCallback = callback; 114 mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel, 115 isDozing); 116 mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER; 117 mUiEventLogger = uiEventLogger; 118 119 final WindowManager.LayoutParams params = mParams; 120 params.height = WindowManager.LayoutParams.MATCH_PARENT; 121 params.width = WindowManager.LayoutParams.MATCH_PARENT; 122 params.format = PixelFormat.TRANSLUCENT; 123 params.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; 124 params.setTitle("Charging Animation"); 125 params.layoutInDisplayCutoutMode = 126 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 127 params.setFitInsetsTypes(0 /* ignore all system bar insets */); 128 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 129 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 130 params.setTrustedOverlay(); 131 132 if (looper == null) { 133 // Use Looper.myLooper() if looper is not specified. 134 looper = Looper.myLooper(); 135 if (looper == null) { 136 throw new RuntimeException( 137 "Can't display wireless animation on a thread that has not called " 138 + "Looper.prepare()"); 139 } 140 } 141 142 mHandler = new Handler(looper, null) { 143 @Override 144 public void handleMessage(Message msg) { 145 switch (msg.what) { 146 case SHOW: { 147 handleShow(); 148 break; 149 } 150 case HIDE: { 151 handleHide(); 152 // Don't do this in handleHide() because it is also invoked by 153 // handleShow() 154 mNextView = null; 155 break; 156 } 157 } 158 } 159 }; 160 } 161 show(long delay)162 public void show(long delay) { 163 if (DEBUG) Slog.d(TAG, "SHOW: " + this); 164 mHandler.sendMessageDelayed(Message.obtain(mHandler, SHOW), delay); 165 } 166 hide(long duration)167 public void hide(long duration) { 168 mHandler.removeMessages(HIDE); 169 170 if (DEBUG) Slog.d(TAG, "HIDE: " + this); 171 mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration); 172 } 173 handleShow()174 private void handleShow() { 175 if (DEBUG) { 176 Slog.d(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" 177 + mNextView); 178 } 179 180 if (mView != mNextView) { 181 // remove the old view if necessary 182 handleHide(); 183 mView = mNextView; 184 Context context = mView.getContext().getApplicationContext(); 185 String packageName = mView.getContext().getOpPackageName(); 186 if (context == null) { 187 context = mView.getContext(); 188 } 189 mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 190 mParams.packageName = packageName; 191 mParams.hideTimeoutMilliseconds = DURATION; 192 193 if (mView.getParent() != null) { 194 if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); 195 mWM.removeView(mView); 196 } 197 if (DEBUG) Slog.d(TAG, "ADD! " + mView + " in " + this); 198 199 try { 200 if (mCallback != null) { 201 mCallback.onAnimationStarting(); 202 } 203 mWM.addView(mView, mParams); 204 mUiEventLogger.log(WirelessChargingRippleEvent.WIRELESS_RIPPLE_PLAYED); 205 } catch (WindowManager.BadTokenException e) { 206 Slog.d(TAG, "Unable to add wireless charging view. " + e); 207 } 208 } 209 } 210 handleHide()211 private void handleHide() { 212 if (DEBUG) Slog.d(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 213 if (mView != null) { 214 if (mView.getParent() != null) { 215 if (DEBUG) Slog.d(TAG, "REMOVE! " + mView + " in " + this); 216 if (mCallback != null) { 217 mCallback.onAnimationEnded(); 218 } 219 mWM.removeViewImmediate(mView); 220 } 221 222 mView = null; 223 } 224 } 225 226 enum WirelessChargingRippleEvent implements UiEventLogger.UiEventEnum { 227 @UiEvent(doc = "Wireless charging ripple effect played") 228 WIRELESS_RIPPLE_PLAYED(830); 229 230 private final int mInt; WirelessChargingRippleEvent(int id)231 WirelessChargingRippleEvent(int id) { 232 mInt = id; 233 } 234 getId()235 @Override public int getId() { 236 return mInt; 237 } 238 } 239 } 240 } 241