|  | /* | 
|  | * Copyright (C) 2011 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | package com.android.launcher2; | 
|  |  | 
|  | import android.animation.Animator; | 
|  | import android.animation.AnimatorListenerAdapter; | 
|  | import android.animation.AnimatorSet; | 
|  | import android.animation.ObjectAnimator; | 
|  | import android.content.Context; | 
|  | import android.content.res.Resources; | 
|  | import android.util.AttributeSet; | 
|  | import android.view.LayoutInflater; | 
|  | import android.view.MotionEvent; | 
|  | import android.view.View; | 
|  | import android.view.ViewGroup; | 
|  | import android.widget.FrameLayout; | 
|  | import android.widget.LinearLayout; | 
|  | import android.widget.TabHost; | 
|  | import android.widget.TabWidget; | 
|  | import android.widget.TextView; | 
|  |  | 
|  | import com.android.launcher.R; | 
|  |  | 
|  | import java.util.ArrayList; | 
|  |  | 
|  | public class AppsCustomizeTabHost extends TabHost implements LauncherTransitionable, | 
|  | TabHost.OnTabChangeListener  { | 
|  | static final String LOG_TAG = "AppsCustomizeTabHost"; | 
|  |  | 
|  | private static final String APPS_TAB_TAG = "APPS"; | 
|  | private static final String WIDGETS_TAB_TAG = "WIDGETS"; | 
|  |  | 
|  | private final LayoutInflater mLayoutInflater; | 
|  | private ViewGroup mTabs; | 
|  | private ViewGroup mTabsContainer; | 
|  | private AppsCustomizePagedView mAppsCustomizePane; | 
|  | private FrameLayout mAnimationBuffer; | 
|  | private LinearLayout mContent; | 
|  |  | 
|  | private boolean mInTransition; | 
|  | private boolean mTransitioningToWorkspace; | 
|  | private boolean mResetAfterTransition; | 
|  | private Runnable mRelayoutAndMakeVisible; | 
|  |  | 
|  | public AppsCustomizeTabHost(Context context, AttributeSet attrs) { | 
|  | super(context, attrs); | 
|  | mLayoutInflater = LayoutInflater.from(context); | 
|  | mRelayoutAndMakeVisible = new Runnable() { | 
|  | public void run() { | 
|  | mTabs.requestLayout(); | 
|  | mTabsContainer.setAlpha(1f); | 
|  | } | 
|  | }; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Convenience methods to select specific tabs.  We want to set the content type immediately | 
|  | * in these cases, but we note that we still call setCurrentTabByTag() so that the tab view | 
|  | * reflects the new content (but doesn't do the animation and logic associated with changing | 
|  | * tabs manually). | 
|  | */ | 
|  | void setContentTypeImmediate(AppsCustomizePagedView.ContentType type) { | 
|  | setOnTabChangedListener(null); | 
|  | onTabChangedStart(); | 
|  | onTabChangedEnd(type); | 
|  | setCurrentTabByTag(getTabTagForContentType(type)); | 
|  | setOnTabChangedListener(this); | 
|  | } | 
|  | void selectAppsTab() { | 
|  | setContentTypeImmediate(AppsCustomizePagedView.ContentType.Applications); | 
|  | } | 
|  | void selectWidgetsTab() { | 
|  | setContentTypeImmediate(AppsCustomizePagedView.ContentType.Widgets); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Setup the tab host and create all necessary tabs. | 
|  | */ | 
|  | @Override | 
|  | protected void onFinishInflate() { | 
|  | // Setup the tab host | 
|  | setup(); | 
|  |  | 
|  | final ViewGroup tabsContainer = (ViewGroup) findViewById(R.id.tabs_container); | 
|  | final TabWidget tabs = getTabWidget(); | 
|  | final AppsCustomizePagedView appsCustomizePane = (AppsCustomizePagedView) | 
|  | findViewById(R.id.apps_customize_pane_content); | 
|  | mTabs = tabs; | 
|  | mTabsContainer = tabsContainer; | 
|  | mAppsCustomizePane = appsCustomizePane; | 
|  | mAnimationBuffer = (FrameLayout) findViewById(R.id.animation_buffer); | 
|  | mContent = (LinearLayout) findViewById(R.id.apps_customize_content); | 
|  | if (tabs == null || mAppsCustomizePane == null) throw new Resources.NotFoundException(); | 
|  |  | 
|  | // Configure the tabs content factory to return the same paged view (that we change the | 
|  | // content filter on) | 
|  | TabContentFactory contentFactory = new TabContentFactory() { | 
|  | public View createTabContent(String tag) { | 
|  | return appsCustomizePane; | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Create the tabs | 
|  | TextView tabView; | 
|  | String label; | 
|  | label = getContext().getString(R.string.all_apps_button_label); | 
|  | tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); | 
|  | tabView.setText(label); | 
|  | tabView.setContentDescription(label); | 
|  | addTab(newTabSpec(APPS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); | 
|  | label = getContext().getString(R.string.widgets_tab_label); | 
|  | tabView = (TextView) mLayoutInflater.inflate(R.layout.tab_widget_indicator, tabs, false); | 
|  | tabView.setText(label); | 
|  | tabView.setContentDescription(label); | 
|  | addTab(newTabSpec(WIDGETS_TAB_TAG).setIndicator(tabView).setContent(contentFactory)); | 
|  | setOnTabChangedListener(this); | 
|  |  | 
|  | // Setup the key listener to jump between the last tab view and the market icon | 
|  | AppsCustomizeTabKeyEventListener keyListener = new AppsCustomizeTabKeyEventListener(); | 
|  | View lastTab = tabs.getChildTabViewAt(tabs.getTabCount() - 1); | 
|  | lastTab.setOnKeyListener(keyListener); | 
|  | View shopButton = findViewById(R.id.market_button); | 
|  | shopButton.setOnKeyListener(keyListener); | 
|  |  | 
|  | // Hide the tab bar until we measure | 
|  | mTabsContainer.setAlpha(0f); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | 
|  | boolean remeasureTabWidth = (mTabs.getLayoutParams().width <= 0); | 
|  | super.onMeasure(widthMeasureSpec, heightMeasureSpec); | 
|  |  | 
|  | // Set the width of the tab list to the content width | 
|  | if (remeasureTabWidth) { | 
|  | int contentWidth = mAppsCustomizePane.getPageContentWidth(); | 
|  | if (contentWidth > 0 && mTabs.getLayoutParams().width != contentWidth) { | 
|  | // Set the width and show the tab bar | 
|  | mTabs.getLayoutParams().width = contentWidth; | 
|  | mRelayoutAndMakeVisible.run(); | 
|  | } | 
|  |  | 
|  | super.onMeasure(widthMeasureSpec, heightMeasureSpec); | 
|  | } | 
|  | } | 
|  |  | 
|  | public boolean onInterceptTouchEvent(MotionEvent ev) { | 
|  | // If we are mid transitioning to the workspace, then intercept touch events here so we | 
|  | // can ignore them, otherwise we just let all apps handle the touch events. | 
|  | if (mInTransition && mTransitioningToWorkspace) { | 
|  | return true; | 
|  | } | 
|  | return super.onInterceptTouchEvent(ev); | 
|  | }; | 
|  |  | 
|  | @Override | 
|  | public boolean onTouchEvent(MotionEvent event) { | 
|  | // Allow touch events to fall through to the workspace if we are transitioning there | 
|  | if (mInTransition && mTransitioningToWorkspace) { | 
|  | return super.onTouchEvent(event); | 
|  | } | 
|  |  | 
|  | // Intercept all touch events up to the bottom of the AppsCustomizePane so they do not fall | 
|  | // through to the workspace and trigger showWorkspace() | 
|  | if (event.getY() < mAppsCustomizePane.getBottom()) { | 
|  | return true; | 
|  | } | 
|  | return super.onTouchEvent(event); | 
|  | } | 
|  |  | 
|  | private void onTabChangedStart() { | 
|  | mAppsCustomizePane.hideScrollingIndicator(false); | 
|  | } | 
|  |  | 
|  | private void reloadCurrentPage() { | 
|  | if (!LauncherApplication.isScreenLarge()) { | 
|  | mAppsCustomizePane.flashScrollingIndicator(true); | 
|  | } | 
|  | mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | 
|  | mAppsCustomizePane.requestFocus(); | 
|  | } | 
|  |  | 
|  | private void onTabChangedEnd(AppsCustomizePagedView.ContentType type) { | 
|  | mAppsCustomizePane.setContentType(type); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onTabChanged(String tabId) { | 
|  | final AppsCustomizePagedView.ContentType type = getContentTypeForTabTag(tabId); | 
|  |  | 
|  | // Animate the changing of the tab content by fading pages in and out | 
|  | final Resources res = getResources(); | 
|  | final int duration = res.getInteger(R.integer.config_tabTransitionDuration); | 
|  |  | 
|  | // We post a runnable here because there is a delay while the first page is loading and | 
|  | // the feedback from having changed the tab almost feels better than having it stick | 
|  | post(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | if (mAppsCustomizePane.getMeasuredWidth() <= 0 || | 
|  | mAppsCustomizePane.getMeasuredHeight() <= 0) { | 
|  | reloadCurrentPage(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Take the visible pages and re-parent them temporarily to mAnimatorBuffer | 
|  | // and then cross fade to the new pages | 
|  | int[] visiblePageRange = new int[2]; | 
|  | mAppsCustomizePane.getVisiblePages(visiblePageRange); | 
|  | if (visiblePageRange[0] == -1 && visiblePageRange[1] == -1) { | 
|  | // If we can't get the visible page ranges, then just skip the animation | 
|  | reloadCurrentPage(); | 
|  | return; | 
|  | } | 
|  | ArrayList<View> visiblePages = new ArrayList<View>(); | 
|  | for (int i = visiblePageRange[0]; i <= visiblePageRange[1]; i++) { | 
|  | visiblePages.add(mAppsCustomizePane.getPageAt(i)); | 
|  | } | 
|  |  | 
|  | // We want the pages to be rendered in exactly the same way as they were when | 
|  | // their parent was mAppsCustomizePane -- so set the scroll on mAnimationBuffer | 
|  | // to be exactly the same as mAppsCustomizePane, and below, set the left/top | 
|  | // parameters to be correct for each of the pages | 
|  | mAnimationBuffer.scrollTo(mAppsCustomizePane.getScrollX(), 0); | 
|  |  | 
|  | // mAppsCustomizePane renders its children in reverse order, so | 
|  | // add the pages to mAnimationBuffer in reverse order to match that behavior | 
|  | for (int i = visiblePages.size() - 1; i >= 0; i--) { | 
|  | View child = visiblePages.get(i); | 
|  | if (child instanceof PagedViewCellLayout) { | 
|  | ((PagedViewCellLayout) child).resetChildrenOnKeyListeners(); | 
|  | } else if (child instanceof PagedViewGridLayout) { | 
|  | ((PagedViewGridLayout) child).resetChildrenOnKeyListeners(); | 
|  | } | 
|  | PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(false); | 
|  | mAppsCustomizePane.removeView(child); | 
|  | PagedViewWidget.setDeletePreviewsWhenDetachedFromWindow(true); | 
|  | mAnimationBuffer.setAlpha(1f); | 
|  | mAnimationBuffer.setVisibility(View.VISIBLE); | 
|  | LayoutParams p = new FrameLayout.LayoutParams(child.getMeasuredWidth(), | 
|  | child.getMeasuredHeight()); | 
|  | p.setMargins((int) child.getLeft(), (int) child.getTop(), 0, 0); | 
|  | mAnimationBuffer.addView(child, p); | 
|  | } | 
|  |  | 
|  | // Toggle the new content | 
|  | onTabChangedStart(); | 
|  | onTabChangedEnd(type); | 
|  |  | 
|  | // Animate the transition | 
|  | ObjectAnimator outAnim = LauncherAnimUtils.ofFloat(mAnimationBuffer, "alpha", 0f); | 
|  | outAnim.addListener(new AnimatorListenerAdapter() { | 
|  | private void clearAnimationBuffer() { | 
|  | mAnimationBuffer.setVisibility(View.GONE); | 
|  | PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(false); | 
|  | mAnimationBuffer.removeAllViews(); | 
|  | PagedViewWidget.setRecyclePreviewsWhenDetachedFromWindow(true); | 
|  | } | 
|  | @Override | 
|  | public void onAnimationEnd(Animator animation) { | 
|  | clearAnimationBuffer(); | 
|  | } | 
|  | @Override | 
|  | public void onAnimationCancel(Animator animation) { | 
|  | clearAnimationBuffer(); | 
|  | } | 
|  | }); | 
|  | ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f); | 
|  | inAnim.addListener(new AnimatorListenerAdapter() { | 
|  | @Override | 
|  | public void onAnimationEnd(Animator animation) { | 
|  | reloadCurrentPage(); | 
|  | } | 
|  | }); | 
|  |  | 
|  | final AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet(); | 
|  | animSet.playTogether(outAnim, inAnim); | 
|  | animSet.setDuration(duration); | 
|  | animSet.start(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) { | 
|  | setOnTabChangedListener(null); | 
|  | setCurrentTabByTag(getTabTagForContentType(type)); | 
|  | setOnTabChangedListener(this); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the content type for the specified tab tag. | 
|  | */ | 
|  | public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) { | 
|  | if (tag.equals(APPS_TAB_TAG)) { | 
|  | return AppsCustomizePagedView.ContentType.Applications; | 
|  | } else if (tag.equals(WIDGETS_TAB_TAG)) { | 
|  | return AppsCustomizePagedView.ContentType.Widgets; | 
|  | } | 
|  | return AppsCustomizePagedView.ContentType.Applications; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Returns the tab tag for a given content type. | 
|  | */ | 
|  | public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) { | 
|  | if (type == AppsCustomizePagedView.ContentType.Applications) { | 
|  | return APPS_TAB_TAG; | 
|  | } else if (type == AppsCustomizePagedView.ContentType.Widgets) { | 
|  | return WIDGETS_TAB_TAG; | 
|  | } | 
|  | return APPS_TAB_TAG; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Disable focus on anything under this view in the hierarchy if we are not visible. | 
|  | */ | 
|  | @Override | 
|  | public int getDescendantFocusability() { | 
|  | if (getVisibility() != View.VISIBLE) { | 
|  | return ViewGroup.FOCUS_BLOCK_DESCENDANTS; | 
|  | } | 
|  | return super.getDescendantFocusability(); | 
|  | } | 
|  |  | 
|  | void reset() { | 
|  | if (mInTransition) { | 
|  | // Defer to after the transition to reset | 
|  | mResetAfterTransition = true; | 
|  | } else { | 
|  | // Reset immediately | 
|  | mAppsCustomizePane.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void enableAndBuildHardwareLayer() { | 
|  | // isHardwareAccelerated() checks if we're attached to a window and if that | 
|  | // window is HW accelerated-- we were sometimes not attached to a window | 
|  | // and buildLayer was throwing an IllegalStateException | 
|  | if (isHardwareAccelerated()) { | 
|  | // Turn on hardware layers for performance | 
|  | setLayerType(LAYER_TYPE_HARDWARE, null); | 
|  |  | 
|  | // force building the layer, so you don't get a blip early in an animation | 
|  | // when the layer is created layer | 
|  | buildLayer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public View getContent() { | 
|  | return mContent; | 
|  | } | 
|  |  | 
|  | /* LauncherTransitionable overrides */ | 
|  | @Override | 
|  | public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) { | 
|  | mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace); | 
|  | mInTransition = true; | 
|  | mTransitioningToWorkspace = toWorkspace; | 
|  |  | 
|  | if (toWorkspace) { | 
|  | // Going from All Apps -> Workspace | 
|  | setVisibilityOfSiblingsWithLowerZOrder(VISIBLE); | 
|  | // Stop the scrolling indicator - we don't want All Apps to be invalidating itself | 
|  | // during the transition, especially since it has a hardware layer set on it | 
|  | mAppsCustomizePane.cancelScrollingIndicatorAnimations(); | 
|  | } else { | 
|  | // Going from Workspace -> All Apps | 
|  | mContent.setVisibility(VISIBLE); | 
|  |  | 
|  | // Make sure the current page is loaded (we start loading the side pages after the | 
|  | // transition to prevent slowing down the animation) | 
|  | mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); | 
|  |  | 
|  | if (!LauncherApplication.isScreenLarge()) { | 
|  | mAppsCustomizePane.showScrollingIndicator(true); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (mResetAfterTransition) { | 
|  | mAppsCustomizePane.reset(); | 
|  | mResetAfterTransition = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) { | 
|  | if (animated) { | 
|  | enableAndBuildHardwareLayer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLauncherTransitionStep(Launcher l, float t) { | 
|  | // Do nothing | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) { | 
|  | mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace); | 
|  | mInTransition = false; | 
|  | if (animated) { | 
|  | setLayerType(LAYER_TYPE_NONE, null); | 
|  | } | 
|  |  | 
|  | if (!toWorkspace) { | 
|  | // Dismiss the workspace cling | 
|  | l.dismissWorkspaceCling(null); | 
|  | // Show the all apps cling (if not already shown) | 
|  | mAppsCustomizePane.showAllAppsCling(); | 
|  | // Make sure adjacent pages are loaded (we wait until after the transition to | 
|  | // prevent slowing down the animation) | 
|  | mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | 
|  |  | 
|  | if (!LauncherApplication.isScreenLarge()) { | 
|  | mAppsCustomizePane.hideScrollingIndicator(false); | 
|  | } | 
|  |  | 
|  | // Going from Workspace -> All Apps | 
|  | // NOTE: We should do this at the end since we check visibility state in some of the | 
|  | // cling initialization/dismiss code above. | 
|  | setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) { | 
|  | ViewGroup parent = (ViewGroup) getParent(); | 
|  | if (parent == null) return; | 
|  |  | 
|  | final int count = parent.getChildCount(); | 
|  | if (!isChildrenDrawingOrderEnabled()) { | 
|  | for (int i = 0; i < count; i++) { | 
|  | final View child = parent.getChildAt(i); | 
|  | if (child == this) { | 
|  | break; | 
|  | } else { | 
|  | if (child.getVisibility() == GONE) { | 
|  | continue; | 
|  | } | 
|  | child.setVisibility(visibility); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | throw new RuntimeException("Failed; can't get z-order of views"); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void onWindowVisible() { | 
|  | if (getVisibility() == VISIBLE) { | 
|  | mContent.setVisibility(VISIBLE); | 
|  | // We unload the widget previews when the UI is hidden, so need to reload pages | 
|  | // Load the current page synchronously, and the neighboring pages asynchronously | 
|  | mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true); | 
|  | mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage()); | 
|  | } | 
|  | } | 
|  |  | 
|  | public void onTrimMemory() { | 
|  | mContent.setVisibility(GONE); | 
|  | // Clear the widget pages of all their subviews - this will trigger the widget previews | 
|  | // to delete their bitmaps | 
|  | mAppsCustomizePane.clearAllWidgetPages(); | 
|  | } | 
|  |  | 
|  | boolean isTransitioning() { | 
|  | return mInTransition; | 
|  | } | 
|  | } |