1 /* 2 * Copyright (C) 2011 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.launcher3; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ObjectAnimator; 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Color; 26 import android.graphics.Rect; 27 import android.util.AttributeSet; 28 import android.view.LayoutInflater; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.FrameLayout; 33 import android.widget.LinearLayout; 34 import android.widget.TabHost; 35 import android.widget.TabWidget; 36 import android.widget.TextView; 37 38 import java.util.ArrayList; 39 40 public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, 41 TabHost.OnTabChangeListener, Insettable { 42 static final String LOG_TAG = "AppsCustomizeTabHost"; 43 44 private static final String APPS_TAB_TAG = "APPS"; 45 private static final String WIDGETS_TAB_TAG = "WIDGETS"; 46 47 private final LayoutInflater mLayoutInflater; 48 private ViewGroup mTabs; 49 private ViewGroup mTabsContainer; 50 private AppsCustomizePagedView mAppsCustomizePane; 51 private FrameLayout mAnimationBuffer; 52 private LinearLayout mContent; 53 54 private boolean mInTransition; 55 private boolean mTransitioningToWorkspace; 56 private boolean mResetAfterTransition; 57 private Runnable mRelayoutAndMakeVisible; 58 private final Rect mInsets = new Rect(); 59 60 public AppsCustomizeTabHost(Context context, AttributeSet attrs) { 61 super(context, attrs); 62 mLayoutInflater = LayoutInflater.from(context); 63 mRelayoutAndMakeVisible = new Runnable() { 64 public void run() { 65 mTabs.requestLayout(); 66 mTabsContainer.setAlpha(1f); 67 } 68 }; 69 } 70 71 /** 72 * Convenience methods to select specific tabs. We want to set the content type immediately 73 * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view 74 * reflects the new content (but doesn't do the animation and logic associated with changing 75 * tabs manually). 76 */ 77 void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { 78 setOnTabChangedListener(null); 79 onTabChangedStart(); 80 onTabChangedEnd(type); 81 setCurrentTabByTag(getTabTagForContentType(type)); 82 setOnTabChangedListener(this); 83 } 84 85 @Override 86 public void setInsets(Rect insets) { 87 mInsets.set(insets); 88 FrameLayout.LayoutParams flp = (LayoutParams) mContent.getLayoutParams(); 89 flp.topMargin = insets.top; 90 flp.bottomMargin = insets.bottom; 91 flp.leftMargin = insets.left; 92 flp.rightMargin = insets.right; 93 mContent.setLayoutParams(flp); 94 } 95 96 /** 97 * Setup the tab host and create all necessary tabs. 98 */ 99 @Override 100 protected void onFinishInflate() { 101 // Setup the tab host 102 setup(); 103 104 final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); 105 final TabWidget tabs = getTabWidget(); 106 final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) 107 findViewById(R.id.apps_customize_pane_content); 108 mTabs = tabs; 109 mTabsContainer = tabsContainer; 110 mAppsCustomizePane = appsCustomizePane; 111 mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); 112 mContent = (LinearLayout) findViewById(R.id.apps_customize_content); 113 if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); 114 115 // Configure the tabs content factory to return the same paged view (that we change the 116 // content filter on) 117 TabContentFactory contentFactory = new TabContentFactory() { 118 public View createTabContent(String tag) { 119 return appsCustomizePane; 120 } 121 }; 122 123 // Create the tabs 124 TextView tabView; 125 String label; 126 label = getContext().getString(R.string.all_apps_button_label); 127 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 128 tabView.setText(label); 129 tabView.setContentDescription(label); 130 addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 131 label = getContext().getString(R.string.widgets_tab_label); 132 tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); 133 tabView.setText(label); 134 tabView.setContentDescription(label); 135 addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); 136 setOnTabChangedListener(this); 137 138 // Setup the key listener to jump between the last tab view and the market icon 139 AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); 140 View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); 141 lastTab.setOnKeyListener(keyListener); 142 View shopButton = findViewById(R.id.market_button); 143 shopButton.setOnKeyListener(keyListener); 144 145 // Hide the tab bar until we measure 146 mTabsContainer.setAlpha(0f); 147 } 148 149 @Override 150 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 151 boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); 152 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 153 154 // Set the width of the tab list to the content width 155 if (remeasureTabWidth) { 156 int contentWidth = mAppsCustomizePane.getPageContentWidth(); 157 if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { 158 // Set the width and show the tab bar 159 mTabs.getLayoutParams().width = contentWidth; 160 mRelayoutAndMakeVisible.run(); 161 } 162 163 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 164 } 165 } 166 167 public boolean onInterceptTouchEvent(MotionEvent ev) { 168 // If we are mid transitioning to the workspace, then intercept touch events here so we 169 // can ignore them, otherwise we just let all apps handle the touch events. 170 if (mInTransition && mTransitioningToWorkspace) { 171 return true; 172 } 173 return super.onInterceptTouchEvent(ev); 174 }; 175 176 @Override 177 public boolean onTouchEvent(MotionEvent event) { 178 // Allow touch events to fall through to the workspace if we are transitioning there 179 if (mInTransition && mTransitioningToWorkspace) { 180 return super.onTouchEvent(event); 181 } 182 183 // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall 184 // through to the workspace and trigger showWorkspace() 185 if (event.getY() < mAppsCustomizePane.getBottom()) { 186 return true; 187 } 188 return super.onTouchEvent(event); 189 } 190 191 private void onTabChangedStart() { 192 } 193 194 private void reloadCurrentPage() { 195 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 196 mAppsCustomizePane.requestFocus(); 197 } 198 199 private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { 200 int bgAlpha = (int) (255 * (getResources().getInteger( 201 R.integer.config_appsCustomizeSpringLoadedBgAlpha) / 100f)); 202 setBackgroundColor(Color.argb(bgAlpha, 0, 0, 0)); 203 mAppsCustomizePane.setContentType(type); 204 } 205 206 @Override 207 public void onTabChanged(String tabId) { 208 final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); 209 210 // Animate the changing of the tab content by fading pages in and out 211 final Resources res = getResources(); 212 final int duration = res.getInteger(R.integer.config_tabTransitionDuration); 213 214 // We post a runnable here because there is a delay while the first page is loading and 215 // the feedback from having changed the tab almost feels better than having it stick 216 post(new Runnable() { 217 @Override 218 public void run() { 219 if (mAppsCustomizePane.getMeasuredWidth() <= 0 || 220 mAppsCustomizePane.getMeasuredHeight() <= 0) { 221 reloadCurrentPage(); 222 return; 223 } 224 225 // Take the visible pages and re-parent them temporarily to mAnimatorBuffer 226 // and then cross fade to the new pages 227 int[] visiblePageRange = new int[2]; 228 mAppsCustomizePane.getVisiblePages(visiblePageRange); 229 if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { 230 // If we can't get the visible page ranges, then just skip the animation 231 reloadCurrentPage(); 232 return; 233 } 234 ArrayList<View> visiblePages = new ArrayList<View>(); 235 for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { 236 visiblePages.add(mAppsCustomizePane.getPageAt(i)); 237 } 238 239 // We want the pages to be rendered in exactly the same way as they were when 240 // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer 241 // to be exactly the same as mAppsCustomizePane, and below, set the left/top 242 // parameters to be correct for each of the pages 243 mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); 244 245 // mAppsCustomizePane renders its children in reverse order, so 246 // add the pages to mAnimationBuffer in reverse order to match that behavior 247 for (int i = visiblePages.size() - 1; i >= 0; i--) { 248 View child = visiblePages.get(i); 249 if (child instanceof AppsCustomizeCellLayout) { 250 ((AppsCustomizeCellLayout) child).resetChildrenOnKeyListeners(); 251 } else if (child instanceof PagedViewGridLayout) { 252 ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); 253 } 254 PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); 255 mAppsCustomizePane.removeView(child); 256 PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); 257 mAnimationBuffer.setAlpha(1f); 258 mAnimationBuffer.setVisibility(View.VISIBLE); 259 LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), 260 child.getMeasuredHeight()); 261 p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); 262 mAnimationBuffer.addView(child, p); 263 } 264 265 // Toggle the new content 266 onTabChangedStart(); 267 onTabChangedEnd(type); 268 269 // Animate the transition 270 ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f); 271 outAnim.addListener(new AnimatorListenerAdapter() { 272 private void clearAnimationBuffer() { 273 mAnimationBuffer.setVisibility(View.GONE); 274 PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(false); 275 mAnimationBuffer.removeAllViews(); 276 PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(true); 277 } 278 @Override 279 public void onAnimationEnd(Animator animation) { 280 clearAnimationBuffer(); 281 } 282 @Override 283 public void onAnimationCancel(Animator animation) { 284 clearAnimationBuffer(); 285 } 286 }); 287 ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f); 288 inAnim.addListener(new AnimatorListenerAdapter() { 289 @Override 290 public void onAnimationEnd(Animator animation) { 291 reloadCurrentPage(); 292 } 293 }); 294 295 final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet(); 296 animSet.playTogether(outAnim, inAnim); 297 animSet.setDuration(duration); 298 animSet.start(); 299 } 300 }); 301 } 302 303 public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { 304 setOnTabChangedListener(null); 305 setCurrentTabByTag(getTabTagForContentType(type)); 306 setOnTabChangedListener(this); 307 } 308 309 /** 310 * Returns the content type for the specified tab tag. 311 */ 312 public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { 313 if (tag.equals(APPS_TAB_TAG)) { 314 return AppsCustomizePagedView.ContentType.Applications; 315 } else if (tag.equals(WIDGETS_TAB_TAG)) { 316 return AppsCustomizePagedView.ContentType.Widgets; 317 } 318 return AppsCustomizePagedView.ContentType.Applications; 319 } 320 321 /** 322 * Returns the tab tag for a given content type. 323 */ 324 public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { 325 if (type == AppsCustomizePagedView.ContentType.Applications) { 326 return APPS_TAB_TAG; 327 } else if (type == AppsCustomizePagedView.ContentType.Widgets) { 328 return WIDGETS_TAB_TAG; 329 } 330 return APPS_TAB_TAG; 331 } 332 333 /** 334 * Disable focus on anything under this view in the hierarchy if we are not visible. 335 */ 336 @Override 337 public int getDescendantFocusability() { 338 if (getVisibility() != View.VISIBLE) { 339 return ViewGroup.FOCUS_BLOCK_DESCENDANTS; 340 } 341 return super.getDescendantFocusability(); 342 } 343 344 void reset() { 345 if (mInTransition) { 346 // Defer to after the transition to reset 347 mResetAfterTransition = true; 348 } else { 349 // Reset immediately 350 mAppsCustomizePane.reset(); 351 } 352 } 353 354 private void enableAndBuildHardwareLayer() { 355 // isHardwareAccelerated() checks if we're attached to a window and if that 356 // window is HW accelerated-- we were sometimes not attached to a window 357 // and buildLayer was throwing an IllegalStateException 358 if (isHardwareAccelerated()) { 359 // Turn on hardware layers for performance 360 setLayerType(LAYER_TYPE_HARDWARE, null); 361 362 // force building the layer, so you don't get a blip early in an animation 363 // when the layer is created layer 364 buildLayer(); 365 } 366 } 367 368 @Override 369 public View getContent() { 370 return mContent; 371 } 372 373 /* LauncherTransitionable overrides */ 374 @Override 375 public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { 376 mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); 377 mInTransition = true; 378 mTransitioningToWorkspace = toWorkspace; 379 380 if (toWorkspace) { 381 // Going from All Apps -> Workspace 382 setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); 383 } else { 384 // Going from Workspace -> All Apps 385 mContent.setVisibility(VISIBLE); 386 387 // Make sure the current page is loaded (we start loading the side pages after the 388 // transition to prevent slowing down the animation) 389 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); 390 } 391 392 if (mResetAfterTransition) { 393 mAppsCustomizePane.reset(); 394 mResetAfterTransition = false; 395 } 396 } 397 398 @Override 399 public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { 400 if (animated) { 401 enableAndBuildHardwareLayer(); 402 } 403 404 // Dismiss the workspace cling 405 l.dismissWorkspaceCling(null); 406 } 407 408 @Override 409 public void onLauncherTransitionStep(Launcher l, float t) { 410 // Do nothing 411 } 412 413 @Override 414 public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { 415 mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); 416 mInTransition = false; 417 if (animated) { 418 setLayerType(LAYER_TYPE_NONE, null); 419 } 420 421 if (!toWorkspace) { 422 // Show the all apps cling (if not already shown) 423 mAppsCustomizePane.showAllAppsCling(); 424 // Make sure adjacent pages are loaded (we wait until after the transition to 425 // prevent slowing down the animation) 426 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 427 428 // Going from Workspace -> All Apps 429 // NOTE: We should do this at the end since we check visibility state in some of the 430 // cling initialization/dismiss code above. 431 setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); 432 } 433 } 434 435 private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { 436 ViewGroup parent = (ViewGroup) getParent(); 437 if (parent == null) return; 438 439 View overviewPanel = ((Launcher) getContext()).getOverviewPanel(); 440 final int count = parent.getChildCount(); 441 if (!isChildrenDrawingOrderEnabled()) { 442 for (int i = 0; i < count; i++) { 443 final View child = parent.getChildAt(i); 444 if (child == this) { 445 break; 446 } else { 447 if (child.getVisibility() == GONE || child == overviewPanel) { 448 continue; 449 } 450 child.setVisibility(visibility); 451 } 452 } 453 } else { 454 throw new RuntimeException("Failed; can't get z-order of views"); 455 } 456 } 457 458 public void onWindowVisible() { 459 if (getVisibility() == VISIBLE) { 460 mContent.setVisibility(VISIBLE); 461 // We unload the widget previews when the UI is hidden, so need to reload pages 462 // Load the current page synchronously, and the neighboring pages asynchronously 463 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); 464 mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); 465 } 466 } 467 468 public void onTrimMemory() { 469 mContent.setVisibility(GONE); 470 // Clear the widget pages of all their subviews - this will trigger the widget previews 471 // to delete their bitmaps 472 mAppsCustomizePane.clearAllWidgetPages(); 473 } 474 475 boolean isTransitioning() { 476 return mInTransition; 477 } 478 } 479