From 524f568802c32ba1126fe2886f3e96d018d7f23d Mon Sep 17 00:00:00 2001 From: Daniel Sandler Date: Wed, 13 Oct 2010 12:13:33 -0400 Subject: [PATCH] New system bar "shirt pocket", for quickly stashing draggable bits. Anything that can be dragged using the new drag manager APIs (View#startDrag) can be dropped here; the pocket will hold it for you until you're ready to drag it out again. Known issues: - currently discarding the clipping whenever you drag it out; in the future we will only discard it once you actually drag it somewhere - there's a bug where dragging things back out of the pocket's window requires an extra tap (i.e. tap once then tap and drag) - almost nothing uses the new drag manager yet :) Change-Id: Iff3103e4117f31023e1e96667632d77912e21d30 --- .../res/drawable-mdpi/ic_sysbar_pocket.png | Bin 0 -> 509 bytes .../drawable-mdpi/ic_sysbar_pocket_drag.png | Bin 0 -> 627 bytes .../drawable-mdpi/ic_sysbar_pocket_hidden.png | Bin 0 -> 696 bytes .../ic_sysbar_pocket_holding.png | Bin 0 -> 606 bytes .../SystemUI/res/layout-xlarge/status_bar.xml | 21 ++ .../res/layout-xlarge/sysbar_panel_pocket.xml | 61 +++++ .../statusbar/tablet/ShirtPocket.java | 248 ++++++++++++++++++ 7 files changed, 330 insertions(+) create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket.png create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket_drag.png create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket_hidden.png create mode 100644 packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket_holding.png create mode 100644 packages/SystemUI/res/layout-xlarge/sysbar_panel_pocket.xml create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/tablet/ShirtPocket.java diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket.png new file mode 100644 index 0000000000000000000000000000000000000000..90dffa973c2dc9ae8b90d6a6146abd9a0edcdeab GIT binary patch literal 509 zcmVFdrP+=X(GP-y3@Z*6fK#f0aUO$dIB=J4~qMTm`f z3aGKisE;5{8Mu%w;TdCI2)i@HCv^&)NPXb@j**U-n0)HT=mSV@@kUO;{^;WCm$(PK zxry8ZB0vO)01+SpLZ#C_ZdT=zP7f7eice)vb~%O(gc5$cya z&aJ`Rb?6wVka0F1UAe)W)zAmWBy&f6ngvaTu3KW%mLDbh%-9k;U$MD^XQM~J>|Ej~ zU{q1pS2|t0EH3we2$+B^Ms^f`D1m>4PswiYrFRdO3uNTHqB!r3|_#}K17KP0M8`sB2AobU6U z?d!Epk|d^fEFZxp#XyIsw@E%iE~_ITZD1a56YyJCcs4qA@}1^AqU0H zXe@~b+lJ-d?m~aNsuK~fHy79Oyhqkir&$6x`42|Fugem!cOkS^4xI%oKKeEpk`SO9 z4;vXm(i6J*!wESB;4Z|J@v8|TP96xv08bafLnbS16yh=5=izlF9!PDYoG{F;cT8EP z5EF5JFZT|-xAQiddl7R22q}aCXfz#=<*YCIob+d;Gnk4K7l>0yyjHuA6mrzs2xdSy zM$*EfS>NNcoaLR`Y@?r>mRK9h=|Ffy6_+R0D?bM$Y5Rr73a|pK04u->sFQ$HI_(#5 zvidpG>1_qb@&`xA6Sq7fFgxXAhRV2Xn=u^IPfQur=^@sIe83g9U9CjYHIz^EzMT9c zwoiZ`Gh(Vv?~)4z+Ut;{3tWt37rfXneHRM(po(NJRvz?@Ga6{?BrvQYy{{Ay;MkR9 zqcOT#&c3UWat#9XzB?6li%O?gc^+=h0V|*e=Gea)SOHc*v+YZO0RZ|d+ykdJqAUOa N002ovPDHLkV1oNf1f~E0 literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket_hidden.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_pocket_hidden.png new file mode 100644 index 0000000000000000000000000000000000000000..93b0d238fb0ce00df104210fe722317a1da1b55d GIT binary patch literal 696 zcmV;p0!RIcP)mGx`edX(cIHehlAp?wHG%Up#=s41A&3SKw#jC8EBTlKAX*=!C(-@aoj3nZ`&kEQmohO zY%-Z-_S$!{cL4MG{HELO-b#xOheP|j4P4fn&1RmaX|~(#((!n_b+J($F9u;A@Hf(` z0uK;;;S=BNq2OG`j*F7&P&QeX!MsjYI+E31uSWpRrQ{U=HNV+xPG!^SbRMgSV87r0 z#SfYAJy%{OR0Y_oeRyTSXLDimpY#`Oed;+i-5iZ2!YfUz|SfnUO2*J z&S8UD4qvO)`a?%Ho9I1g3CVbGuIcZhD4yslAB2#1zzO)7YCpKGzb|J=8k~hK_edn;*t8{ys%!jRTh8(kSxTo3}7Gn zqpDUiRvv(&5}@y;rE*>4)xQTq0CkmytKX0TSElOH3mK@3Sy#V5D+79+R(A9frgE9l z^dT=l@9VTz2Ke~I_ZtPu<|Y80cO7kee6xqEJ$l|3lf`0jD|WHmbt=7v$mMS@ev{E? zlmg7Pd1U~ntJMmIlH-Q1`+TXV;E%q))U_8+-+1hW&*bHDSqz86M!(TwcaqKjfS*pMWWlR>myqfG+(%vx`e*;D>^0=FA9YO};dfvlFc26B e30000-ER$ zikE#}g(9`@?1MVoyTwbPy`Cr;yPT?h0LcsO-9Su)@J1#36gTr$J zz3eMw331F1C1V=cFYGcJeH8)%LN|>CA;re2*m$ari1!a}w3o}a2k`_gqa*}~cSnZD zgxk~UR%}!!QA_qdu=Z6DdIzW0DMn0i5PUD$S0kA1~HuL~k2!71p zkbOFB(LlWz$Xh`>hAHOy^xLIq%pWm2OI}JGk7rHty5+JRdo|i8pyzTC1%x`(l~qpL sQ3mIIfCVI=@pobtzyeY(;75P~01;&5uPN_=cmMzZ07*qoM6N<$f*;-vo&W#< literal 0 HcmV?d00001 diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index 295c79b72a5d9..28e47b0b82b71 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -136,6 +136,27 @@ android:layout_alignParentRight="true" android:orientation="horizontal" > + + + + + + + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/ShirtPocket.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/ShirtPocket.java new file mode 100644 index 0000000000000..658038cad5a97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/ShirtPocket.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2010 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.tablet; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.Slog; +import android.view.View; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.TextView; +import android.view.DragEvent; +import android.view.MotionEvent; +import android.content.ClipData; +import android.content.ClipDescription; +import android.graphics.Paint; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Point; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.view.WindowManagerImpl; +import android.graphics.PixelFormat; +import android.view.Gravity; + +import com.android.systemui.R; + +public class ShirtPocket extends FrameLayout { + private static final boolean DEBUG = false; + private static final String TAG = "StatusBar/ShirtPocket"; + + private ClipData mClipping = null; + + private View mWindow = null; + private ImageView mIcon; + private ImageView mPreviewIcon; + private TextView mDescription; + private TextView mAltText; + + public ShirtPocket(Context context, AttributeSet attrs) { + super(context, attrs); + setupWindow(); + } + + // TODO: "pin area" panel, dragging things out + ObjectAnimator mAnimHide, mAnimShow; + + protected void onAttachedToWindow() { + // Drag API notes: we must be visible to receive drag events + setVisibility(View.VISIBLE); + + mIcon = (ImageView) findViewById(R.id.pocket_icon); + refreshStatusIcon(); + + setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (mClipping != null) { + if (mWindow.getVisibility() == View.VISIBLE) hideWindow(); + else showWindow(); + } + } + }); + } + + private void refreshStatusIcon() { + setClickable(mClipping != null); + mIcon.setImageResource(mClipping == null + ? R.drawable.ic_sysbar_pocket_hidden + : R.drawable.ic_sysbar_pocket_holding); + mIcon.setVisibility(mClipping == null ? View.INVISIBLE : View.VISIBLE); + } + + private void showWindow() { + getHandler().post(new Runnable() { + public void run() { + mWindow.setVisibility(View.VISIBLE); + refreshStatusIcon(); + } + }); + } + + private void hideWindow() { + getHandler().post(new Runnable() { + public void run() { + mWindow.setVisibility(View.GONE); + refreshStatusIcon(); + } + }); + } + + private void hideWindowInJustASec() { + getHandler().postDelayed(new Runnable() { + public void run() { + mWindow.setVisibility(View.GONE); + refreshStatusIcon(); + } + }, + 250); + } + + private void stash(ClipData clipping) { + mClipping = clipping; + if (mClipping != null) { + Bitmap icon = mClipping.getIcon(); + mDescription.setText(mClipping.getLabel()); + if (icon != null) { + mPreviewIcon.setImageBitmap(icon); + mPreviewIcon.setVisibility(View.VISIBLE); + mAltText.setVisibility(View.GONE); + } else { + mPreviewIcon.setVisibility(View.GONE); + mAltText.setVisibility(View.VISIBLE); + if (mClipping.getItemCount() > 0) { + // TODO: figure out how to visualize every kind of ClipData! + mAltText.setText(mClipping.getItem(0).coerceToText(getContext())); + } + } + } + } + + private boolean isInViewContentArea(View v, int x, int y) { + final int l = v.getPaddingLeft(); + final int r = v.getWidth() - v.getPaddingRight(); + final int t = v.getPaddingTop(); + final int b = v.getHeight() - v.getPaddingBottom(); + return x >= l && x < r && y >= t && y < b; + } + + View.OnTouchListener mWindowTouchListener = new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent ev) { + final int action = ev.getAction(); + if (action == MotionEvent.ACTION_OUTSIDE + || (action == MotionEvent.ACTION_DOWN + && !isInViewContentArea(mWindow, (int)ev.getX(), (int)ev.getY()))) { + hideWindow(); + return true; + } else if (action == MotionEvent.ACTION_DOWN) { + Slog.d(TAG, "ACTION_DOWN"); + final ClipData clip = mClipping; + if (clip != null) { + final Bitmap icon = clip.getIcon(); + DragThumbnailBuilder thumb; + if (icon != null) { + thumb = new DragThumbnailBuilder(v) { + public void onProvideThumbnailMetrics(Point thumbnailSize, Point thumbnailTouchPoint) { + thumbnailSize.set(icon.getWidth(), icon.getHeight()); + thumbnailTouchPoint.set(thumbnailSize.x / 2, thumbnailSize.y / 2); + } + public void onDrawThumbnail(Canvas canvas) { + canvas.drawBitmap(icon, 0, 0, new Paint()); + } + }; + } else { + // uhhh, what now? + thumb = new DragThumbnailBuilder(mWindow.findViewById(R.id.preview)); + } + + ShirtPocket.this.startDrag(clip, thumb, false); + + // TODO: only discard the clipping if it was accepted + stash(null); + + hideWindowInJustASec(); // will refresh the icon + + return true; + } + } + return false; + } + }; + + private void setupWindow() { + mWindow = View.inflate(getContext(), R.layout.sysbar_panel_pocket, null); + + mPreviewIcon = (ImageView) mWindow.findViewById(R.id.icon); + mDescription = (TextView) mWindow.findViewById(R.id.description); + mAltText = (TextView) mWindow.findViewById(R.id.alt); + + mWindow.setVisibility(View.GONE); + mWindow.setOnTouchListener(mWindowTouchListener); + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + 400, + 250, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + PixelFormat.TRANSLUCENT); + lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; +// int pos[] = new int[2]; +// getLocationOnScreen(pos); +// lp.x = pos[1]; +// lp.y = 0; + lp.setTitle("ShirtPocket"); + lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard; + + WindowManagerImpl.getDefault().addView(mWindow, lp); + + } + + public boolean onDragEvent(DragEvent event) { + if (DEBUG) Slog.d(TAG, "onDragEvent: " + event); + if (mIcon != null) { + switch (event.getAction()) { + // We want to appear whenever a potential drag takes off from anywhere in the UI. + case DragEvent.ACTION_DRAG_STARTED: + mIcon.setImageResource(mClipping == null + ? R.drawable.ic_sysbar_pocket + : R.drawable.ic_sysbar_pocket_holding); + mIcon.setVisibility(View.VISIBLE); + break; + case DragEvent.ACTION_DRAG_ENTERED: + if (DEBUG) Slog.d(TAG, "entered!"); + mIcon.setImageResource(R.drawable.ic_sysbar_pocket_drag); + break; + case DragEvent.ACTION_DRAG_EXITED: + if (DEBUG) Slog.d(TAG, "exited!"); + mIcon.setImageResource(mClipping == null + ? R.drawable.ic_sysbar_pocket + : R.drawable.ic_sysbar_pocket_holding); + break; + case DragEvent.ACTION_DROP: + if (DEBUG) Slog.d(TAG, "dropped!"); + stash(event.getClipData()); + refreshStatusIcon(); + break; + case DragEvent.ACTION_DRAG_ENDED: + refreshStatusIcon(); + break; + } + } + return true; // we want everything, thank you + } +} +