From: Jeff Sharkey Date: Mon, 5 Oct 2009 18:15:57 +0000 (-0700) Subject: Fix FastTrack recycling consistency issues, shadows. X-Git-Tag: android-x86-2.2~159^2~7^2~54^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=9a8a3d9d0b424e579ac146bec49257ea01d221d8;p=android-x86%2Fpackages-apps-Contacts.git Fix FastTrack recycling consistency issues, shadows. When FastTrack is displayed and a configuration change occurs, we tear down and recreate the host Activity, but in the process moveTaskToBack(), which means the next trigger will show a stale track and rectangle. This change dismisses the track in onPause() to reduce risk of bringing back a stale track. It also introduces a new mDismissed flag to catch any race conditions where the track is dismissed before showInternal() is called. When requested a show() and already visible, instead of ignoring, I'm calling dismissInternal() to replace the track with the updated request. I'm also using query tokens to ignore query results from stale show() requests. This change also fixes padding in the large FT format when no photo is available. It also fixes shadow behind track to remove sharp vertical edges visible during animations. Also clears "make default" checkbox between recycles and fixes an issue where icons for preferred apps remained after the user selects "always use" from intent disambig list. Fixes http://b/2163611 and http://b/2164119 --- diff --git a/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png index 6fafcbe..0dcf076 100644 Binary files a/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png and b/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png differ diff --git a/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png b/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png index efbb1da..2d20076 100644 Binary files a/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png and b/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png differ diff --git a/res/layout-finger/quickcontact.xml b/res/layout-finger/quickcontact.xml index a2a94d4..13b5c20 100644 --- a/res/layout-finger/quickcontact.xml +++ b/res/layout-finger/quickcontact.xml @@ -18,6 +18,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:paddingLeft="@dimen/quickcontact_shadow_horiz" + android:paddingRight="@dimen/quickcontact_shadow_horiz" android:background="@drawable/quickcontact_drop_shadow"> + + diff --git a/res/layout-finger/quickcontact_header_large.xml b/res/layout-finger/quickcontact_header_large.xml index df01f8a..8a11aa2 100644 --- a/res/layout-finger/quickcontact_header_large.xml +++ b/res/layout-finger/quickcontact_header_large.xml @@ -29,13 +29,13 @@ android:layout_width="50dip" android:layout_height="56dip" android:layout_marginLeft="15dip" - android:layout_marginRight="15dip" style="@*android:style/Widget.QuickContactBadge.WindowLarge" /> diff --git a/res/layout-finger/quickcontact_item.xml b/res/layout-finger/quickcontact_item.xml index 819915d..8580ac5 100644 --- a/res/layout-finger/quickcontact_item.xml +++ b/res/layout-finger/quickcontact_item.xml @@ -17,11 +17,11 @@ - 37dip + 30dip + 37dip + 20dip + 76dip - + 40dip 25dip diff --git a/src/com/android/contacts/ui/QuickContactActivity.java b/src/com/android/contacts/ui/QuickContactActivity.java index 6445664..d17e3be 100644 --- a/src/com/android/contacts/ui/QuickContactActivity.java +++ b/src/com/android/contacts/ui/QuickContactActivity.java @@ -41,13 +41,14 @@ public final class QuickContactActivity extends Activity implements @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + if (LOGV) Log.d(TAG, "onCreate"); + this.onNewIntent(getIntent()); } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); - if (LOGV) Log.d(TAG, "onNewIntent"); if (QuickContactWindow.TRACE_LAUNCH) { @@ -82,14 +83,15 @@ public final class QuickContactActivity extends Activity implements protected void onPause() { super.onPause(); if (LOGV) Log.d(TAG, "onPause"); + + // Dismiss any dialog when pausing + mQuickContact.dismiss(); } @Override protected void onDestroy() { super.onDestroy(); if (LOGV) Log.d(TAG, "onDestroy"); - - mQuickContact.dismiss(); } /** {@inheritDoc} */ @@ -97,8 +99,6 @@ public final class QuickContactActivity extends Activity implements if (LOGV) Log.d(TAG, "onDismiss"); if (isTaskRoot() && !FORCE_CREATE) { - if (LOGV) Log.d(TAG, "Moving task to back"); - // Instead of stopping, simply push this to the back of the stack. // This is only done when running at the top of the stack; // otherwise, we have been launched by someone else so need to diff --git a/src/com/android/contacts/ui/QuickContactWindow.java b/src/com/android/contacts/ui/QuickContactWindow.java index 45cb73f..ecb5f9d 100644 --- a/src/com/android/contacts/ui/QuickContactWindow.java +++ b/src/com/android/contacts/ui/QuickContactWindow.java @@ -26,15 +26,16 @@ import com.android.contacts.util.Constants; import com.android.contacts.util.DataStatus; import com.android.contacts.util.NotifyingAsyncQueryHandler; import com.android.internal.policy.PolicyManager; -import com.google.android.collect.Lists; import com.google.android.collect.Sets; +import android.app.Activity; import android.content.ActivityNotFoundException; -import android.content.ContentValues; import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.EntityIterator; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -45,7 +46,6 @@ import android.graphics.BitmapFactory; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Handler; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.QuickContact; @@ -55,14 +55,9 @@ import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Photo; -import android.provider.SocialContract.Activities; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; -import android.util.Pool; -import android.util.Poolable; -import android.util.PoolableManager; -import android.util.Pools; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; @@ -70,13 +65,12 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; -import android.view.Window.Callback; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.view.accessibility.AccessibilityEvent; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -93,7 +87,6 @@ import android.widget.TextView; import android.widget.Toast; import java.lang.ref.SoftReference; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -106,7 +99,8 @@ import java.util.Set; */ public class QuickContactWindow implements Window.Callback, NotifyingAsyncQueryHandler.AsyncQueryListener, View.OnClickListener, - AbsListView.OnItemClickListener, CompoundButton.OnCheckedChangeListener, KeyEvent.Callback { + AbsListView.OnItemClickListener, CompoundButton.OnCheckedChangeListener, KeyEvent.Callback, + OnGlobalLayoutListener { private static final String TAG = "QuickContactWindow"; /** @@ -124,6 +118,7 @@ public class QuickContactWindow implements Window.Callback, private View mDecor; private final Rect mRect = new Rect(); + private boolean mDismissed = false; private boolean mQuerying = false; private boolean mShowing = false; @@ -134,7 +129,13 @@ public class QuickContactWindow implements Window.Callback, private Uri mLookupUri; private Rect mAnchor; - private int mShadowHeight; + private int mShadowHoriz; + private int mShadowVert; + private int mShadowTouch; + + private int mScreenWidth; + private int mScreenHeight; + private int mRequestedY; private boolean mHasValidSocial = false; private boolean mHasData = false; @@ -226,7 +227,12 @@ public class QuickContactWindow implements Window.Callback, mResolveCache = new ResolveCache(mContext); final Resources res = mContext.getResources(); - mShadowHeight = res.getDimensionPixelSize(R.dimen.quickcontact_shadow); + mShadowHoriz = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_horiz); + mShadowVert = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_vert); + mShadowTouch = res.getDimensionPixelSize(R.dimen.quickcontact_shadow_touch); + + mScreenWidth = mWindowManager.getDefaultDisplay().getWidth(); + mScreenHeight = mWindowManager.getDefaultDisplay().getHeight(); mTrack = (ViewGroup)mWindow.findViewById(R.id.quickcontact); mTrackScroll = (HorizontalScrollView)mWindow.findViewById(R.id.scroll); @@ -290,10 +296,10 @@ public class QuickContactWindow implements Window.Callback, * Start showing a dialog for the given {@link Contacts#_ID} pointing * towards the given location. */ - public void show(Uri lookupUri, Rect anchor, int mode, String[] excludeMimes) { - if (mShowing || mQuerying) { - Log.w(TAG, "already in process of showing"); - return; + public synchronized void show(Uri lookupUri, Rect anchor, int mode, String[] excludeMimes) { + if (mQuerying || mShowing) { + Log.w(TAG, "dismissing before showing"); + dismissInternal(); } if (TRACE_LAUNCH && !android.os.Debug.isMethodTracingActive()) { @@ -316,7 +322,10 @@ public class QuickContactWindow implements Window.Callback, setHeaderImage(R.id.presence, null); setHeaderImage(R.id.source, null); + resetTrack(); + mHasValidSocial = false; + mDismissed = false; mQuerying = true; // Start background query for data, but only select photo rows when they @@ -327,12 +336,12 @@ public class QuickContactWindow implements Window.Callback, // Only request photo data when required by mode if (mMode == QuickContact.MODE_LARGE) { // Select photos, but only super-primary - mHandler.startQuery(TOKEN_DATA, null, dataUri, DataQuery.PROJECTION, Data.MIMETYPE + mHandler.startQuery(TOKEN_DATA, lookupUri, dataUri, DataQuery.PROJECTION, Data.MIMETYPE + "!=? OR (" + Data.MIMETYPE + "=? AND " + Data._ID + "=" + Contacts.PHOTO_ID + ")", new String[] { Photo.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE }, null); } else { // Exclude all photos from cursor - mHandler.startQuery(TOKEN_DATA, null, dataUri, DataQuery.PROJECTION, Data.MIMETYPE + mHandler.startQuery(TOKEN_DATA, lookupUri, dataUri, DataQuery.PROJECTION, Data.MIMETYPE + "!=?", new String[] { Photo.CONTENT_ITEM_TYPE }, null); } } @@ -380,41 +389,43 @@ public class QuickContactWindow implements Window.Callback, */ private void showInternal() { mDecor = mWindow.getDecorView(); + mDecor.getViewTreeObserver().addOnGlobalLayoutListener(this); WindowManager.LayoutParams l = mWindow.getAttributes(); - l.width = WindowManager.LayoutParams.FILL_PARENT; + l.width = mScreenWidth + mShadowHoriz + mShadowHoriz; l.height = WindowManager.LayoutParams.WRAP_CONTENT; // Force layout measuring pass so we have baseline numbers mDecor.measure(l.width, l.height); - final int blockHeight = mDecor.getMeasuredHeight(); l.gravity = Gravity.TOP | Gravity.LEFT; - l.x = 0; + l.x = -mShadowHoriz; if (mAnchor.top > blockHeight) { // Show downwards callout when enough room, aligning bottom block // edge with top of anchor area, and adjusting to inset arrow. showArrow(R.id.arrow_down, mAnchor.centerX()); - l.y = mAnchor.top - blockHeight + mShadowHeight; + l.y = mAnchor.top - blockHeight + mShadowVert; l.windowAnimations = R.style.QuickContactAboveAnimation; } else { // Otherwise show upwards callout, aligning block top with bottom of // anchor area, and adjusting to inset arrow. showArrow(R.id.arrow_up, mAnchor.centerX()); - l.y = mAnchor.bottom - mShadowHeight; + l.y = mAnchor.bottom - mShadowVert; l.windowAnimations = R.style.QuickContactBelowAnimation; } l.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; + | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + mRequestedY = l.y; mWindowManager.addView(mDecor, l); mShowing = true; mQuerying = false; + mDismissed = false; mTrack.startAnimation(mTrackAnim); @@ -425,35 +436,75 @@ public class QuickContactWindow implements Window.Callback, } } + /** {@inheritDoc} */ + public void onGlobalLayout() { + layoutInScreen(); + } + + /** + * Adjust vertical {@link WindowManager.LayoutParams} to fit window as best + * as possible, shifting up to display content as needed. + */ + private void layoutInScreen() { + if (!mShowing) return; + + final WindowManager.LayoutParams l = mWindow.getAttributes(); + final int originalY = l.y; + + final int blockHeight = mDecor.getHeight(); + + l.y = mRequestedY; + if (mRequestedY + blockHeight > mScreenHeight) { + // Shift up from bottom when overflowing + l.y = mScreenHeight - blockHeight; + } + + if (originalY != l.y) { + // Only update when value is changed + mWindow.setAttributes(l); + } + } + /** * Dismiss this dialog if showing. */ - public void dismiss() { + public synchronized void dismiss() { // Notify any listeners that we've been dismissed if (mDismissListener != null) { mDismissListener.onDismiss(this); } - if (!isShowing()) { - if (LOGD) Log.d(TAG, "not visible, ignore"); - return; - } + dismissInternal(); + } + private void dismissInternal() { + // Remove any attached window decor for recycling boolean hadDecor = mDecor != null; if (hadDecor) { mWindowManager.removeView(mDecor); + mWindowRecycled++; + mDecor.getViewTreeObserver().removeGlobalOnLayoutListener(this); mDecor = null; mWindow.closeAllPanels(); } + mShowing = false; + mDismissed = true; - // Release reference to last chiclet. - mLastAction = null; + // Cancel any pending queries + mHandler.cancelOperation(TOKEN_DATA); + mQuerying = false; - // Completely hide header from current mode + // Completely hide header and reset track mHeader.setVisibility(View.GONE); + resetTrack(); + } - // Cancel any pending queries - mHandler.cancelOperation(TOKEN_DATA); + /** + * Reset track to initial state, recycling any chiclets. + */ + private void resetTrack() { + // Release reference to last chiclet + mLastAction = null; // Clear track actions and scroll to hard left mResolveCache.clear(); @@ -468,32 +519,19 @@ public class QuickContactWindow implements Window.Callback, mTrackScroll.fullScroll(View.FOCUS_LEFT); mWasDownArrow = false; - setResolveVisible(false, null); - - mQuerying = false; + // Clear any primary requests + mMakePrimary = false; + mSetPrimaryCheckBox.setChecked(false); - if (!hadDecor || !mShowing) { - if (LOGD) Log.d(TAG, "not showing, ignore"); - return; - } - - mShowing = false; - mWindowRecycled++; - } - - /** - * Returns true if this dialog is showing or querying. - */ - public boolean isShowing() { - return mShowing || mQuerying; + setResolveVisible(false, null); } /** * Consider showing this window, which will only call through to * {@link #showInternal()} when all data items are present. */ - private synchronized void considerShowing() { - if (mHasData && !mShowing) { + private void considerShowing() { + if (mHasData && !mShowing && !mDismissed) { if (mMode == QuickContact.MODE_MEDIUM && !mHasValidSocial) { // Missing valid social, swap medium for small header mHeader.setVisibility(View.GONE); @@ -506,7 +544,10 @@ public class QuickContactWindow implements Window.Callback, } /** {@inheritDoc} */ - public void onQueryComplete(int token, Object cookie, Cursor cursor) { + public synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) { + // Bail early when query is stale + if (cookie != mLookupUri) return; + if (cursor == null) { // Problem while running query, so bail without showing Log.w(TAG, "Missing cursor for token=" + token); @@ -741,7 +782,7 @@ public class QuickContactWindow implements Window.Callback, } // Always launch as new task, since we're like a launcher - mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); } /** {@inheritDoc} */ @@ -826,7 +867,7 @@ public class QuickContactWindow implements Window.Callback, /** {@inheritDoc} */ public Intent getIntent() { final Intent intent = new Intent(Intent.ACTION_VIEW, mLookupUri); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); return intent; } @@ -887,7 +928,7 @@ public class QuickContactWindow implements Window.Callback, if (size == 1) { bestResolve = matches.get(0); } else if (size > 1) { - bestResolve = getBestResolve(matches); + bestResolve = getBestResolve(intent, matches); } if (bestResolve != null) { @@ -911,7 +952,18 @@ public class QuickContactWindow implements Window.Callback, * displaying in the track, and does not shortcut the system * {@link Intent} disambiguation dialog. */ - protected ResolveInfo getBestResolve(List matches) { + protected ResolveInfo getBestResolve(Intent intent, List matches) { + // Try finding preferred activity, otherwise detect disambig + final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent, + PackageManager.MATCH_DEFAULT_ONLY); + final boolean foundDisambig = (foundResolve.match & + IntentFilter.MATCH_CATEGORY_MASK) == 0; + + if (!foundDisambig) { + // Found concrete match, so return directly + return foundResolve; + } + // Accept any package from prefer list, otherwise first system app ResolveInfo firstSystem = null; for (ResolveInfo info : matches) { @@ -920,6 +972,8 @@ public class QuickContactWindow implements Window.Callback, final boolean isPrefer = QuickContactWindow.sPreferResolve .contains(info.activityInfo.applicationInfo.packageName); + + if (isPrefer) return info; if (isSystem && firstSystem != null) firstSystem = info; } @@ -1245,6 +1299,7 @@ public class QuickContactWindow implements Window.Callback, if (tag instanceof Action) { // Incoming tag is concrete intent, so try launching final Action action = (Action)tag; + final boolean makePrimary = mMakePrimary; try { mContext.startActivity(action.getIntent()); @@ -1257,7 +1312,7 @@ public class QuickContactWindow implements Window.Callback, setResolveVisible(false, actionView); this.dismiss(); - if (mMakePrimary) { + if (makePrimary) { ContentValues values = new ContentValues(1); values.put(Data.IS_SUPER_PRIMARY, 1); final Uri dataUri = action.getDataUri(); @@ -1308,7 +1363,8 @@ public class QuickContactWindow implements Window.Callback, }); // Make sure we resize to make room for ListView - onWindowAttributesChanged(mWindow.getAttributes()); + mDecor.forceLayout(); + mDecor.invalidate(); } } @@ -1322,6 +1378,8 @@ public class QuickContactWindow implements Window.Callback, // it will close the entire dialog. if (mFooterDisambig.getVisibility() == View.VISIBLE) { setResolveVisible(false, null); + mDecor.forceLayout(); + mDecor.invalidate(); } else { dismiss(); } @@ -1381,8 +1439,8 @@ public class QuickContactWindow implements Window.Callback, if (event.getAction() == MotionEvent.ACTION_DOWN) { // Only try detecting outside events on down-press mDecor.getHitRect(mRect); - mRect.top = mRect.top + mDecor.getPaddingTop(); - mRect.bottom = mRect.bottom - mDecor.getPaddingBottom(); + mRect.top = mRect.top + mShadowTouch; + mRect.bottom = mRect.bottom - mShadowTouch; final int x = (int)event.getX(); final int y = (int)event.getY(); if (!mRect.contains(x, y)) {