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 LOCAL_LOGV = false;
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 
99         private View mView;
100         private View mNextView;
101         private WindowManager mWM;
102         private Callback mCallback;
103 
WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel, Callback callback, boolean isDozing)104         public WirelessChargingView(Context context, @Nullable Looper looper, int batteryLevel,
105                 Callback callback, boolean isDozing) {
106             mCallback = callback;
107             mNextView = new WirelessChargingLayout(context, batteryLevel, isDozing);
108             mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
109 
110             final WindowManager.LayoutParams params = mParams;
111             params.height = WindowManager.LayoutParams.WRAP_CONTENT;
112             params.width = WindowManager.LayoutParams.MATCH_PARENT;
113             params.format = PixelFormat.TRANSLUCENT;
114 
115             params.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
116             params.setTitle("Charging Animation");
117             params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
118                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
119                     | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
120 
121             params.dimAmount = .3f;
122 
123             if (looper == null) {
124                 // Use Looper.myLooper() if looper is not specified.
125                 looper = Looper.myLooper();
126                 if (looper == null) {
127                     throw new RuntimeException(
128                             "Can't display wireless animation on a thread that has not called "
129                                     + "Looper.prepare()");
130                 }
131             }
132 
133             mHandler = new Handler(looper, null) {
134                 @Override
135                 public void handleMessage(Message msg) {
136                     switch (msg.what) {
137                         case SHOW: {
138                             handleShow();
139                             break;
140                         }
141                         case HIDE: {
142                             handleHide();
143                             // Don't do this in handleHide() because it is also invoked by
144                             // handleShow()
145                             mNextView = null;
146                             break;
147                         }
148                     }
149                 }
150             };
151         }
152 
show()153         public void show() {
154             if (LOCAL_LOGV) Log.v(TAG, "SHOW: " + this);
155             mHandler.obtainMessage(SHOW).sendToTarget();
156         }
157 
hide(long duration)158         public void hide(long duration) {
159             mHandler.removeMessages(HIDE);
160 
161             if (LOCAL_LOGV) Log.v(TAG, "HIDE: " + this);
162             mHandler.sendMessageDelayed(Message.obtain(mHandler, HIDE), duration);
163         }
164 
handleShow()165         private void handleShow() {
166             if (LOCAL_LOGV) {
167                 Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView="
168                         + mNextView);
169             }
170 
171             if (mView != mNextView) {
172                 // remove the old view if necessary
173                 handleHide();
174                 mView = mNextView;
175                 Context context = mView.getContext().getApplicationContext();
176                 String packageName = mView.getContext().getOpPackageName();
177                 if (context == null) {
178                     context = mView.getContext();
179                 }
180                 mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
181                 mParams.packageName = packageName;
182                 mParams.hideTimeoutMilliseconds = DURATION;
183 
184                 if (mView.getParent() != null) {
185                     if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
186                     mWM.removeView(mView);
187                 }
188                 if (LOCAL_LOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
189 
190                 try {
191                     if (mCallback != null) {
192                         mCallback.onAnimationStarting();
193                     }
194                     mWM.addView(mView, mParams);
195                 } catch (WindowManager.BadTokenException e) {
196                     Slog.d(TAG, "Unable to add wireless charging view. " + e);
197                 }
198             }
199         }
200 
handleHide()201         private void handleHide() {
202             if (LOCAL_LOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
203             if (mView != null) {
204                 if (mView.getParent() != null) {
205                     if (LOCAL_LOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
206                     if (mCallback != null) {
207                         mCallback.onAnimationEnded();
208                     }
209                     mWM.removeViewImmediate(mView);
210                 }
211 
212                 mView = null;
213             }
214         }
215     }
216 }
217