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