| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.recent; |
| |
| import android.animation.LayoutTransition; |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.database.DataSetObserver; |
| import android.graphics.Canvas; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.widget.LinearLayout; |
| import android.widget.ScrollView; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.SwipeHelper; |
| import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; |
| |
| public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback { |
| private static final String TAG = RecentsPanelView.TAG; |
| private static final boolean DEBUG = RecentsPanelView.DEBUG; |
| private LinearLayout mLinearLayout; |
| private ActivityDescriptionAdapter mAdapter; |
| private RecentsCallback mCallback; |
| protected int mLastScrollPosition; |
| private SwipeHelper mSwipeHelper; |
| private RecentsScrollViewPerformanceHelper mPerformanceHelper; |
| |
| public RecentsVerticalScrollView(Context context, AttributeSet attrs) { |
| super(context, attrs, 0); |
| float densityScale = getResources().getDisplayMetrics().density; |
| float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); |
| mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); |
| |
| mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, true); |
| } |
| |
| private int scrollPositionOfMostRecent() { |
| return mLinearLayout.getHeight() - getHeight(); |
| } |
| |
| private void update() { |
| mLinearLayout.removeAllViews(); |
| // Once we can clear the data associated with individual item views, |
| // we can get rid of the removeAllViews() and the code below will |
| // recycle them. |
| for (int i = 0; i < mAdapter.getCount(); i++) { |
| View old = null; |
| if (i < mLinearLayout.getChildCount()) { |
| old = mLinearLayout.getChildAt(i); |
| old.setVisibility(View.VISIBLE); |
| } |
| final View view = mAdapter.getView(i, old, mLinearLayout); |
| |
| if (mPerformanceHelper != null) { |
| mPerformanceHelper.addViewCallback(view); |
| } |
| |
| if (old == null) { |
| OnTouchListener noOpListener = new OnTouchListener() { |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| return true; |
| } |
| }; |
| |
| view.setOnClickListener(new OnClickListener() { |
| public void onClick(View v) { |
| mCallback.dismiss(); |
| } |
| }); |
| // We don't want a click sound when we dimiss recents |
| view.setSoundEffectsEnabled(false); |
| |
| OnClickListener launchAppListener = new OnClickListener() { |
| public void onClick(View v) { |
| mCallback.handleOnClick(view); |
| } |
| }; |
| |
| final View thumbnailView = view.findViewById(R.id.app_thumbnail); |
| OnLongClickListener longClickListener = new OnLongClickListener() { |
| public boolean onLongClick(View v) { |
| final View anchorView = view.findViewById(R.id.app_description); |
| mCallback.handleLongPress(view, anchorView, thumbnailView); |
| return true; |
| } |
| }; |
| thumbnailView.setClickable(true); |
| thumbnailView.setOnClickListener(launchAppListener); |
| thumbnailView.setOnLongClickListener(longClickListener); |
| |
| // We don't want to dismiss recents if a user clicks on the app title |
| // (we also don't want to launch the app either, though, because the |
| // app title is a small target and doesn't have great click feedback) |
| final View appTitle = view.findViewById(R.id.app_label); |
| appTitle.setContentDescription(" "); |
| appTitle.setOnTouchListener(noOpListener); |
| final View calloutLine = view.findViewById(R.id.recents_callout_line); |
| calloutLine.setOnTouchListener(noOpListener); |
| mLinearLayout.addView(view); |
| } |
| } |
| for (int i = mAdapter.getCount(); i < mLinearLayout.getChildCount(); i++) { |
| mLinearLayout.getChildAt(i).setVisibility(View.GONE); |
| } |
| // Scroll to end after layout. |
| post(new Runnable() { |
| public void run() { |
| mLastScrollPosition = scrollPositionOfMostRecent(); |
| scrollTo(0, mLastScrollPosition); |
| } |
| }); |
| } |
| |
| @Override |
| public void removeViewInLayout(final View view) { |
| dismissChild(view); |
| } |
| |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); |
| return mSwipeHelper.onInterceptTouchEvent(ev) || |
| super.onInterceptTouchEvent(ev); |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| return mSwipeHelper.onTouchEvent(ev) || |
| super.onTouchEvent(ev); |
| } |
| |
| public boolean canChildBeDismissed(View v) { |
| return true; |
| } |
| |
| public void dismissChild(View v) { |
| mSwipeHelper.dismissChild(v, 0); |
| } |
| |
| public void onChildDismissed(View v) { |
| mLinearLayout.removeView(v); |
| mCallback.handleSwipe(v); |
| } |
| |
| public void onBeginDrag(View v) { |
| // We do this so the underlying ScrollView knows that it won't get |
| // the chance to intercept events anymore |
| requestDisallowInterceptTouchEvent(true); |
| v.setActivated(true); |
| } |
| |
| public void onDragCancelled(View v) { |
| v.setActivated(false); |
| } |
| |
| public View getChildAtPosition(MotionEvent ev) { |
| final float x = ev.getX() + getScrollX(); |
| final float y = ev.getY() + getScrollY(); |
| for (int i = 0; i < mLinearLayout.getChildCount(); i++) { |
| View item = mLinearLayout.getChildAt(i); |
| if (item.getVisibility() == View.VISIBLE |
| && x >= item.getLeft() && x < item.getRight() |
| && y >= item.getTop() && y < item.getBottom()) { |
| return item; |
| } |
| } |
| return null; |
| } |
| |
| public View getChildContentView(View v) { |
| return v.findViewById(R.id.recent_item); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| super.onLayout(changed, left, top, right, bottom); |
| if (mPerformanceHelper != null) { |
| mPerformanceHelper.onLayoutCallback(); |
| } |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| super.draw(canvas); |
| |
| if (mPerformanceHelper != null) { |
| int paddingLeft = mPaddingLeft; |
| final boolean offsetRequired = isPaddingOffsetRequired(); |
| if (offsetRequired) { |
| paddingLeft += getLeftPaddingOffset(); |
| } |
| |
| int left = mScrollX + paddingLeft; |
| int right = left + mRight - mLeft - mPaddingRight - paddingLeft; |
| int top = mScrollY + getFadeTop(offsetRequired); |
| int bottom = top + getFadeHeight(offsetRequired); |
| |
| if (offsetRequired) { |
| right += getRightPaddingOffset(); |
| bottom += getBottomPaddingOffset(); |
| } |
| mPerformanceHelper.drawCallback(canvas, |
| left, right, top, bottom, mScrollX, mScrollY, |
| getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(), |
| 0, 0); |
| } |
| } |
| |
| @Override |
| public int getVerticalFadingEdgeLength() { |
| if (mPerformanceHelper != null) { |
| return mPerformanceHelper.getVerticalFadingEdgeLengthCallback(); |
| } else { |
| return super.getVerticalFadingEdgeLength(); |
| } |
| } |
| |
| @Override |
| public int getHorizontalFadingEdgeLength() { |
| if (mPerformanceHelper != null) { |
| return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback(); |
| } else { |
| return super.getHorizontalFadingEdgeLength(); |
| } |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| setScrollbarFadingEnabled(true); |
| mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout); |
| final int leftPadding = mContext.getResources() |
| .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin); |
| setOverScrollEffectPadding(leftPadding, 0); |
| } |
| |
| @Override |
| public void onAttachedToWindow() { |
| if (mPerformanceHelper != null) { |
| mPerformanceHelper.onAttachedToWindowCallback( |
| mCallback, mLinearLayout, isHardwareAccelerated()); |
| } |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| float densityScale = getResources().getDisplayMetrics().density; |
| mSwipeHelper.setDensityScale(densityScale); |
| float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); |
| mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); |
| } |
| |
| private void setOverScrollEffectPadding(int leftPadding, int i) { |
| // TODO Add to (Vertical)ScrollView |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| super.onSizeChanged(w, h, oldw, oldh); |
| |
| // Skip this work if a transition is running; it sets the scroll values independently |
| // and should not have those animated values clobbered by this logic |
| LayoutTransition transition = mLinearLayout.getLayoutTransition(); |
| if (transition != null && transition.isRunning()) { |
| return; |
| } |
| // Keep track of the last visible item in the list so we can restore it |
| // to the bottom when the orientation changes. |
| mLastScrollPosition = scrollPositionOfMostRecent(); |
| |
| // This has to happen post-layout, so run it "in the future" |
| post(new Runnable() { |
| public void run() { |
| // Make sure we're still not clobbering the transition-set values, since this |
| // runnable launches asynchronously |
| LayoutTransition transition = mLinearLayout.getLayoutTransition(); |
| if (transition == null || !transition.isRunning()) { |
| scrollTo(0, mLastScrollPosition); |
| } |
| } |
| }); |
| } |
| |
| public void onRecentsVisibilityChanged() { |
| if (mPerformanceHelper != null) { |
| mPerformanceHelper.updateShowBackground(); |
| } |
| } |
| |
| @Override |
| protected void onVisibilityChanged(View changedView, int visibility) { |
| super.onVisibilityChanged(changedView, visibility); |
| // scroll to bottom after reloading |
| if (visibility == View.VISIBLE && changedView == this) { |
| post(new Runnable() { |
| public void run() { |
| update(); |
| } |
| }); |
| } |
| } |
| |
| public void setAdapter(ActivityDescriptionAdapter adapter) { |
| mAdapter = adapter; |
| mAdapter.registerDataSetObserver(new DataSetObserver() { |
| public void onChanged() { |
| update(); |
| } |
| |
| public void onInvalidated() { |
| update(); |
| } |
| }); |
| } |
| |
| @Override |
| public void setLayoutTransition(LayoutTransition transition) { |
| if (mPerformanceHelper != null) { |
| mPerformanceHelper.setLayoutTransitionCallback(transition); |
| } |
| // The layout transition applies to our embedded LinearLayout |
| mLinearLayout.setLayoutTransition(transition); |
| } |
| |
| public void setCallback(RecentsCallback callback) { |
| mCallback = callback; |
| } |
| } |