OSDN Git Service

Fix FastTrack recycling consistency issues, shadows.
authorJeff Sharkey <jsharkey@android.com>
Mon, 5 Oct 2009 18:15:57 +0000 (11:15 -0700)
committerJeff Sharkey <jsharkey@android.com>
Tue, 6 Oct 2009 22:38:25 +0000 (15:38 -0700)
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

res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png
res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png
res/layout-finger/quickcontact.xml
res/layout-finger/quickcontact_header_large.xml
res/layout-finger/quickcontact_item.xml
res/values/dimens.xml
src/com/android/contacts/ui/QuickContactActivity.java
src/com/android/contacts/ui/QuickContactWindow.java

index 6fafcbe..0dcf076 100644 (file)
Binary files a/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png and b/res/drawable-hdpi-finger/quickcontact_drop_shadow.9.png differ
index efbb1da..2d20076 100644 (file)
Binary files a/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png and b/res/drawable-mdpi-finger/quickcontact_drop_shadow.9.png differ
index a2a94d4..13b5c20 100644 (file)
@@ -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">
 
     <FrameLayout
         android:layout_below="@id/footer"
         android:src="@drawable/quickcontact_arrow_down" />
 
+    <ImageView
+        android:id="@+id/arrow_down_stub"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="-1dip"
+        android:layout_below="@id/footer_disambig"
+        android:visibility="invisible"
+        android:src="@drawable/quickcontact_arrow_down" />
+
 </RelativeLayout>
index df01f8a..8a11aa2 100644 (file)
         android:layout_width="50dip"
         android:layout_height="56dip"
         android:layout_marginLeft="15dip"
-        android:layout_marginRight="15dip"
         style="@*android:style/Widget.QuickContactBadge.WindowLarge" />
 
     <LinearLayout
         android:layout_width="0dip"
         android:layout_height="wrap_content"
         android:layout_weight="1"
+        android:layout_marginLeft="15dip"
         android:paddingRight="8dip"
         android:orientation="vertical">
 
index 819915d..8580ac5 100644 (file)
 <com.android.contacts.ui.widget.CheckableImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="59dip"
-    android:layout_height="52dip"
+    android:layout_height="51dip"
     android:paddingLeft="12dip"
     android:paddingRight="12dip"
     android:paddingTop="8dip"
-    android:paddingBottom="9dip"
+    android:paddingBottom="8dip"
     android:scaleType="centerInside"
     android:focusable="true"
     android:clickable="true"
index 845c3dc..a82f5a9 100644 (file)
 -->
 
 <resources>
-    <dimen name="quickcontact_shadow">37dip</dimen>
+    <dimen name="quickcontact_shadow_horiz">30dip</dimen>
+    <dimen name="quickcontact_shadow_vert">37dip</dimen>
+    <dimen name="quickcontact_shadow_touch">20dip</dimen>
+
     <dimen name="edit_photo_size">76dip</dimen>
-        
+
     <!-- The height of the ScrollingTabWidget -->
     <dimen name="tab_height">40dip</dimen>
     <dimen name="account_name_height">25dip</dimen>
index 6445664..d17e3be 100644 (file)
@@ -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
index 45cb73f..ecb5f9d 100644 (file)
@@ -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<ResolveInfo> matches) {
+        protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> 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)) {