/*
* 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.view.View.OnTouchListener;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
public class RecentsHorizontalScrollView extends HorizontalScrollView
implements SwipeHelper.Callback {
private static final String TAG = RecentsPanelView.TAG;
private static final boolean DEBUG = RecentsPanelView.DEBUG;
private LinearLayout mLinearLayout;
private TaskDescriptionAdapter mAdapter;
private RecentsCallback mCallback;
protected int mLastScrollPosition;
private SwipeHelper mSwipeHelper;
private RecentsScrollViewPerformanceHelper mPerformanceHelper;
public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs, 0);
float densityScale = getResources().getDisplayMetrics().density;
float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false);
}
private int scrollPositionOfMostRecent() {
return mLinearLayout.getWidth() - getWidth();
}
private void update() {
mLinearLayout.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
final View view = mAdapter.getView(i, null, mLinearLayout);
if (mPerformanceHelper != null) {
mPerformanceHelper.addViewCallback(view);
}
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);
mLinearLayout.addView(view);
}
// Scroll to end after layout.
post(new Runnable() {
public void run() {
mLastScrollPosition = scrollPositionOfMostRecent();
scrollTo(mLastScrollPosition, 0);
}
});
}
@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 (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,
0, 0,
getLeftFadingEdgeStrength(), getRightFadingEdgeStrength());
}
}
@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(mLastScrollPosition, 0);
}
}
});
}
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(TaskDescriptionAdapter 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;
}
}