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