diff options
author | Daniel Sandler <dsandler@android.com> | 2012-03-12 14:38:58 -0400 |
---|---|---|
committer | Chris Wren <cwren@android.com> | 2012-04-18 11:21:19 -0400 |
commit | 6a858c347f4d4e5db4c8f00d5e285967631b71ca (patch) | |
tree | d84fd54c5ffff04165ede028ed205a0781002da1 /packages | |
parent | 3d100d97a55c5aba2cac5599a158fe3759d278ca (diff) | |
download | frameworks_base-6a858c347f4d4e5db4c8f00d5e285967631b71ca.zip frameworks_base-6a858c347f4d4e5db4c8f00d5e285967631b71ca.tar.gz frameworks_base-6a858c347f4d4e5db4c8f00d5e285967631b71ca.tar.bz2 |
Gestures for expanding notifications.
Change-Id: I104c157ffcc2d60b3f0a95c59d4322b07103b69f
Diffstat (limited to 'packages')
11 files changed, 525 insertions, 278 deletions
diff --git a/packages/SystemUI/res/layout/notification_adaptive_wrapper.xml b/packages/SystemUI/res/layout/notification_adaptive_wrapper.xml new file mode 100644 index 0000000..15d0890 --- /dev/null +++ b/packages/SystemUI/res/layout/notification_adaptive_wrapper.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<SizeAdaptiveLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:background="@android:color/background_dark" + android:id="@+id/notification_adaptive_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index c307c6e..a0d1b08 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -1,6 +1,6 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="@dimen/notification_height" + android:layout_height="wrap_content" > <Button @@ -21,7 +21,14 @@ android:focusable="true" android:clickable="true" android:background="@drawable/notification_row_bg" - /> + > + + <com.android.internal.widget.SizeAdaptiveLayout android:id="@+id/adaptive" + android:background="@android:color/background_dark" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.android.systemui.statusbar.LatestItemView> <View android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4441ca6..f786e86 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -55,6 +55,13 @@ <!-- Height of notification icons in the status bar --> <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen> + <!-- Height of a small notification in the status bar --> + <dimen name="notification_min_height">@android:dimen/notification_large_icon_height</dimen> + + <!-- Height of a small notification in the status bar --> + <!-- TODO: change this back to 256dp once we deal with actions. --> + <dimen name="notification_max_height">320dp</dimen> + <!-- size at which Notification icons will be drawn in the status bar --> <dimen name="status_bar_icon_drawing_size">18dip</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java new file mode 100644 index 0000000..aa289da --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2012 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; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.RectF; +import android.util.Log; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.ViewGroup; +import android.view.View.OnClickListener; +import com.android.internal.widget.SizeAdaptiveLayout; + +public class ExpandHelper implements Gefingerpoken, OnClickListener { + public interface Callback { + View getChildAtPosition(MotionEvent ev); + View getChildAtPosition(float x, float y); + } + + private static final String TAG = "ExpandHelper"; + protected static final boolean DEBUG = false; + private static final long EXPAND_DURATION = 250; + + @SuppressWarnings("unused") + private Context mContext; + + private boolean mStretching; + private View mCurrView; + private float mOldHeight; + private float mNaturalHeight; + private float mInitialTouchSpan; + private Callback mCallback; + private ScaleGestureDetector mDetector; + private ViewScaler mScaler; + private ObjectAnimator mAnimation; + + private int mSmallSize; + private int mLargeSize; + + + private class ViewScaler { + View mView; + public ViewScaler() {} + public void setView(View v) { + mView = v; + } + public void setHeight(float h) { + Log.v(TAG, "SetHeight: setting to " + h); + ViewGroup.LayoutParams lp = mView.getLayoutParams(); + lp.height = (int)h; + mView.setLayoutParams(lp); + mView.requestLayout(); + } + public float getHeight() { + int height = mView.getLayoutParams().height; + if (height < 0) { + height = mView.getMeasuredHeight(); + } + return (float) height; + } + public int getNaturalHeight(int maximum) { + ViewGroup.LayoutParams lp = mView.getLayoutParams(); + if (DEBUG) 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(); + } + } + + public ExpandHelper(Context context, Callback callback, int small, int large) { + mSmallSize = small; + mLargeSize = large; + mContext = context; + mCallback = callback; + mScaler = new ViewScaler(); + mDetector = + new ScaleGestureDetector(context, + new ScaleGestureDetector.SimpleOnScaleGestureListener() { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + if (DEBUG) Log.v(TAG, "onscalebegin()"); + View v = mCallback.getChildAtPosition(detector.getFocusX(), detector.getFocusY()); + + // your fingers have to be somewhat close to the bounds of the view in question + mInitialTouchSpan = Math.abs(detector.getCurrentSpanY()); + if (DEBUG) Log.d(TAG, "got mInitialTouchSpan: " + mInitialTouchSpan); + + mStretching = initScale(v); + return mStretching; + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + if (DEBUG) Log.v(TAG, "onscale() on " + mCurrView); + float h = Math.abs(detector.getCurrentSpanY()); + if (DEBUG) Log.d(TAG, "current span is: " + h); + h = h + mOldHeight - mInitialTouchSpan; + h = h<mSmallSize?mSmallSize:(h>mLargeSize?mLargeSize:h); + h = h>mNaturalHeight?mNaturalHeight:h; + if (DEBUG) Log.d(TAG, "scale continues: h=" + h); + mScaler.setHeight(h); + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + if (DEBUG) Log.v(TAG, "onscaleend()"); + // I guess we're alone now + if (DEBUG) Log.d(TAG, "scale end"); + finishScale(false); + } + }); + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (DEBUG) Log.d(TAG, "interceptTouch: act=" + (ev.getAction()) + + " stretching=" + mStretching); + mDetector.onTouchEvent(ev); + return mStretching; + } + + public boolean onTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + if (DEBUG) Log.d(TAG, "touch: act=" + (action) + " stretching=" + mStretching); + if (mStretching) { + mDetector.onTouchEvent(ev); + } + switch (action) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mStretching = false; + mCurrView = null; + break; + } + return true; + } + private boolean initScale(View v) { + if (v != null) { + if (DEBUG) Log.d(TAG, "scale begins on view: " + v); + mStretching = true; + mCurrView = v; + mScaler.setView(v); + mOldHeight = mScaler.getHeight(); + mNaturalHeight = mScaler.getNaturalHeight(mLargeSize); + if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight + + " mNaturalHeight: " + mNaturalHeight); + v.getParent().requestDisallowInterceptTouchEvent(true); + if (DEBUG) v.setBackgroundColor(0xFFFFFF00); + } + return mStretching; + } + + private void finishScale(boolean force) { + float h = mScaler.getHeight(); + final boolean wasClosed = (mOldHeight == mSmallSize); + if (wasClosed) { + h = (force || h > mSmallSize) ? mNaturalHeight : mSmallSize; + } else { + h = (force || h < mNaturalHeight) ? mSmallSize : mNaturalHeight; + } + if (DEBUG) mCurrView.setBackgroundColor(0); + mAnimation = ObjectAnimator.ofFloat(mScaler, "height", h).setDuration(EXPAND_DURATION); + mAnimation.start(); + mStretching = false; + mCurrView = null; + } + + @Override + public void onClick(View v) { + initScale(v); + finishScale(true); + + } +} diff --git a/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java b/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java new file mode 100644 index 0000000..b2d5c21 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012 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; + +import android.view.MotionEvent; + +// ACHTUNG! +public interface Gefingerpoken { + boolean onInterceptTouchEvent(MotionEvent ev); + boolean onTouchEvent(MotionEvent ev); +} diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 276ca21..19657a9 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -29,7 +29,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -public class SwipeHelper { +public class SwipeHelper implements Gefingerpoken { static final String TAG = "com.android.systemui.SwipeHelper"; private static final boolean DEBUG = false; private static final boolean DEBUG_INVALIDATE = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 8d7afc8..ede8e7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -18,9 +18,14 @@ package com.android.systemui.statusbar; import java.util.ArrayList; +import android.app.ActivityManagerNative; +import android.app.KeyguardManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Rect; import android.os.Build; import android.os.Handler; import android.os.IBinder; @@ -34,15 +39,18 @@ import android.view.IWindowManager; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.widget.LinearLayout; +import android.widget.RemoteViews; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.statusbar.StatusBarNotification; +import com.android.internal.widget.SizeAdaptiveLayout; import com.android.systemui.SystemUI; import com.android.systemui.recent.RecentsPanelView; import com.android.systemui.recent.RecentTasksLoader; @@ -66,12 +74,15 @@ public abstract class BaseStatusBar extends SystemUI implements protected IStatusBarService mBarService; protected H mHandler = createHandler(); + // used to notify status bar for suppressing notification LED + protected boolean mPanelSlightlyVisible; + // Recent apps protected RecentsPanelView mRecentsPanel; protected RecentTasksLoader mRecentTasksLoader; // UI-specific methods - + /** * Create all windows necessary for the status bar (including navigation, overlay panels, etc) * and add them to the window manager. @@ -81,15 +92,15 @@ public abstract class BaseStatusBar extends SystemUI implements protected Display mDisplay; private IWindowManager mWindowManager; - + public IWindowManager getWindowManager() { return mWindowManager; } - + public Display getDisplay() { return mDisplay; } - + public IStatusBarService getStatusBarService() { return mBarService; } @@ -109,7 +120,7 @@ public abstract class BaseStatusBar extends SystemUI implements ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>(); mCommandQueue = new CommandQueue(this, iconList); - + int[] switches = new int[7]; ArrayList<IBinder> binders = new ArrayList<IBinder>(); try { @@ -118,7 +129,7 @@ public abstract class BaseStatusBar extends SystemUI implements } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. } - + createAndAddWindows(); disable(switches[0]); @@ -152,7 +163,7 @@ public abstract class BaseStatusBar extends SystemUI implements if (DEBUG) { Slog.d(TAG, String.format( - "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", + "init: icons=%d disabled=0x%08x lights=0x%08x menu=0x%08x imeButton=0x%08x", iconList.size(), switches[0], switches[1], @@ -161,7 +172,7 @@ public abstract class BaseStatusBar extends SystemUI implements )); } } - + protected View updateNotificationVetoButton(View row, StatusBarNotification n) { View vetoButton = row.findViewById(R.id.veto); if (n.isClearable()) { @@ -183,7 +194,7 @@ public abstract class BaseStatusBar extends SystemUI implements } return vetoButton; } - + protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { if (sbn.notification.contentView.getLayoutId() != @@ -323,4 +334,178 @@ public abstract class BaseStatusBar extends SystemUI implements return false; } } + + protected void workAroundBadLayerDrawableOpacity(View v) { + } + + protected boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { + int minHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.notification_min_height); + int maxHeight = + mContext.getResources().getDimensionPixelSize(R.dimen.notification_max_height); + StatusBarNotification sbn = entry.notification; + RemoteViews oneU = sbn.notification.contentView; + RemoteViews large = sbn.notification.bigContentView; + if (oneU == null) { + return false; + } + + // create the row view + LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); + // XXX: temporary: while testing big notifications, auto-expand all of them + ViewGroup.LayoutParams lp = row.getLayoutParams(); + if (sbn.notification.bigContentView != null) { + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + } else { + lp.height = minHeight; + } + row.setLayoutParams(lp); + workAroundBadLayerDrawableOpacity(row); + View vetoButton = updateNotificationVetoButton(row, sbn); + vetoButton.setContentDescription(mContext.getString( + R.string.accessibility_remove_notification)); + + // NB: the large icon is now handled entirely by the template + + // bind the click event to the content area + ViewGroup content = (ViewGroup)row.findViewById(R.id.content); + ViewGroup adaptive = (ViewGroup)row.findViewById(R.id.adaptive); + // XXX: update to allow controls within notification views + content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); +// content.setOnFocusChangeListener(mFocusChangeListener); + PendingIntent contentIntent = sbn.notification.contentIntent; + if (contentIntent != null) { + final View.OnClickListener listener = new NotificationClicker(contentIntent, + sbn.pkg, sbn.tag, sbn.id); + content.setOnClickListener(listener); + } else { + content.setOnClickListener(null); + } + + View expandedOneU = null; + View expandedLarge = null; + Exception exception = null; + try { + expandedOneU = oneU.apply(mContext, adaptive); + if (large != null) { + expandedLarge = large.apply(mContext, adaptive); + } + } + catch (RuntimeException e) { + exception = e; + } + if (expandedOneU == null && expandedLarge == null) { + final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); + Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); + return false; + } else { + if (expandedOneU != null) { + SizeAdaptiveLayout.LayoutParams params = + new SizeAdaptiveLayout.LayoutParams(expandedOneU.getLayoutParams()); + params.minHeight = minHeight; + params.maxHeight = minHeight; + adaptive.addView(expandedOneU, params); + } + if (expandedLarge != null) { + SizeAdaptiveLayout.LayoutParams params = + new SizeAdaptiveLayout.LayoutParams(expandedLarge.getLayoutParams()); + params.minHeight = minHeight+1; + params.maxHeight = SizeAdaptiveLayout.LayoutParams.UNBOUNDED; + adaptive.addView(expandedLarge, params); + } + row.setDrawingCacheEnabled(true); + } + + applyLegacyRowBackground(sbn, content); + + entry.row = row; + entry.content = content; + entry.expanded = expandedOneU; + entry.expandedLarge = expandedOneU; + + return true; + } + + public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { + return new NotificationClicker(intent, pkg, tag, id); + } + + private class NotificationClicker implements View.OnClickListener { + private PendingIntent mIntent; + private String mPkg; + private String mTag; + private int mId; + + NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { + mIntent = intent; + mPkg = pkg; + mTag = tag; + mId = id; + } + + public void onClick(View v) { + try { + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManagerNative.getDefault().resumeAppSwitches(); + // Also, notifications can be launched from the lock screen, + // so dismiss the lock screen when the activity starts. + ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); + } catch (RemoteException e) { + } + + if (mIntent != null) { + int[] pos = new int[2]; + v.getLocationOnScreen(pos); + Intent overlay = new Intent(); + overlay.setSourceBounds( + new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); + try { + mIntent.send(mContext, 0, overlay); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. Just log the exception message. + Slog.w(TAG, "Sending contentIntent failed: " + e); + } + + KeyguardManager kgm = + (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + if (kgm != null) kgm.exitKeyguardSecurely(null); + } + + try { + mBarService.onNotificationClick(mPkg, mTag, mId); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + + // close the shade if it was open + animateCollapse(); + visibilityChanged(false); + + // If this click was on the intruder alert, hide that instead +// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); + } + } + /** + * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. + * This was added last-minute and is inconsistent with the way the rest of the notifications + * are handled, because the notification isn't really cancelled. The lights are just + * turned off. If any other notifications happen, the lights will turn back on. Steve says + * this is what he wants. (see bug 1131461) + */ + protected void visibilityChanged(boolean visible) { + if (mPanelSlightlyVisible != visible) { + mPanelSlightlyVisible = visible; + try { + mBarService.onPanelRevealed(); + } catch (RemoteException ex) { + // Won't fail unless the world has ended. + } + } + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 6fbcd64..3ff85d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -38,6 +38,7 @@ public class NotificationData { public View content; // takes the click events and sends the PendingIntent public View expanded; // the inflated RemoteViews public ImageView largeIcon; + public View expandedLarge; public Entry() {} public Entry(IBinder key, StatusBarNotification n, StatusBarIconView ic) { this.key = key; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 3f611fc..f45b3ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -28,14 +28,11 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; -import android.os.Build; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; @@ -50,11 +47,9 @@ import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.Window; @@ -68,26 +63,25 @@ import android.widget.RemoteViews; import android.widget.ScrollView; import android.widget.TextView; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; - import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.R; -import com.android.systemui.SwipeHelper; import com.android.systemui.recent.RecentTasksLoader; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.IntruderAlertView; import com.android.systemui.statusbar.policy.DateView; +import com.android.systemui.statusbar.policy.IntruderAlertView; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NotificationRowLayout; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + public class PhoneStatusBar extends BaseStatusBar { static final String TAG = "PhoneStatusBar"; public static final boolean DEBUG = false; @@ -832,73 +826,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { - StatusBarNotification sbn = entry.notification; - // XXX: temporary: while testing big notifications, auto-expand all of them - final boolean big = (sbn.notification.bigContentView != null); - RemoteViews remoteViews = big ? sbn.notification.bigContentView - : sbn.notification.contentView; - if (remoteViews == null) { - return false; - } - - // create the row view - LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); - ViewGroup.LayoutParams lp = row.getLayoutParams(); - if (big) { - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; - } else { - lp.height = mContext.getResources().getDimensionPixelSize(R.dimen.notification_height); - } - row.setLayoutParams(lp); - View vetoButton = updateNotificationVetoButton(row, sbn); - vetoButton.setContentDescription(mContext.getString( - R.string.accessibility_remove_notification)); - - // NB: the large icon is now handled entirely by the template - - // bind the click event to the content area - ViewGroup content = (ViewGroup)row.findViewById(R.id.content); - // XXX: update to allow controls within notification views - content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); -// content.setOnFocusChangeListener(mFocusChangeListener); - PendingIntent contentIntent = sbn.notification.contentIntent; - if (contentIntent != null) { - final View.OnClickListener listener = new NotificationClicker(contentIntent, - sbn.pkg, sbn.tag, sbn.id); - content.setOnClickListener(listener); - } else { - content.setOnClickListener(null); - } - - View expanded = null; - Exception exception = null; - try { - expanded = remoteViews.apply(mContext, content); - } - catch (RuntimeException e) { - exception = e; - } - if (expanded == null) { - final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); - Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); - return false; - } else { - content.addView(expanded); - row.setDrawingCacheEnabled(true); - } - - applyLegacyRowBackground(sbn, content); - - entry.row = row; - entry.content = content; - entry.expanded = expanded; - - return true; - } - StatusBarNotification removeNotificationViews(IBinder key) { NotificationData.Entry entry = mNotificationData.remove(key); if (entry == null) { @@ -1520,10 +1447,6 @@ public class PhoneStatusBar extends BaseStatusBar { @Override public void setHardKeyboardStatus(boolean available, boolean enabled) { } - public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { - return new NotificationClicker(intent, pkg, tag, id); - } - private class NotificationClicker implements View.OnClickListener { private PendingIntent mIntent; private String mPkg; @@ -1538,12 +1461,6 @@ public class PhoneStatusBar extends BaseStatusBar { } public void onClick(View v) { - if (DEBUG) { - Slog.v(TAG, "NotificationClicker: intent=" + mIntent - + " pkg=" + mPkg - + " tag=" + mTag - + " id=" + mId); - } try { // The intent we are sending is for the application, which // won't have permission to immediately start an activity after @@ -1976,24 +1893,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - /** - * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. - * This was added last-minute and is inconsistent with the way the rest of the notifications - * are handled, because the notification isn't really cancelled. The lights are just - * turned off. If any other notifications happen, the lights will turn back on. Steve says - * this is what he wants. (see bug 1131461) - */ - void visibilityChanged(boolean visible) { - if (mPanelSlightlyVisible != visible) { - mPanelSlightlyVisible = visible; - try { - mBarService.onPanelRevealed(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - } - } - void performDisableActions(int net) { int old = mDisabled; int diff = net ^ old; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java index 9fd89ed..5369317 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java @@ -34,12 +34,17 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.widget.LinearLayout; +import com.android.systemui.ExpandHelper; +import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.SwipeHelper; import java.util.HashMap; -public class NotificationRowLayout extends LinearLayout implements SwipeHelper.Callback { +public class NotificationRowLayout + extends LinearLayout + implements SwipeHelper.Callback, ExpandHelper.Callback +{ private static final String TAG = "NotificationRowLayout"; private static final boolean DEBUG = false; private static final boolean SLOW_ANIMATIONS = DEBUG; @@ -55,6 +60,9 @@ public class NotificationRowLayout extends LinearLayout implements SwipeHelper.C HashMap<View, ValueAnimator> mDisappearingViews = new HashMap<View, ValueAnimator>(); private SwipeHelper mSwipeHelper; + private ExpandHelper mExpandHelper; + + private Gefingerpoken mCurrentHelper; // Flag set during notification removal animation to avoid causing too much work until // animation is done @@ -71,6 +79,8 @@ public class NotificationRowLayout extends LinearLayout implements SwipeHelper.C setOrientation(LinearLayout.VERTICAL); + setMotionEventSplittingEnabled(false); + if (DEBUG) { setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { @Override @@ -89,23 +99,61 @@ public class NotificationRowLayout extends LinearLayout implements SwipeHelper.C float densityScale = getResources().getDisplayMetrics().density; float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); + int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height); + int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height); + mExpandHelper = new ExpandHelper(mContext, this, minHeight, maxHeight); } public void setAnimateBounds(boolean anim) { mAnimateBounds = anim; } + private void logLayoutTransition() { + Log.v(TAG, "layout " + + (getLayoutTransition().isChangingLayout() ? "is " : "is not ") + + "in transition and animations " + + (getLayoutTransition().isRunning() ? "are " : "are not ") + + "running."); + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - return mSwipeHelper.onInterceptTouchEvent(ev) || - super.onInterceptTouchEvent(ev); + if (DEBUG) logLayoutTransition(); + + MotionEvent cancellation = MotionEvent.obtain(ev); + cancellation.setAction(MotionEvent.ACTION_CANCEL); + + if (mSwipeHelper.onInterceptTouchEvent(ev)) { + if (DEBUG) Log.v(TAG, "will swipe"); + mCurrentHelper = mSwipeHelper; + mExpandHelper.onInterceptTouchEvent(cancellation); + return true; + } else if (mExpandHelper.onInterceptTouchEvent(ev)) { + if (DEBUG) Log.v(TAG, "will stretch"); + mCurrentHelper = mExpandHelper; + mSwipeHelper.onInterceptTouchEvent(cancellation); + return true; + } else { + mCurrentHelper = null; + if (super.onInterceptTouchEvent(ev)) { + if (DEBUG) Log.v(TAG, "intercepting ourselves"); + mSwipeHelper.onInterceptTouchEvent(cancellation); + mExpandHelper.onInterceptTouchEvent(cancellation); + return true; + } + } + return false; } @Override public boolean onTouchEvent(MotionEvent ev) { - return mSwipeHelper.onTouchEvent(ev) || - super.onTouchEvent(ev); + if (DEBUG) Log.v(TAG, "onTouchEvent()"); + if (DEBUG) logLayoutTransition(); + if (mCurrentHelper != null) { + return mCurrentHelper.onTouchEvent(ev); + } + return super.onTouchEvent(ev); } public boolean canChildBeDismissed(View v) { @@ -130,10 +178,12 @@ public class NotificationRowLayout extends LinearLayout implements SwipeHelper.C } public View getChildAtPosition(MotionEvent ev) { + return getChildAtPosition(ev.getX(), ev.getY()); + } + public View getChildAtPosition(float touchX, float touchY) { // find the view under the pointer, accounting for GONE views final int count = getChildCount(); int y = 0; - int touchY = (int) ev.getY(); int childIdx = 0; View slidingChild; for (; childIdx < count; childIdx++) { @@ -188,6 +238,7 @@ public class NotificationRowLayout extends LinearLayout implements SwipeHelper.C @Override public void onDraw(android.graphics.Canvas c) { super.onDraw(c); + if (DEBUG) logLayoutTransition(); if (DEBUG) { //Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: " // + getMeasuredHeight() + "px"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index ba51108..8c1509b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -16,14 +16,9 @@ package com.android.systemui.statusbar.tablet; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.ArrayList; - import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.app.ActivityManagerNative; -import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; @@ -32,17 +27,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; -import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.inputmethodservice.InputMethodService; -import android.os.Build; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; @@ -53,7 +44,6 @@ import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.VelocityTracker; @@ -86,6 +76,10 @@ import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.Prefs; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + public class TabletStatusBar extends BaseStatusBar implements InputMethodsPanel.OnHardKeyboardEnabledChangeListener, RecentsPanelView.OnRecentsPanelVisibilityChangedListener { @@ -187,8 +181,6 @@ public class TabletStatusBar extends BaseStatusBar implements private CompatModePanel mCompatModePanel; private int mSystemUiVisibility = 0; - // used to notify status bar for suppressing notification LED - private boolean mPanelSlightlyVisible; private int mNavigationIconHints = 0; @@ -912,7 +904,7 @@ public class TabletStatusBar extends BaseStatusBar implements // update the contentIntent final PendingIntent contentIntent = notification.notification.contentIntent; if (contentIntent != null) { - final View.OnClickListener listener = new NotificationClicker(contentIntent, + final View.OnClickListener listener = makeClicker(contentIntent, notification.pkg, notification.tag, notification.id); oldEntry.content.setOnClickListener(listener); } else { @@ -1105,24 +1097,6 @@ public class TabletStatusBar extends BaseStatusBar implements } } - /** - * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. - * This was added last-minute and is inconsistent with the way the rest of the notifications - * are handled, because the notification isn't really cancelled. The lights are just - * turned off. If any other notifications happen, the lights will turn back on. Steve says - * this is what he wants. (see bug 1131461) - */ - void visibilityChanged(boolean visible) { - if (mPanelSlightlyVisible != visible) { - mPanelSlightlyVisible = visible; - try { - mBarService.onPanelRevealed(); - } catch (RemoteException ex) { - // Won't fail unless the world has ended. - } - } - } - @Override // CommandQueue public void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; @@ -1364,70 +1338,6 @@ public class TabletStatusBar extends BaseStatusBar implements mHandler.sendEmptyMessage(msg); } - public NotificationClicker makeClicker(PendingIntent intent, String pkg, String tag, int id) { - return new NotificationClicker(intent, pkg, tag, id); - } - - private class NotificationClicker implements View.OnClickListener { - private PendingIntent mIntent; - private String mPkg; - private String mTag; - private int mId; - - NotificationClicker(PendingIntent intent, String pkg, String tag, int id) { - mIntent = intent; - mPkg = pkg; - mTag = tag; - mId = id; - } - - public void onClick(View v) { - try { - // The intent we are sending is for the application, which - // won't have permission to immediately start an activity after - // the user switches to home. We know it is safe to do at this - // point, so make sure new activity switches are now allowed. - ActivityManagerNative.getDefault().resumeAppSwitches(); - // Also, notifications can be launched from the lock screen, - // so dismiss the lock screen when the activity starts. - ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); - } catch (RemoteException e) { - } - - if (mIntent != null) { - int[] pos = new int[2]; - v.getLocationOnScreen(pos); - Intent overlay = new Intent(); - overlay.setSourceBounds( - new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight())); - try { - mIntent.send(mContext, 0, overlay); - - } catch (PendingIntent.CanceledException e) { - // the stack trace isn't very helpful here. Just log the exception message. - Slog.w(TAG, "Sending contentIntent failed: " + e); - } - - KeyguardManager kgm = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - if (kgm != null) kgm.exitKeyguardSecurely(null); - } - - try { - mBarService.onNotificationClick(mPkg, mTag, mId); - } catch (RemoteException ex) { - // system process is dead if we're here. - } - - // close the shade if it was open - animateCollapse(); - visibilityChanged(false); - - // If this click was on the intruder alert, hide that instead -// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER); - } - } - StatusBarNotification removeNotificationViews(IBinder key) { NotificationData.Entry entry = mNotificationData.remove(key); if (entry == null) { @@ -1801,7 +1711,8 @@ public class TabletStatusBar extends BaseStatusBar implements mNotificationPanel.setNotificationCount(N); } - void workAroundBadLayerDrawableOpacity(View v) { + @Override + protected void workAroundBadLayerDrawableOpacity(View v) { Drawable bgd = v.getBackground(); if (!(bgd instanceof LayerDrawable)) return; @@ -1811,64 +1722,6 @@ public class TabletStatusBar extends BaseStatusBar implements v.setBackgroundDrawable(d); } - private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) { - StatusBarNotification sbn = entry.notification; - RemoteViews remoteViews = sbn.notification.contentView; - if (remoteViews == null) { - return false; - } - - // create the row view - LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); - View row = inflater.inflate(R.layout.status_bar_notification_row, parent, false); - workAroundBadLayerDrawableOpacity(row); - View vetoButton = updateNotificationVetoButton(row, entry.notification); - vetoButton.setContentDescription(mContext.getString( - R.string.accessibility_remove_notification)); - - // NB: the large icon is now handled entirely by the template - - // bind the click event to the content area - ViewGroup content = (ViewGroup)row.findViewById(R.id.content); - // XXX: update to allow controls within notification views - content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); -// content.setOnFocusChangeListener(mFocusChangeListener); - PendingIntent contentIntent = sbn.notification.contentIntent; - if (contentIntent != null) { - final View.OnClickListener listener = new NotificationClicker( - contentIntent, sbn.pkg, sbn.tag, sbn.id); - content.setOnClickListener(listener); - } else { - content.setOnClickListener(null); - } - - View expanded = null; - Exception exception = null; - try { - expanded = remoteViews.apply(mContext, content); - } - catch (RuntimeException e) { - exception = e; - } - if (expanded == null) { - final String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id); - Slog.e(TAG, "couldn't inflate view for notification " + ident, exception); - return false; - } else { - content.addView(expanded); - row.setDrawingCacheEnabled(true); - } - - applyLegacyRowBackground(sbn, content); - - entry.row = row; - entry.content = content; - entry.expanded = expanded; - - return true; - } - public void clearAll() { try { mBarService.onClearAllNotifications(); |