1 package com.android.packageinstaller.permission.ui.wear;
2 
3 import android.animation.ObjectAnimator;
4 import android.animation.PropertyValuesHolder;
5 import android.content.Context;
6 import android.graphics.drawable.Drawable;
7 import android.graphics.drawable.Icon;
8 import android.os.Handler;
9 import android.os.Looper;
10 import android.os.Message;
11 import android.text.TextUtils;
12 import android.util.Log;
13 import android.view.LayoutInflater;
14 import android.view.View;
15 import android.view.ViewGroup;
16 import android.view.ViewTreeObserver;
17 import android.view.animation.AnimationUtils;
18 import android.view.animation.Interpolator;
19 import android.widget.Button;
20 import android.widget.ImageView;
21 import android.widget.ScrollView;
22 import android.widget.TextView;
23 
24 import com.android.packageinstaller.R;
25 
26 public abstract class ConfirmationViewHandler implements
27         Handler.Callback,
28         View.OnClickListener,
29         ViewTreeObserver.OnScrollChangedListener,
30         ViewTreeObserver.OnGlobalLayoutListener {
31     private static final String TAG = "ConfirmationViewHandler";
32 
33     public static final int MODE_HORIZONTAL_BUTTONS = 0;
34     public static final int MODE_VERTICAL_BUTTONS = 1;
35 
36     private static final int MSG_SHOW_BUTTON_BAR = 1001;
37     private static final int MSG_HIDE_BUTTON_BAR = 1002;
38     private static final long HIDE_ANIM_DURATION = 500;
39 
40     private View mRoot;
41     private TextView mCurrentPageText;
42     private ImageView mIcon;
43     private TextView mMessage;
44     private ScrollView mScrollingContainer;
45     private ViewGroup mContent;
46     private ViewGroup mHorizontalButtonBar;
47     private ViewGroup mVerticalButtonBar;
48     private Button mVerticalButton1;
49     private Button mVerticalButton2;
50     private Button mVerticalButton3;
51     private View mButtonBarContainer;
52 
53     private Context mContext;
54 
55     private Handler mHideHandler;
56     private Interpolator mInterpolator;
57     private float mButtonBarFloatingHeight;
58     private ObjectAnimator mButtonBarAnimator;
59     private float mCurrentTranslation;
60     private boolean mHiddenBefore;
61 
62     // TODO: Move these into a builder
63     /** In the 2 button layout, this is allow button */
onButton1()64     public abstract void onButton1();
65     /** In the 2 button layout, this is deny button */
onButton2()66     public abstract void onButton2();
onButton3()67     public abstract void onButton3();
getVerticalButton1Text()68     public abstract CharSequence getVerticalButton1Text();
getVerticalButton2Text()69     public abstract CharSequence getVerticalButton2Text();
getVerticalButton3Text()70     public abstract CharSequence getVerticalButton3Text();
getVerticalButton1Icon()71     public abstract Drawable getVerticalButton1Icon();
getVerticalButton2Icon()72     public abstract Drawable getVerticalButton2Icon();
getVerticalButton3Icon()73     public abstract Drawable getVerticalButton3Icon();
getCurrentPageText()74     public abstract CharSequence getCurrentPageText();
getPermissionIcon()75     public abstract Icon getPermissionIcon();
getMessage()76     public abstract CharSequence getMessage();
77 
ConfirmationViewHandler(Context context)78     public ConfirmationViewHandler(Context context) {
79         mContext = context;
80     }
81 
createView()82     public View createView() {
83         mRoot = LayoutInflater.from(mContext).inflate(R.layout.confirmation_dialog, null);
84 
85         mMessage = (TextView) mRoot.findViewById(R.id.message);
86         mCurrentPageText = (TextView) mRoot.findViewById(R.id.current_page_text);
87         mIcon = (ImageView) mRoot.findViewById(R.id.icon);
88         mButtonBarContainer = mRoot.findViewById(R.id.button_bar_container);
89         mContent = (ViewGroup) mRoot.findViewById(R.id.content);
90         mScrollingContainer = (ScrollView) mRoot.findViewById(R.id.scrolling_container);
91         mHorizontalButtonBar = (ViewGroup) mRoot.findViewById(R.id.horizontal_button_bar);
92         mVerticalButtonBar = (ViewGroup) mRoot.findViewById(R.id.vertical_button_bar);
93 
94         Button horizontalAllow = (Button) mRoot.findViewById(R.id.permission_allow_button);
95         Button horizontalDeny = (Button) mRoot.findViewById(R.id.permission_deny_button);
96         horizontalAllow.setOnClickListener(this);
97         horizontalDeny.setOnClickListener(this);
98 
99         mVerticalButton1 = (Button) mRoot.findViewById(R.id.vertical_button1);
100         mVerticalButton2 = (Button) mRoot.findViewById(R.id.vertical_button2);
101         mVerticalButton3 = (Button) mRoot.findViewById(R.id.vertical_button3);
102         mVerticalButton1.setOnClickListener(this);
103         mVerticalButton2.setOnClickListener(this);
104         mVerticalButton3.setOnClickListener(this);
105 
106         mInterpolator = AnimationUtils.loadInterpolator(mContext,
107                 android.R.interpolator.fast_out_slow_in);
108         mButtonBarFloatingHeight = mContext.getResources().getDimension(
109                 R.dimen.conf_diag_floating_height);
110         mHideHandler = new Handler(Looper.getMainLooper(), this);
111 
112         mScrollingContainer.getViewTreeObserver().addOnScrollChangedListener(this);
113         mRoot.getViewTreeObserver().addOnGlobalLayoutListener(this);
114 
115         return mRoot;
116     }
117 
118     /**
119      * Child class should override this for other modes.  Call invalidate() to update the UI to the
120      * new button mode.
121      * @return The current mode the layout should use for the buttons
122      */
getButtonBarMode()123     public int getButtonBarMode() {
124         return MODE_HORIZONTAL_BUTTONS;
125     }
126 
invalidate()127     public void invalidate() {
128         CharSequence currentPageText = getCurrentPageText();
129         if (!TextUtils.isEmpty(currentPageText)) {
130             mCurrentPageText.setText(currentPageText);
131             mCurrentPageText.setVisibility(View.VISIBLE);
132         } else {
133             mCurrentPageText.setVisibility(View.GONE);
134         }
135 
136         Icon icon = getPermissionIcon();
137         if (icon != null) {
138             mIcon.setImageIcon(icon);
139             mIcon.setVisibility(View.VISIBLE);
140         } else {
141             mIcon.setVisibility(View.GONE);
142         }
143         mMessage.setText(getMessage());
144 
145         switch (getButtonBarMode()) {
146             case MODE_HORIZONTAL_BUTTONS:
147                 mHorizontalButtonBar.setVisibility(View.VISIBLE);
148                 mVerticalButtonBar.setVisibility(View.GONE);
149                 break;
150             case MODE_VERTICAL_BUTTONS:
151                 mHorizontalButtonBar.setVisibility(View.GONE);
152                 mVerticalButtonBar.setVisibility(View.VISIBLE);
153 
154                 mVerticalButton1.setText(getVerticalButton1Text());
155                 mVerticalButton2.setText(getVerticalButton2Text());
156 
157                 mVerticalButton1.setCompoundDrawablesWithIntrinsicBounds(
158                         getVerticalButton1Icon(), null, null, null);
159                 mVerticalButton2.setCompoundDrawablesWithIntrinsicBounds(
160                         getVerticalButton2Icon(), null, null, null);
161 
162                 CharSequence verticalButton3Text = getVerticalButton3Text();
163                 if (TextUtils.isEmpty(verticalButton3Text)) {
164                     mVerticalButton3.setVisibility(View.GONE);
165                 } else {
166                     mVerticalButton3.setText(getVerticalButton3Text());
167                     mVerticalButton3.setCompoundDrawablesWithIntrinsicBounds(
168                             getVerticalButton3Icon(), null, null, null);
169                 }
170                 break;
171         }
172 
173         mScrollingContainer.scrollTo(0, 0);
174 
175         mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
176         mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR);
177     }
178 
179     @Override
onGlobalLayout()180     public void onGlobalLayout() {
181         if (Log.isLoggable(TAG, Log.DEBUG)) {
182             Log.d(TAG, "onGlobalLayout");
183             Log.d(TAG, "    contentHeight: " + mContent.getHeight());
184         }
185 
186         if (mButtonBarAnimator != null) {
187             mButtonBarAnimator.cancel();
188         }
189 
190         // In order to fake the buttons peeking at the bottom, need to do set the
191         // padding properly.
192         if (mContent.getPaddingBottom() != mButtonBarContainer.getHeight()) {
193             mContent.setPadding(mContent.getPaddingLeft(), mContent.getPaddingTop(),
194                     mContent.getPaddingRight(), mButtonBarContainer.getHeight());
195             if (Log.isLoggable(TAG, Log.DEBUG)) {
196                 Log.d(TAG, "    set mContent.PaddingBottom: " + mButtonBarContainer.getHeight());
197             }
198         }
199 
200         mButtonBarContainer.setTranslationY(mButtonBarContainer.getHeight());
201 
202         // Give everything a chance to render
203         mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
204         mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR);
205         mHideHandler.sendEmptyMessageDelayed(MSG_SHOW_BUTTON_BAR, 50);
206     }
207 
208     @Override
onClick(View v)209     public void onClick(View v) {
210         int id = v.getId();
211         switch (id) {
212             case R.id.permission_allow_button:
213             case R.id.vertical_button1:
214                 onButton1();
215                 break;
216             case R.id.permission_deny_button:
217             case R.id.vertical_button2:
218                 onButton2();
219                 break;
220             case R.id.vertical_button3:
221                 onButton3();
222                 break;
223         }
224     }
225 
226     @Override
handleMessage(Message msg)227     public boolean handleMessage (Message msg) {
228         switch (msg.what) {
229             case MSG_SHOW_BUTTON_BAR:
230                 showButtonBar();
231                 return true;
232             case MSG_HIDE_BUTTON_BAR:
233                 hideButtonBar();
234                 return true;
235         }
236         return false;
237     }
238 
239     @Override
onScrollChanged()240     public void onScrollChanged () {
241         if (Log.isLoggable(TAG, Log.DEBUG)) {
242             Log.d(TAG, "onScrollChanged");
243         }
244         mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
245         hideButtonBar();
246     }
247 
showButtonBar()248     private void showButtonBar() {
249         if (Log.isLoggable(TAG, Log.DEBUG)) {
250             Log.d(TAG, "showButtonBar");
251         }
252 
253         // Setup Button animation.
254         // pop the button bar back to full height, stop all animation
255         if (mButtonBarAnimator != null) {
256             mButtonBarAnimator.cancel();
257         }
258 
259         // stop any calls to hide the button bar in the future
260         mHideHandler.removeMessages(MSG_HIDE_BUTTON_BAR);
261         mHiddenBefore = false;
262 
263         // Evaluate the max height the button bar can go
264         final int screenHeight = mRoot.getHeight();
265         final int halfScreenHeight = screenHeight / 2;
266         final int buttonBarHeight = mButtonBarContainer.getHeight();
267         final int contentHeight = mContent.getHeight() - buttonBarHeight;
268         final int buttonBarMaxHeight =
269                 Math.min(buttonBarHeight, halfScreenHeight);
270 
271         if (Log.isLoggable(TAG, Log.DEBUG)) {
272             Log.d(TAG, "    screenHeight: " + screenHeight);
273             Log.d(TAG, "    contentHeight: " + contentHeight);
274             Log.d(TAG, "    buttonBarHeight: " + buttonBarHeight);
275             Log.d(TAG, "    buttonBarMaxHeight: " + buttonBarMaxHeight);
276         }
277 
278         mButtonBarContainer.setTranslationZ(mButtonBarFloatingHeight);
279 
280         // Only hide the button bar if it is occluding the content or the button bar is bigger than
281         // half the screen
282         if (contentHeight > (screenHeight - buttonBarHeight)
283                 || buttonBarHeight > halfScreenHeight) {
284             mHideHandler.sendEmptyMessageDelayed(MSG_HIDE_BUTTON_BAR, 3000);
285         }
286 
287         generateButtonBarAnimator(buttonBarHeight,
288                 buttonBarHeight - buttonBarMaxHeight, 0, mButtonBarFloatingHeight, 1000);
289     }
290 
hideButtonBar()291     private void hideButtonBar() {
292         if (Log.isLoggable(TAG, Log.DEBUG)) {
293             Log.d(TAG, "hideButtonBar");
294         }
295 
296         // The desired margin space between the button bar and the bottom of the dialog text
297         final int topMargin = mContext.getResources().getDimensionPixelSize(
298                 R.dimen.conf_diag_button_container_top_margin);
299         final int contentHeight = mContent.getHeight() + topMargin;
300         final int screenHeight = mRoot.getHeight();
301         final int buttonBarHeight = mButtonBarContainer.getHeight();
302 
303         final int offset = screenHeight + buttonBarHeight
304                 - contentHeight + Math.max(mScrollingContainer.getScrollY(), 0);
305         final int translationY = (offset > 0 ?
306                 mButtonBarContainer.getHeight() - offset : mButtonBarContainer.getHeight());
307 
308         if (Log.isLoggable(TAG, Log.DEBUG)) {
309             Log.d(TAG, "    topMargin: " + topMargin);
310             Log.d(TAG, "    contentHeight: " + contentHeight);
311             Log.d(TAG, "    screenHeight: " + screenHeight);
312             Log.d(TAG, "    offset: " + offset);
313             Log.d(TAG, "    buttonBarHeight: " + buttonBarHeight);
314             Log.d(TAG, "    mContent.getPaddingBottom(): " + mContent.getPaddingBottom());
315             Log.d(TAG, "    mScrollingContainer.getScrollY(): " + mScrollingContainer.getScrollY());
316             Log.d(TAG, "    translationY: " + translationY);
317         }
318 
319         if (!mHiddenBefore || mButtonBarAnimator == null) {
320             // Remove previous call to MSG_SHOW_BUTTON_BAR if the user scrolled or something before
321             // the animation got a chance to play
322             mHideHandler.removeMessages(MSG_SHOW_BUTTON_BAR);
323 
324             if(mButtonBarAnimator != null) {
325                 mButtonBarAnimator.cancel(); // stop current animation if there is one playing
326             }
327 
328             // hasn't hidden the bar yet, just hide now to the right height
329             generateButtonBarAnimator(
330                     mButtonBarContainer.getTranslationY(), translationY,
331                     mButtonBarFloatingHeight, 0, HIDE_ANIM_DURATION);
332         } else if (mButtonBarAnimator.isRunning()) {
333             // we are animating the button bar closing, change to animate to the right place
334             if (Math.abs(mCurrentTranslation - translationY) > 1e-2f) {
335                 mButtonBarAnimator.cancel(); // stop current animation
336 
337                 if (Math.abs(mButtonBarContainer.getTranslationY() - translationY) > 1e-2f) {
338                     long duration = Math.max((long) (
339                             (float) HIDE_ANIM_DURATION
340                                     * (translationY - mButtonBarContainer.getTranslationY())
341                                     / mButtonBarContainer.getHeight()), 0);
342 
343                     generateButtonBarAnimator(
344                             mButtonBarContainer.getTranslationY(), translationY,
345                             mButtonBarFloatingHeight, 0, duration);
346                 } else {
347                     mButtonBarContainer.setTranslationY(translationY);
348                     mButtonBarContainer.setTranslationZ(0);
349                 }
350             }
351         } else {
352             // not currently animating, have already hidden, snap to the right offset
353             mButtonBarContainer.setTranslationY(translationY);
354             mButtonBarContainer.setTranslationZ(0);
355         }
356 
357         mHiddenBefore = true;
358     }
359 
generateButtonBarAnimator( float startY, float endY, float startZ, float endZ, long duration)360     private void generateButtonBarAnimator(
361             float startY, float endY, float startZ, float endZ, long duration) {
362         if (Log.isLoggable(TAG, Log.DEBUG)) {
363             Log.d(TAG, "generateButtonBarAnimator");
364             Log.d(TAG, "    startY: " + startY);
365             Log.d(TAG, "    endY: " + endY);
366             Log.d(TAG, "    startZ: " + startZ);
367             Log.d(TAG, "    endZ: " + endZ);
368             Log.d(TAG, "    duration: " + duration);
369         }
370 
371         mButtonBarAnimator =
372                 ObjectAnimator.ofPropertyValuesHolder(
373                         mButtonBarContainer,
374                         PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, startY, endY),
375                         PropertyValuesHolder.ofFloat(View.TRANSLATION_Z, startZ, endZ));
376         mCurrentTranslation = endY;
377         mButtonBarAnimator.setDuration(duration);
378         mButtonBarAnimator.setInterpolator(mInterpolator);
379         mButtonBarAnimator.start();
380     }
381 }
382