diff options
18 files changed, 709 insertions, 244 deletions
diff --git a/api/current.txt b/api/current.txt index b81c83347b7e..150af9a32bb7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -23673,7 +23673,11 @@ package android.provider { public final class TvContract { method public static final android.net.Uri buildChannelUri(long); + method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName); + method public static final android.net.Uri buildChannelsUriForInput(android.content.ComponentName, boolean); method public static final android.net.Uri buildProgramUri(long); + method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri); + method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long); field public static final java.lang.String AUTHORITY = "com.android.tv"; } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 528e1194c18c..722d9568f4e4 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -455,22 +455,25 @@ public final class CameraCharacteristics extends CameraMetadata { * <p>The maximum numbers of different types of output streams * that can be configured and used simultaneously by a camera device.</p> * <p>This is a 3 element tuple that contains the max number of output simultaneous - * streams for raw sensor, processed (and uncompressed), and JPEG formats respectively. - * For example, if max raw sensor format output stream number is 1, max YUV streams + * streams for raw sensor, processed (but not stalling), and processed (and stalling) + * formats respectively. For example, assuming that JPEG is typically a processed and + * stalling stream, if max raw sensor format output stream number is 1, max YUV streams * number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p> * <p>This lists the upper bound of the number of output streams supported by * the camera device. Using more streams simultaneously may require more hardware and * CPU resources that will consume more power. The image format for a output stream can - * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats}. The formats - * defined in {@link CameraCharacteristics#SCALER_AVAILABLE_FORMATS android.scaler.availableFormats} can be catergorized into the 3 stream types - * as below:</p> + * be any supported format provided by {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations}. + * The formats defined in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS android.scaler.availableStreamConfigurations} can be catergorized + * into the 3 stream types as below:</p> * <ul> - * <li>JPEG-compressed format: BLOB.</li> - * <li>Raw formats: RAW_SENSOR and RAW_OPAQUE.</li> - * <li>processed, uncompressed formats: YCbCr_420_888, YCrCb_420_SP, YV12.</li> + * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. + * Typically JPEG format (ImageFormat#JPEG).</li> + * <li>Raw formats: ImageFormat#RAW_SENSOR and ImageFormat#RAW_OPAQUE.</li> + * <li>Processed (but not-stalling): any non-RAW format without a stall duration. + * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li> * </ul> * - * @see CameraCharacteristics#SCALER_AVAILABLE_FORMATS + * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_CONFIGURATIONS */ public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS = new Key<int[]>("android.request.maxNumOutputStreams", int[].class); diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java index 233e0caaec4f..62252be15fef 100644 --- a/core/java/android/provider/TvContract.java +++ b/core/java/android/provider/TvContract.java @@ -16,9 +16,13 @@ package android.provider; +import android.content.ComponentName; +import android.content.ContentResolver; import android.content.ContentUris; import android.net.Uri; +import java.util.List; + /** * <p> * The contract between the TV provider and applications. Contains definitions for the supported @@ -42,6 +46,35 @@ public final class TvContract { /** The authority for the TV provider. */ public static final String AUTHORITY = "com.android.tv"; + private static final String PATH_CHANNEL = "channel"; + private static final String PATH_PROGRAM = "program"; + private static final String PATH_INPUT = "input"; + + /** + * An optional query, update or delete URI parameter that allows the caller to specify start + * time (in milliseconds since the epoch) to filter programs. + * + * @hide + */ + public static final String PARAM_START_TIME = "start_time"; + + /** + * An optional query, update or delete URI parameter that allows the caller to specify end time + * (in milliseconds since the epoch) to filter programs. + * + * @hide + */ + public static final String PARAM_END_TIME = "end_time"; + + /** + * A query, update or delete URI parameter that allows the caller to operate on all or + * browsable-only channels. If set to "true", the rows that contain non-browsable channels are + * not affected. + * + * @hide + */ + public static final String PARAM_BROWSABLE_ONLY = "browable_only"; + /** * Builds a URI that points to a specific channel. * @@ -52,6 +85,32 @@ public final class TvContract { } /** + * Builds a URI that points to all browsable channels from a given TV input. + * + * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements + * the given TV input. + */ + public static final Uri buildChannelsUriForInput(ComponentName name) { + return buildChannelsUriForInput(name, true); + } + + /** + * Builds a URI that points to all or browsable-only channels from a given TV input. + * + * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements + * the given TV input. + * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set + * to {@code false} the URI points to all channels regardless of whether they are + * browsable or not. + */ + public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) + .appendPath(PATH_INPUT).appendPath(name.getPackageName()) + .appendPath(name.getClassName()).appendPath(PATH_CHANNEL) + .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build(); + } + + /** * Builds a URI that points to a specific program. * * @param programId The ID of the program to point to. @@ -61,6 +120,37 @@ public final class TvContract { } /** + * Builds a URI that points to all programs on a given channel. + * + * @param channelUri The URI of the channel to return programs for. + */ + public static final Uri buildProgramsUriForChannel(Uri channelUri) { + if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) { + throw new IllegalArgumentException("Not a channel: " + channelUri); + } + String channelId = String.valueOf(ContentUris.parseId(channelUri)); + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY) + .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build(); + } + + /** + * Builds a URI that points to programs on a specific channel whose schedules overlap with the + * given time frame. + * + * @param channelUri The URI of the channel to return programs for. + * @param startTime The start time used to filter programs. The returned programs should have + * {@link Programs#END_TIME_UTC_MILLIS} that is greater than this time. + * @param endTime The end time used to filter programs. The returned programs should have + * {@link Programs#START_TIME_UTC_MILLIS} that is less than this time. + */ + public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime, + long endTime) { + Uri uri = buildProgramsUriForChannel(channelUri); + return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime)) + .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build(); + } + + /** * Builds a URI that points to a specific program the user watched. * * @param watchedProgramId The ID of the watched program to point to. @@ -70,6 +160,61 @@ public final class TvContract { return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId); } + /** + * Extracts the {@link Channels#PACKAGE_NAME} from a given URI. + * + * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or + * {@link #buildChannelsUriForInput(ComponentName, boolean)}. + * @hide + */ + public static final String getPackageName(Uri channelsUri) { + final List<String> paths = channelsUri.getPathSegments(); + if (paths.size() < 4) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + return paths.get(1); + } + + /** + * Extracts the {@link Channels#SERVICE_NAME} from a given URI. + * + * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or + * {@link #buildChannelsUriForInput(ComponentName, boolean)}. + * @hide + */ + public static final String getServiceName(Uri channelsUri) { + final List<String> paths = channelsUri.getPathSegments(); + if (paths.size() < 4) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) { + throw new IllegalArgumentException("Not channels: " + channelsUri); + } + return paths.get(2); + } + + /** + * Extracts the {@link Channels#_ID} from a given URI. + * + * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or + * {@link #buildProgramsUriForChannel(Uri, long, long)}. + * @hide + */ + public static final String getChannelId(Uri programsUri) { + final List<String> paths = programsUri.getPathSegments(); + if (paths.size() < 3) { + throw new IllegalArgumentException("Not programs: " + programsUri); + } + if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) { + throw new IllegalArgumentException("Not programs: " + programsUri); + } + return paths.get(1); + } + + private TvContract() {} /** @@ -93,7 +238,8 @@ public final class TvContract { public static final class Channels implements BaseTvColumns { /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/channel"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + + PATH_CHANNEL); /** The MIME type of a directory of TV channels. */ public static final String CONTENT_TYPE = @@ -276,7 +422,8 @@ public final class TvContract { public static final class Programs implements BaseTvColumns { /** The content:// style URI for this table. */ - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/program"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + + PATH_PROGRAM); /** The MIME type of a directory of TV programs. */ public static final String CONTENT_TYPE = diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml index d81e525bf3de..2e08bff92a1e 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml @@ -18,10 +18,9 @@ <com.android.systemui.statusbar.NotificationOverflowContainer xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="32dp" android:focusable="true" android:clickable="true" - android:background="@*android:drawable/notification_quantum_bg_dim" > <TextView android:id="@+id/more_text" diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 41e7dac84edd..8959a5b56fe8 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -4,14 +4,13 @@ android:layout_height="wrap_content" android:focusable="true" android:clickable="true" - android:background="@*android:drawable/notification_quantum_bg" > - <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expanded" + <com.android.systemui.statusbar.NotificationContentView android:id="@+id/expanded" android:layout_width="match_parent" android:layout_height="wrap_content" /> - <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/expandedPublic" + <com.android.systemui.statusbar.NotificationContentView android:id="@+id/expandedPublic" android:layout_width="match_parent" android:layout_height="wrap_content" /> diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index c585a5b928c0..61c268e65417 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -31,8 +31,9 @@ import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewConfiguration; -import android.view.ViewGroup; +import com.android.systemui.statusbar.ExpandableView; +import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.ScrollAdapter; public class ExpandHelper implements Gefingerpoken, OnClickListener { @@ -115,9 +116,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { float focusY = detector.getFocusY(); final View underFocus = findView(focusX, focusY); - if (underFocus != null) { - startExpanding(underFocus, STRETCH); - } + startExpanding(underFocus, STRETCH); return mExpanding; } @@ -133,41 +132,21 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { }; private class ViewScaler { - View mView; + ExpandableView mView; public ViewScaler() {} - public void setView(View v) { + public void setView(ExpandableView v) { mView = v; } public void setHeight(float h) { if (DEBUG_SCALE) Log.v(TAG, "SetHeight: setting to " + h); - ViewGroup.LayoutParams lp = mView.getLayoutParams(); - lp.height = (int)h; - mView.setLayoutParams(lp); - mView.requestLayout(); + mView.setActualHeight((int) h); } public float getHeight() { - int height = mView.getLayoutParams().height; - if (height < 0) { - height = mView.getMeasuredHeight(); - } - return height; + return mView.getActualHeight(); } public int getNaturalHeight(int maximum) { - ViewGroup.LayoutParams lp = mView.getLayoutParams(); - if (DEBUG_SCALE) Log.v(TAG, "Inspecting a child of type: " + - mView.getClass().getName()); - int oldHeight = lp.height; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - mView.setLayoutParams(lp); - mView.measure( - View.MeasureSpec.makeMeasureSpec(mView.getMeasuredWidth(), - View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(maximum, - View.MeasureSpec.AT_MOST)); - lp.height = oldHeight; - mView.setLayoutParams(lp); - return mView.getMeasuredHeight(); + return Math.min(maximum, mView.getMaxHeight()); } } @@ -189,12 +168,6 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { mGravity = Gravity.TOP; mScaleAnimation = ObjectAnimator.ofFloat(mScaler, "height", 0f); mScaleAnimation.setDuration(EXPAND_DURATION); - mScaleAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCallback.setUserLockedChild(mCurrView, false); - } - }); mPopLimit = mContext.getResources().getDimension(R.dimen.blinds_pop_threshold); mPopDuration = mContext.getResources().getInteger(R.integer.blinds_pop_duration_ms); mPullGestureMinXSpan = mContext.getResources().getDimension(R.dimen.pull_span_min); @@ -341,9 +314,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (DEBUG_SCALE) Log.v(TAG, "got pull gesture (xspan=" + xspan + "px)"); final View underFocus = findView(x, y); - if (underFocus != null) { - startExpanding(underFocus, PULL); - } + startExpanding(underFocus, PULL); return true; } if (mScrollAdapter != null && !mScrollAdapter.isScrolledToTop()) { @@ -358,8 +329,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)"); mLastMotionY = y; final View underFocus = findView(x, y); - if (underFocus != null) { - startExpanding(underFocus, BLINDS); + if (startExpanding(underFocus, BLINDS)) { mInitialTouchY = mLastMotionY; mHasPopped = false; } @@ -459,16 +429,22 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { return true; } - private void startExpanding(View v, int expandType) { + /** + * @return True if the view is expandable, false otherwise. + */ + private boolean startExpanding(View v, int expandType) { + if (!(v instanceof ExpandableNotificationRow)) { + return false; + } mExpansionStyle = expandType; - if (mExpanding && v == mCurrView) { - return; + if (mExpanding && v == mCurrView) { + return true; } mExpanding = true; if (DEBUG) Log.d(TAG, "scale type " + expandType + " beginning on view: " + v); mCallback.setUserLockedChild(v, true); setView(v); - mScaler.setView(v); + mScaler.setView((ExpandableView) v); mOldHeight = mScaler.getHeight(); if (mCallback.canChildBeExpanded(v)) { if (DEBUG) Log.d(TAG, "working on an expandable child"); @@ -480,6 +456,7 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight + " mNaturalHeight: " + mNaturalHeight); v.getParent().requestDisallowInterceptTouchEvent(true); + return true; } private void finishExpanding(boolean force) { @@ -499,10 +476,18 @@ public class ExpandHelper implements Gefingerpoken, OnClickListener { if (mScaleAnimation.isRunning()) { mScaleAnimation.cancel(); } - mCallback.setUserExpandedChild(mCurrView, h == mNaturalHeight); + mCallback.setUserExpandedChild(mCurrView, targetHeight == mNaturalHeight); if (targetHeight != currentHeight) { mScaleAnimation.setFloatValues(targetHeight); mScaleAnimation.setupStartValues(); + final View scaledView = mCurrView; + mScaleAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCallback.setUserLockedChild(scaledView, false); + mScaleAnimation.removeListener(this); + } + }); mScaleAnimation.start(); } else { mCallback.setUserLockedChild(mCurrView, false); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index d647dfa82045..0f32dc015a0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -29,7 +29,7 @@ import com.android.internal.R; * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} * to implement dimming/activating on Keyguard for the double-tap gesture */ -public class ActivatableNotificationView extends FrameLayout { +public abstract class ActivatableNotificationView extends ExpandableOutlineView { private static final long DOUBLETAP_TIMEOUT_MS = 1000; @@ -54,6 +54,7 @@ public class ActivatableNotificationView extends FrameLayout { public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + updateBackgroundResource(); } @@ -84,6 +85,9 @@ public class ActivatableNotificationView extends FrameLayout { case MotionEvent.ACTION_DOWN: mDownX = event.getX(); mDownY = event.getY(); + if (mDownY > getActualHeight()) { + return false; + } // Call the listener tentatively directly, even if we don't know whether the user // will stay within the touch slop, as the listener is implemented as a scale @@ -122,7 +126,7 @@ public class ActivatableNotificationView extends FrameLayout { } private void makeActive(float x, float y) { - getBackground().setHotspot(0, x, y); + mCustomBackground.setHotspot(0, x, y); mActivated = true; } @@ -132,8 +136,8 @@ public class ActivatableNotificationView extends FrameLayout { private void makeInactive() { if (mActivated) { // Make sure that we clear the hotspot from the center. - getBackground().setHotspot(0, getWidth() / 2, getHeight() / 2); - getBackground().removeHotspot(0); + mCustomBackground.setHotspot(0, getWidth() / 2, getActualHeight() / 2); + mCustomBackground.removeHotspot(0); mActivated = false; } if (mOnActivatedListener != null) { @@ -178,7 +182,19 @@ public class ActivatableNotificationView extends FrameLayout { } private void updateBackgroundResource() { - setBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId); + setCustomBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + setPivotX(getWidth()/2); + } + + @Override + public void setActualHeight(int actualHeight) { + super.setActualHeight(actualHeight); + setPivotY(actualHeight/2); } public void setOnActivatedListener(OnActivatedListener onActivatedListener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 3e2164077820..90b63f48f7ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -760,9 +760,10 @@ public abstract class BaseStatusBar extends SystemUI implements // NB: the large icon is now handled entirely by the template // bind the click event to the content area - SizeAdaptiveLayout expanded = (SizeAdaptiveLayout)row.findViewById(R.id.expanded); - SizeAdaptiveLayout expandedPublic - = (SizeAdaptiveLayout)row.findViewById(R.id.expandedPublic); + NotificationContentView expanded = + (NotificationContentView) row.findViewById(R.id.expanded); + NotificationContentView expandedPublic = + (NotificationContentView) row.findViewById(R.id.expandedPublic); row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); @@ -794,19 +795,11 @@ public abstract class BaseStatusBar extends SystemUI implements if (contentViewLocal != null) { contentViewLocal.setIsRootNamespace(true); - SizeAdaptiveLayout.LayoutParams params = - new SizeAdaptiveLayout.LayoutParams(contentViewLocal.getLayoutParams()); - params.minHeight = minHeight; - params.maxHeight = minHeight; - expanded.addView(contentViewLocal, params); + expanded.setContractedChild(contentViewLocal); } if (bigContentViewLocal != null) { bigContentViewLocal.setIsRootNamespace(true); - SizeAdaptiveLayout.LayoutParams params = - new SizeAdaptiveLayout.LayoutParams(bigContentViewLocal.getLayoutParams()); - params.minHeight = minHeight+1; - params.maxHeight = maxHeight; - expanded.addView(bigContentViewLocal, params); + expanded.setExpandedChild(bigContentViewLocal); } PackageManager pm = mContext.getPackageManager(); @@ -820,11 +813,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (publicViewLocal != null) { publicViewLocal.setIsRootNamespace(true); - SizeAdaptiveLayout.LayoutParams params = - new SizeAdaptiveLayout.LayoutParams(publicViewLocal.getLayoutParams()); - params.minHeight = minHeight; - params.maxHeight = minHeight; - expandedPublic.addView(publicViewLocal, params); + expandedPublic.setContractedChild(publicViewLocal); } } catch (RuntimeException e) { @@ -1352,6 +1341,7 @@ public abstract class BaseStatusBar extends SystemUI implements } else { entry.row.setOnClickListener(null); } + entry.row.notifyContentUpdated(); } protected void notifyHeadsUpScreenOn(boolean screenOn) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 35c02eb67a6c..b813e6573639 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -17,12 +17,12 @@ package com.android.systemui.statusbar; import android.content.Context; +import android.graphics.Canvas; import android.util.AttributeSet; +import android.util.Log; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; -import com.android.internal.widget.SizeAdaptiveLayout; import com.android.systemui.R; public class ExpandableNotificationRow extends ActivatableNotificationView { @@ -45,12 +45,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * user expansion. */ private boolean mIsSystemExpanded; - private SizeAdaptiveLayout mPublicLayout; - private SizeAdaptiveLayout mPrivateLayout; + private NotificationContentView mPublicLayout; + private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; - private boolean mMaxHeightNeedsUpdate; private NotificationActivator mActivator; - private boolean mSelfInitiatedLayout; public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); @@ -59,8 +57,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override protected void onFinishInflate() { super.onFinishInflate(); - mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic); - mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded); + mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); + mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); mActivator = new NotificationActivator(this); } @@ -82,7 +80,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { public void setHeightRange(int rowMinHeight, int rowMaxHeight) { mRowMinHeight = rowMinHeight; mRowMaxHeight = rowMaxHeight; - mMaxHeightNeedsUpdate = true; } public boolean isExpandable() { @@ -145,13 +142,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * @param expand should the layout be in the expanded state */ public void applyExpansionToLayout(boolean expand) { - ViewGroup.LayoutParams lp = getLayoutParams(); if (expand && mExpandable) { - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + setActualHeight(mMaxExpandHeight); } else { - lp.height = mRowMinHeight; + setActualHeight(mRowMinHeight); } - setLayoutParams(lp); } /** @@ -161,6 +156,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * @return the maximum allowed expansion height of this view. */ public int getMaximumAllowedExpandHeight() { + if (isUserLocked()) { + return getActualHeight(); + } boolean inExpansionState = isExpanded(); if (!inExpansionState) { // not expanded, so we return the collapsed size @@ -170,31 +168,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mShowingPublic ? mRowMinHeight : getMaxExpandHeight(); } - private void updateMaxExpandHeight() { - - // We don't want this method to trigger a layout of the whole view hierarchy, - // as the layout parameters in the end are the same which they were in the beginning. - // Otherwise a loop may occur if this method is called on the layout of a parent. - mSelfInitiatedLayout = true; - ViewGroup.LayoutParams lp = getLayoutParams(); - int oldHeight = lp.height; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - setLayoutParams(lp); - measure(View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY), - View.MeasureSpec.makeMeasureSpec(mRowMaxHeight, View.MeasureSpec.AT_MOST)); - lp.height = oldHeight; - setLayoutParams(lp); - mMaxExpandHeight = getMeasuredHeight(); - mSelfInitiatedLayout = false; - } - - @Override - public void requestLayout() { - if (!mSelfInitiatedLayout) { - super.requestLayout(); - } - } - /** * Check whether the view state is currently expanded. This is given by the system in {@link * #setSystemExpanded(boolean)} and can be overridden by user expansion or @@ -210,7 +183,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mMaxHeightNeedsUpdate = true; + boolean updateExpandHeight = mMaxExpandHeight == 0; + mMaxExpandHeight = mPrivateLayout.getMaxHeight(); + if (updateExpandHeight) { + applyExpansionToLayout(isExpanded()); + } } public void setShowingPublic(boolean show) { @@ -233,10 +210,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } public int getMaxExpandHeight() { - if (mMaxHeightNeedsUpdate) { - updateMaxExpandHeight(); - mMaxHeightNeedsUpdate = false; - } return mMaxExpandHeight; } @@ -248,6 +221,28 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { * @return the potential height this view could expand in addition. */ public int getExpandPotential() { - return getMaximumAllowedExpandHeight() - getHeight(); + return getMaximumAllowedExpandHeight() - getActualHeight(); + } + + @Override + public void setActualHeight(int height) { + mPrivateLayout.setActualHeight(height); + invalidate(); + super.setActualHeight(height); + } + + @Override + public int getMaxHeight() { + return mPrivateLayout.getMaxHeight(); + } + + @Override + public void setClipTopAmount(int clipTopAmount) { + super.setClipTopAmount(clipTopAmount); + mPrivateLayout.setClipTopAmount(clipTopAmount); + } + + public void notifyContentUpdated() { + mPrivateLayout.notifyContentUpdated(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java new file mode 100644 index 000000000000..43eb5b5f952e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 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.statusbar; + +import android.content.Context; +import android.graphics.Outline; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * Like {@link ExpandableView}, but setting an outline for the height and clipping. + */ +public abstract class ExpandableOutlineView extends ExpandableView { + + private final Outline mOutline = new Outline(); + + public ExpandableOutlineView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setActualHeight(int actualHeight) { + super.setActualHeight(actualHeight); + updateOutline(); + } + + @Override + public void setClipTopAmount(int clipTopAmount) { + super.setClipTopAmount(clipTopAmount); + updateOutline(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateOutline(); + } + + private void updateOutline() { + mOutline.setRect(0, + mClipTopAmount, + getWidth(), + Math.max(mActualHeight, mClipTopAmount)); + setOutline(mOutline); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java new file mode 100644 index 000000000000..35913fa9a1bd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2014 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.statusbar; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +/** + * An abstract view for expandable views. + */ +public abstract class ExpandableView extends FrameLayout { + + private OnHeightChangedListener mOnHeightChangedListener; + protected int mActualHeight; + protected int mClipTopAmount; + protected Drawable mCustomBackground; + private boolean mActualHeightInitialized; + + public ExpandableView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mCustomBackground != null) { + mCustomBackground.setBounds(0, mClipTopAmount, getWidth(), mActualHeight); + mCustomBackground.draw(canvas); + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return super.verifyDrawable(who) || who == mCustomBackground; + } + + @Override + protected void drawableStateChanged() { + final Drawable d = mCustomBackground; + if (d != null && d.isStateful()) { + d.setState(getDrawableState()); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (!mActualHeightInitialized && mActualHeight == 0) { + mActualHeight = getHeight(); + } + mActualHeightInitialized = true; + } + + /** + * Sets the actual height of this notification. This is different than the laid out + * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding. + */ + public void setActualHeight(int actualHeight) { + mActualHeight = actualHeight; + invalidate(); + if (mOnHeightChangedListener != null) { + mOnHeightChangedListener.onHeightChanged(this); + } + } + + /** + * See {@link #setActualHeight}. + * + * @return The actual height of this notification. + */ + public int getActualHeight() { + return mActualHeight; + } + + /** + * @return The maximum height of this notification. + */ + public abstract int getMaxHeight(); + + /** + * Sets the amount this view should be clipped from the top. This is used when an expanded + * notification is scrolling in the top or bottom stack. + * + * @param clipTopAmount The amount of pixels this view should be clipped from top. + */ + public void setClipTopAmount(int clipTopAmount) { + mClipTopAmount = clipTopAmount; + invalidate(); + } + + public void setOnHeightChangedListener(OnHeightChangedListener listener) { + mOnHeightChangedListener = listener; + } + + /** + * Sets a custom background drawable. As we need to change our bounds independently of layout, + * we need the notition of a custom background. + */ + public void setCustomBackground(Drawable customBackground) { + if (mCustomBackground != null) { + mCustomBackground.setCallback(null); + unscheduleDrawable(mCustomBackground); + } + mCustomBackground = customBackground; + mCustomBackground.setCallback(this); + setWillNotDraw(customBackground == null); + invalidate(); + } + + public void setCustomBackgroundResource(int drawableResId) { + setCustomBackground(getResources().getDrawable(drawableResId)); + } + + /** + * A listener notifying when {@link #getActualHeight} changes. + */ + public interface OnHeightChangedListener { + void onHeightChanged(ExpandableView view); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java new file mode 100644 index 000000000000..fd0cb08c3d2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 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.statusbar; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; + +import com.android.systemui.R; + +/** + * A frame layout containing the actual payload of the notification, including the contracted and + * expanded layout. This class is responsible for clipping the content and and switching between the + * expanded and contracted view depending on its clipped size. + */ +public class NotificationContentView extends ExpandableView { + + private final Rect mClipBounds = new Rect(); + + private View mContractedChild; + private View mExpandedChild; + + private int mSmallHeight; + + public NotificationContentView(Context context, AttributeSet attrs) { + super(context, attrs); + mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); + mActualHeight = mSmallHeight; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateClipping(); + } + + public void setContractedChild(View child) { + if (mContractedChild != null) { + removeView(mContractedChild); + } + sanitizeContractedLayoutParams(child); + addView(child); + mContractedChild = child; + selectLayout(); + } + + public void setExpandedChild(View child) { + if (mExpandedChild != null) { + removeView(mExpandedChild); + } + addView(child); + mExpandedChild = child; + selectLayout(); + } + + @Override + public void setActualHeight(int actualHeight) { + super.setActualHeight(actualHeight); + selectLayout(); + updateClipping(); + } + + @Override + public int getMaxHeight() { + + // The maximum height is just the laid out height. + return getHeight(); + } + + @Override + public void setClipTopAmount(int clipTopAmount) { + super.setClipTopAmount(clipTopAmount); + updateClipping(); + } + + public int getClipTopAmount() { + return mClipTopAmount; + } + + private void updateClipping() { + mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight); + setClipBounds(mClipBounds); + } + + private void sanitizeContractedLayoutParams(View contractedChild) { + LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams(); + lp.height = mSmallHeight; + contractedChild.setLayoutParams(lp); + } + + private void selectLayout() { + if (mActualHeight <= mSmallHeight || mExpandedChild == null) { + if (mContractedChild.getVisibility() != View.VISIBLE) { + mContractedChild.setVisibility(View.VISIBLE); + } + if (mExpandedChild != null && mExpandedChild.getVisibility() != View.INVISIBLE) { + mExpandedChild.setVisibility(View.INVISIBLE); + } + } else { + if (mExpandedChild.getVisibility() != View.VISIBLE) { + mExpandedChild.setVisibility(View.VISIBLE); + } + if (mContractedChild.getVisibility() != View.INVISIBLE) { + mContractedChild.setVisibility(View.INVISIBLE); + } + } + } + + public void notifyContentUpdated() { + selectLayout(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java index af913147d823..8ebd50d3947c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java @@ -35,6 +35,26 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { } @Override + public void setActualHeight(int currentHeight) { + // noop + } + + @Override + public int getActualHeight() { + return getHeight(); + } + + @Override + public int getMaxHeight() { + return getHeight(); + } + + @Override + public void setClipTopAmount(int clipTopAmount) { + // noop + } + + @Override protected void onFinishInflate() { super.onFinishInflate(); mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view); @@ -43,6 +63,7 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { mActivator = new NotificationActivator(this); mActivator.setDimmed(true); setLocked(true); + setDimmed(true); } public NotificationOverflowIconsView getIconsView() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 5d758018cf21..82fbb1648b86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -23,10 +23,12 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; -public class NotificationPanelView extends PanelView { +public class NotificationPanelView extends PanelView implements + ExpandableView.OnHeightChangedListener { public static final boolean DEBUG_GESTURES = true; PhoneStatusBar mStatusBar; @@ -67,6 +69,7 @@ public class NotificationPanelView extends PanelView { mKeyguardStatusView = findViewById(R.id.keyguard_status_view); mNotificationStackScroller = (NotificationStackScrollLayout) findViewById(R.id.notification_stack_scroller); + mNotificationStackScroller.setOnHeightChangedListener(this); mNotificationParent = findViewById(R.id.notification_container_parent); } @@ -218,4 +221,9 @@ public class NotificationPanelView extends PanelView { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); } + + @Override + public void onHeightChanged(ExpandableView view) { + requestPanelHeightUpdate(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java index a5e016aca97a..1bc97a075ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java @@ -92,6 +92,7 @@ public abstract class ZenModeViewAdapter implements ZenModeView.Adapter { mExitIndex = 0; dispatchChanged(); } + setZenModeCondition(); } @Override @@ -143,7 +144,15 @@ public abstract class ZenModeViewAdapter implements ZenModeView.Adapter { } mExitIndex = i; dispatchChanged(); - final Uri conditionUri = (Uri) ec.tag; + setZenModeCondition(); + } + + private void setZenModeCondition() { + if (mExitIndex < 0 || mExitIndex >= mExits.size()) { + Log.w(TAG, "setZenModeCondition to bad index " + mExitIndex + " of " + mExits.size()); + return; + } + final Uri conditionUri = (Uri) mExits.get(mExitIndex).tag; try { mNoMan.setZenModeCondition(conditionUri); } catch (RemoteException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 948ef907dbb5..d6d90a63df26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -37,6 +37,7 @@ import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -44,7 +45,8 @@ import com.android.systemui.statusbar.policy.ScrollAdapter; * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ public class NotificationStackScrollLayout extends ViewGroup - implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter { + implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter, + ExpandableView.OnHeightChangedListener { private static final String TAG = "NotificationStackScrollLayout"; private static final boolean DEBUG = false; @@ -78,6 +80,7 @@ public class NotificationStackScrollLayout extends ViewGroup private int mBottomStackPeekSize; private int mEmptyMarginBottom; private int mPaddingBetweenElements; + private boolean mListenForHeightChanges = true; /** * The algorithm which calculates the properties for our children @@ -90,6 +93,7 @@ public class NotificationStackScrollLayout extends ViewGroup private final StackScrollState mCurrentStackScrollState = new StackScrollState(this); private OnChildLocationsChangedListener mListener; + private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -227,7 +231,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (!isCurrentlyAnimating()) { mCurrentStackScrollState.setScrollY(mOwnScrollY); mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState); + mListenForHeightChanges = false; mCurrentStackScrollState.apply(); + mListenForHeightChanges = true; if (mListener != null) { mListener.onChildLocationsChanged(this); } @@ -306,12 +312,12 @@ public class NotificationStackScrollLayout extends ViewGroup // find the view under the pointer, accounting for GONE views final int count = getChildCount(); for (int childIdx = 0; childIdx < count; childIdx++) { - View slidingChild = getChildAt(childIdx); + ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx); if (slidingChild.getVisibility() == GONE) { continue; } float top = slidingChild.getTranslationY(); - float bottom = top + slidingChild.getHeight(); + float bottom = top + slidingChild.getActualHeight(); int left = slidingChild.getLeft(); int right = slidingChild.getRight(); @@ -615,16 +621,11 @@ public class NotificationStackScrollLayout extends ViewGroup private int getScrollRange() { int scrollRange = 0; - View firstChild = getFirstChildNotGone(); + ExpandableView firstChild = (ExpandableView) getFirstChildNotGone(); if (firstChild != null) { int contentHeight = getContentHeight(); int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild); - int firstChildExpandPotential = firstChildMaxExpandHeight - firstChild.getHeight(); - // If we already scrolled in, the first child is layouted smaller than it actually - // could be when expanded. We have to compensate for this loss of the contentHeight - // by adding the expand potential again. - contentHeight += firstChildExpandPotential; scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize); if (scrollRange > 0 && getChildCount() > 0) { // We want to at least be able collapse the first item and not ending in a weird @@ -666,10 +667,17 @@ public class NotificationStackScrollLayout extends ViewGroup for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (child.getVisibility() != View.GONE) { - height += child.getHeight(); - if (i < getChildCount()-1) { + if (height != 0) { + // add the padding before this element height += mPaddingBetweenElements; } + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + height += row.getMaximumAllowedExpandHeight(); + } else if (child instanceof ExpandableView) { + ExpandableView expandableView = (ExpandableView) child; + height += expandableView.getActualHeight(); + } } } mContentHeight = height; @@ -723,6 +731,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override protected void onViewRemoved(View child) { super.onViewRemoved(child); + ((ExpandableView) child).setOnHeightChangedListener(null); mCurrentStackScrollState.removeViewStateForView(child); mStackScrollAlgorithm.notifyChildrenChanged(this); } @@ -731,6 +740,7 @@ public class NotificationStackScrollLayout extends ViewGroup protected void onViewAdded(View child) { super.onViewAdded(child); mStackScrollAlgorithm.notifyChildrenChanged(this); + ((ExpandableView) child).setOnHeightChangedListener(this); } private boolean onInterceptTouchEventScroll(MotionEvent ev) { @@ -891,6 +901,23 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @Override + public void onHeightChanged(ExpandableView view) { + if (mListenForHeightChanges) { + updateContentHeight(); + updateScrollPositionIfNecessary(); + if (mOnHeightChangedListener != null) { + mOnHeightChangedListener.onHeightChanged(view); + } + updateChildren(); + } + } + + public void setOnHeightChangedListener( + ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { + this.mOnHeightChangedListener = mOnHeightChangedListener; + } + /** * A listener that is notified when some child locations might have changed. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 2a6e4aef86c7..acd1c6ac212e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -23,6 +23,7 @@ import android.view.ViewGroup; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.ExpandableView; import java.util.ArrayList; @@ -53,7 +54,7 @@ public class StackScrollAlgorithm { private boolean mIsExpansionChanging; private int mFirstChildMaxHeight; private boolean mIsExpanded; - private View mFirstChildWhileExpanding; + private ExpandableView mFirstChildWhileExpanding; private boolean mExpandedOnStart; private int mTopStackTotalSize; @@ -82,7 +83,7 @@ public class StackScrollAlgorithm { mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor( MAX_ITEMS_IN_BOTTOM_STACK, mBottomStackPeekSize, - mCollapsedSize + mPaddingBetweenElements + mBottomStackPeekSize, + mCollapsedSize + mBottomStackPeekSize + mPaddingBetweenElements, 0.5f); } @@ -101,11 +102,10 @@ public class StackScrollAlgorithm { algorithmState.scrolledPixelsTop = 0; algorithmState.itemsInBottomStack = 0.0f; algorithmState.partialInBottom = 0.0f; + algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize; updateVisibleChildren(resultState, algorithmState); - algorithmState.scrollY = getAlgorithmScrollPosition(resultState, algorithmState); - // Phase 1: findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState); @@ -117,42 +117,6 @@ public class StackScrollAlgorithm { } /** - * Calculates the scroll offset of the algorithm, based on the resultState. - * - * @param resultState the state to base the calculation on - * @param algorithmState The state in which the current pass of the algorithm is currently in - * @return the scroll offset used for the algorithm - */ - private int getAlgorithmScrollPosition(StackScrollState resultState, - StackScrollAlgorithmState algorithmState) { - - int resultScroll = resultState.getScrollY() + mCollapsedSize; - - // If the first child was collapsed in an earlier pass, we have to decrease the scroll - // position to get into the same state again. - if (algorithmState.visibleChildren.size() > 0) { - View firstView = algorithmState.visibleChildren.get(0); - if (firstView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow firstRow = (ExpandableNotificationRow) firstView; - if (firstRow.isUserLocked()) { - // User is currently modifying this height. - return resultScroll; - } - int scrolledInAmount = 0; - // If the child size was not decreased due to scrolling, we don't substract it, - if (!mIsExpansionChanging) { - scrolledInAmount = firstRow.getExpandPotential(); - } else if (mExpandedOnStart && mFirstChildWhileExpanding == firstView) { - scrolledInAmount = firstRow.getMaximumAllowedExpandHeight() - - mFirstChildMaxHeight; - } - resultScroll -= scrolledInAmount; - } - } - return resultScroll; - } - - /** * Update the visible children on the state. */ private void updateVisibleChildren(StackScrollState resultState, @@ -162,7 +126,7 @@ public class StackScrollAlgorithm { state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); for (int i = 0; i < childCount; i++) { - View v = hostView.getChildAt(i); + ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { state.visibleChildren.add(v); } @@ -194,10 +158,10 @@ public class StackScrollAlgorithm { int childCount = algorithmState.visibleChildren.size(); int numberOfElementsCompletelyIn = (int) algorithmState.itemsInTopStack; for (int i = 0; i < childCount; i++) { - View child = algorithmState.visibleChildren.get(i); + ExpandableView child = algorithmState.visibleChildren.get(i); StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); childViewState.location = StackScrollState.ViewState.LOCATION_UNKNOWN; - int childHeight = child.getHeight(); + int childHeight = getMaxAllowedChildHeight(child); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -291,7 +255,7 @@ public class StackScrollAlgorithm { /** * Clamp the yTranslation of the child up such that its end is at lest on the end of the top - * stack. + * stack.get * * @param childViewState the view state of the child * @param childHeight the height of this child @@ -306,8 +270,11 @@ public class StackScrollAlgorithm { if (child instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) child; return row.getMaximumAllowedExpandHeight(); + } else if (child instanceof ExpandableView) { + ExpandableView expandableView = (ExpandableView) child; + return expandableView.getActualHeight(); } - return child.getHeight(); + return child == null? mCollapsedSize : child.getHeight(); } private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState, @@ -419,9 +386,9 @@ public class StackScrollAlgorithm { // find the number of elements in the top stack. for (int i = 0; i < childCount; i++) { - View child = algorithmState.visibleChildren.get(i); + ExpandableView child = algorithmState.visibleChildren.get(i); StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); - int childHeight = child.getHeight(); + int childHeight = getMaxAllowedChildHeight(child); float yPositionInScrollViewAfterElement = yPositionInScrollView + childHeight + mPaddingBetweenElements; @@ -524,13 +491,13 @@ public class StackScrollAlgorithm { } private void updateFirstChildHeightWhileExpanding(ViewGroup hostView) { - mFirstChildWhileExpanding = findFirstVisibleChild(hostView); + mFirstChildWhileExpanding = (ExpandableView) findFirstVisibleChild(hostView); if (mFirstChildWhileExpanding != null) { if (mExpandedOnStart) { // We are collapsing the shade, so the first child can get as most as high as the // current height. - mFirstChildMaxHeight = mFirstChildWhileExpanding.getHeight(); + mFirstChildMaxHeight = mFirstChildWhileExpanding.getActualHeight(); } else { // We are expanding the shade, expand it to its full height. @@ -627,7 +594,7 @@ public class StackScrollAlgorithm { /** * The children from the host view which are not gone. */ - public final ArrayList<View> visibleChildren = new ArrayList<View>(); + public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 6e2e87e4b115..92151109cc7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -22,7 +22,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; -import com.android.systemui.R; +import com.android.systemui.statusbar.ExpandableView; import java.util.HashMap; import java.util.Map; @@ -36,12 +36,11 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private Map<View, ViewState> mStateMap; + private Map<ExpandableView, ViewState> mStateMap; private int mScrollY; private final Rect mClipRect = new Rect(); private int mBackgroundRoundedRectCornerRadius; private final Outline mChildOutline = new Outline(); - private final int mChildDividerHeight; public int getScrollY() { return mScrollY; @@ -53,11 +52,9 @@ public class StackScrollState { public StackScrollState(ViewGroup hostView) { mHostView = hostView; - mStateMap = new HashMap<View, ViewState>(); + mStateMap = new HashMap<ExpandableView, ViewState>(); mBackgroundRoundedRectCornerRadius = hostView.getResources().getDimensionPixelSize( com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); - mChildDividerHeight = hostView.getResources().getDimensionPixelSize(R.dimen - .notification_divider_height); } public ViewGroup getHostView() { @@ -67,14 +64,14 @@ public class StackScrollState { public void resetViewStates() { int numChildren = mHostView.getChildCount(); for (int i = 0; i < numChildren; i++) { - View child = mHostView.getChildAt(i); + ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState viewState = mStateMap.get(child); if (viewState == null) { viewState = new ViewState(); mStateMap.put(child, viewState); } // initialize with the default values of the view - viewState.height = child.getHeight(); + viewState.height = child.getActualHeight(); viewState.alpha = 1; viewState.gone = child.getVisibility() == View.GONE; } @@ -98,7 +95,7 @@ public class StackScrollState { float previousNotificationEnd = 0; float previousNotificationStart = 0; for (int i = 0; i < numChildren; i++) { - View child = mHostView.getChildAt(i); + ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState state = mStateMap.get(child); if (state == null) { Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + @@ -109,7 +106,7 @@ public class StackScrollState { float alpha = child.getAlpha(); float yTranslation = child.getTranslationY(); float zTranslation = child.getTranslationZ(); - int height = child.getHeight(); + int height = child.getActualHeight(); float newAlpha = state.alpha; float newYTranslation = state.yTranslation; float newZTranslation = state.zTranslation; @@ -152,14 +149,14 @@ public class StackScrollState { // apply height if (height != newHeight) { - applyNewHeight(child, newHeight); + child.setActualHeight(newHeight); } // apply clipping and shadow float newNotificationEnd = newYTranslation + newHeight; - updateChildClippingAndShadow(child, newHeight, - newNotificationEnd - (previousNotificationEnd - mChildDividerHeight), - newHeight - (previousNotificationStart - newYTranslation)); + updateChildClippingAndBackground(child, newHeight, + newNotificationEnd - (previousNotificationEnd), + (int) (newHeight - (previousNotificationStart - newYTranslation))); previousNotificationStart = newYTranslation; previousNotificationEnd = newNotificationEnd; @@ -173,20 +170,21 @@ public class StackScrollState { * @param child the view to update * @param realHeight the currently applied height of the view * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - * @param shadowHeight the desired height of the shadow, the shadow ends on the bottom + * @param backgroundHeight the desired background height. The shadows of the view will be + * based on this height and the content will be clipped from the top */ - private void updateChildClippingAndShadow(View child, int realHeight, float clipHeight, - float shadowHeight) { - if (realHeight > shadowHeight) { - updateChildOutline(child, realHeight, shadowHeight); - } else { - updateChildOutline(child, realHeight, realHeight); - } + private void updateChildClippingAndBackground(ExpandableView child, int realHeight, + float clipHeight, int backgroundHeight) { if (realHeight > clipHeight) { updateChildClip(child, realHeight, clipHeight); } else { child.setClipBounds(null); } + if (realHeight > backgroundHeight) { + child.setClipTopAmount(realHeight - backgroundHeight); + } else { + child.setClipTopAmount(0); + } } /** @@ -205,37 +203,6 @@ public class StackScrollState { child.setClipBounds(mClipRect); } - /** - * Updates the outline of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param outlineHeight the desired height of the outline, the outline ends on the bottom - */ - private void updateChildOutline(View child, int height, - float outlineHeight) { - int shadowInset = (int) (height - outlineHeight); - getOutlineForSize(child.getLeft(), - child.getTop() + shadowInset, - child.getWidth(), - child.getHeight() - shadowInset, - mChildOutline); - child.setOutline(mChildOutline); - } - - private void getOutlineForSize(int leftInset, int topInset, int width, int height, - Outline result) { - result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height, - mBackgroundRoundedRectCornerRadius); - } - - private void applyNewHeight(View child, int newHeight) { - ViewGroup.LayoutParams lp = child.getLayoutParams(); - lp.height = newHeight; - child.setLayoutParams(lp); - } - - public static class ViewState { // These are flags such that we can create masks for filtering. |