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