1package com.android.packageinstaller.permission.ui.wear;
2
3import android.animation.ObjectAnimator;
4import android.animation.PropertyValuesHolder;
5import android.content.Context;
6import android.graphics.drawable.Drawable;
7import android.graphics.drawable.Icon;
8import android.os.Handler;
9import android.os.Looper;
10import android.os.Message;
11import android.text.TextUtils;
12import android.util.Log;
13import android.view.LayoutInflater;
14import android.view.View;
15import android.view.ViewGroup;
16import android.view.ViewTreeObserver;
17import android.view.animation.AnimationUtils;
18import android.view.animation.Interpolator;
19import android.widget.Button;
20import android.widget.ImageView;
21import android.widget.ScrollView;
22import android.widget.TextView;
23
24import com.android.packageinstaller.R;
25
26public 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 */
64    public abstract void onButton1();
65    /** In the 2 button layout, this is deny button */
66    public abstract void onButton2();
67    public abstract void onButton3();
68    public abstract CharSequence getVerticalButton1Text();
69    public abstract CharSequence getVerticalButton2Text();
70    public abstract CharSequence getVerticalButton3Text();
71    public abstract Drawable getVerticalButton1Icon();
72    public abstract Drawable getVerticalButton2Icon();
73    public abstract Drawable getVerticalButton3Icon();
74    public abstract CharSequence getCurrentPageText();
75    public abstract Icon getPermissionIcon();
76    public abstract CharSequence getMessage();
77
78    public ConfirmationViewHandler(Context context) {
79        mContext = context;
80    }
81
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     */
123    public int getButtonBarMode() {
124        return MODE_HORIZONTAL_BUTTONS;
125    }
126
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
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
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
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
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
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
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
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