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