Home | History | Annotate | Download | only in launcher3
      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