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                     @Override
    268                     public void onAnimationEnd(Animator animation) {
    269                         mAnimationBuffer.setVisibility(View.GONE);
    270                         mAnimationBuffer.removeAllViews();
    271                     }
    272                     @Override
    273                     public void onAnimationCancel(Animator animation) {
    274                         mAnimationBuffer.setVisibility(View.GONE);
    275                         mAnimationBuffer.removeAllViews();
    276                     }
    277                 });
    278                 ObjectAnimator inAnim = LauncherAnimUtils.ofFloat(mAppsCustomizePane, "alpha", 1f);
    279                 inAnim.addListener(new AnimatorListenerAdapter() {
    280                     @Override
    281                     public void onAnimationEnd(Animator animation) {
    282                         reloadCurrentPage();
    283                     }
    284                 });
    285                 AnimatorSet animSet = LauncherAnimUtils.createAnimatorSet();
    286                 animSet.playTogether(outAnim, inAnim);
    287                 animSet.setDuration(duration);
    288                 animSet.start();
    289             }
    290         });
    291     }
    292 
    293     public void setCurrentTabFromContent(AppsCustomizePagedView.ContentType type) {
    294         setOnTabChangedListener(null);
    295         setCurrentTabByTag(getTabTagForContentType(type));
    296         setOnTabChangedListener(this);
    297     }
    298 
    299     /**
    300      * Returns the content type for the specified tab tag.
    301      */
    302     public AppsCustomizePagedView.ContentType getContentTypeForTabTag(String tag) {
    303         if (tag.equals(APPS_TAB_TAG)) {
    304             return AppsCustomizePagedView.ContentType.Applications;
    305         } else if (tag.equals(WIDGETS_TAB_TAG)) {
    306             return AppsCustomizePagedView.ContentType.Widgets;
    307         }
    308         return AppsCustomizePagedView.ContentType.Applications;
    309     }
    310 
    311     /**
    312      * Returns the tab tag for a given content type.
    313      */
    314     public String getTabTagForContentType(AppsCustomizePagedView.ContentType type) {
    315         if (type == AppsCustomizePagedView.ContentType.Applications) {
    316             return APPS_TAB_TAG;
    317         } else if (type == AppsCustomizePagedView.ContentType.Widgets) {
    318             return WIDGETS_TAB_TAG;
    319         }
    320         return APPS_TAB_TAG;
    321     }
    322 
    323     /**
    324      * Disable focus on anything under this view in the hierarchy if we are not visible.
    325      */
    326     @Override
    327     public int getDescendantFocusability() {
    328         if (getVisibility() != View.VISIBLE) {
    329             return ViewGroup.FOCUS_BLOCK_DESCENDANTS;
    330         }
    331         return super.getDescendantFocusability();
    332     }
    333 
    334     void reset() {
    335         if (mInTransition) {
    336             // Defer to after the transition to reset
    337             mResetAfterTransition = true;
    338         } else {
    339             // Reset immediately
    340             mAppsCustomizePane.reset();
    341         }
    342     }
    343 
    344     private void enableAndBuildHardwareLayer() {
    345         // isHardwareAccelerated() checks if we're attached to a window and if that
    346         // window is HW accelerated-- we were sometimes not attached to a window
    347         // and buildLayer was throwing an IllegalStateException
    348         if (isHardwareAccelerated()) {
    349             // Turn on hardware layers for performance
    350             setLayerType(LAYER_TYPE_HARDWARE, null);
    351 
    352             // force building the layer, so you don't get a blip early in an animation
    353             // when the layer is created layer
    354             buildLayer();
    355         }
    356     }
    357 
    358     @Override
    359     public View getContent() {
    360         return mContent;
    361     }
    362 
    363     /* LauncherTransitionable overrides */
    364     @Override
    365     public void onLauncherTransitionPrepare(Launcher l, boolean animated, boolean toWorkspace) {
    366         mAppsCustomizePane.onLauncherTransitionPrepare(l, animated, toWorkspace);
    367         mInTransition = true;
    368         mTransitioningToWorkspace = toWorkspace;
    369 
    370         if (toWorkspace) {
    371             // Going from All Apps -> Workspace
    372             setVisibilityOfSiblingsWithLowerZOrder(VISIBLE);
    373             // Stop the scrolling indicator - we don't want All Apps to be invalidating itself
    374             // during the transition, especially since it has a hardware layer set on it
    375             mAppsCustomizePane.cancelScrollingIndicatorAnimations();
    376         } else {
    377             // Going from Workspace -> All Apps
    378             mContent.setVisibility(VISIBLE);
    379 
    380             // Make sure the current page is loaded (we start loading the side pages after the
    381             // transition to prevent slowing down the animation)
    382             mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
    383 
    384             if (!LauncherApplication.isScreenLarge()) {
    385                 mAppsCustomizePane.showScrollingIndicator(true);
    386             }
    387         }
    388 
    389         if (mResetAfterTransition) {
    390             mAppsCustomizePane.reset();
    391             mResetAfterTransition = false;
    392         }
    393     }
    394 
    395     @Override
    396     public void onLauncherTransitionStart(Launcher l, boolean animated, boolean toWorkspace) {
    397         if (animated) {
    398             enableAndBuildHardwareLayer();
    399         }
    400     }
    401 
    402     @Override
    403     public void onLauncherTransitionStep(Launcher l, float t) {
    404         // Do nothing
    405     }
    406 
    407     @Override
    408     public void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace) {
    409         mAppsCustomizePane.onLauncherTransitionEnd(l, animated, toWorkspace);
    410         mInTransition = false;
    411         if (animated) {
    412             setLayerType(LAYER_TYPE_NONE, null);
    413         }
    414 
    415         if (!toWorkspace) {
    416             // Going from Workspace -> All Apps
    417             setVisibilityOfSiblingsWithLowerZOrder(INVISIBLE);
    418 
    419             // Dismiss the workspace cling and show the all apps cling (if not already shown)
    420             l.dismissWorkspaceCling(null);
    421             mAppsCustomizePane.showAllAppsCling();
    422             // Make sure adjacent pages are loaded (we wait until after the transition to
    423             // prevent slowing down the animation)
    424             mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
    425 
    426             if (!LauncherApplication.isScreenLarge()) {
    427                 mAppsCustomizePane.hideScrollingIndicator(false);
    428             }
    429         }
    430     }
    431 
    432     private void setVisibilityOfSiblingsWithLowerZOrder(int visibility) {
    433         ViewGroup parent = (ViewGroup) getParent();
    434         if (parent == null) return;
    435 
    436         final int count = parent.getChildCount();
    437         if (!isChildrenDrawingOrderEnabled()) {
    438             for (int i = 0; i < count; i++) {
    439                 final View child = parent.getChildAt(i);
    440                 if (child == this) {
    441                     break;
    442                 } else {
    443                     if (child.getVisibility() == GONE) {
    444                         continue;
    445                     }
    446                     child.setVisibility(visibility);
    447                 }
    448             }
    449         } else {
    450             throw new RuntimeException("Failed; can't get z-order of views");
    451         }
    452     }
    453 
    454     public void onWindowVisible() {
    455         if (getVisibility() == VISIBLE) {
    456             mContent.setVisibility(VISIBLE);
    457             // We unload the widget previews when the UI is hidden, so need to reload pages
    458             // Load the current page synchronously, and the neighboring pages asynchronously
    459             mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage(), true);
    460             mAppsCustomizePane.loadAssociatedPages(mAppsCustomizePane.getCurrentPage());
    461         }
    462     }
    463 
    464     public void onTrimMemory() {
    465         mContent.setVisibility(GONE);
    466         // Clear the widget pages of all their subviews - this will trigger the widget previews
    467         // to delete their bitmaps
    468         mAppsCustomizePane.clearAllWidgetPages();
    469     }
    470 
    471     boolean isTransitioning() {
    472         return mInTransition;
    473     }
    474 }
    475