Adopted SocialContract and first pass at fast-track.
Showing live data from SocialProvider through recently added
SocialContract constants, symlinked for now since the contract
isn't in the framework yet.
Added first pass at fast-track using edge-based triggering
from the social list. Wraps the ListView in a EdgeTriggerView
that watches for "pull" actions from a specific edge. Also adds
concept of a FloatyListView to keep a "floaty" window anchored
with respect to ListView scrolling.
The fast-track window summarizes contact methods based on
anyone system-wide who offers an icon for the mime-types. For
example, the testing app pushes app-specific contact methods
into the Data table, and then provides icons through its
RemoteViewsMapping XML resource.
Changed SHOW_OR_CREATE to accept Aggregate Uris and now shows
fast-track in cases where a single matching aggregate is found.
Abstracted AsyncQueryHandler to a QueryCompletedListener callback
interface to clean up code that uses it while still protecting
against leaked Contexts.
diff --git a/src/com/android/contacts/EdgeTriggerView.java b/src/com/android/contacts/EdgeTriggerView.java
new file mode 100644
index 0000000..d40dbad
--- /dev/null
+++ b/src/com/android/contacts/EdgeTriggerView.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2009 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.contacts;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
+
+/**
+ * Lightweight view that wraps around an existing view, watching and capturing
+ * sliding gestures from the left or right edges within a given tolerance.
+ */
+public class EdgeTriggerView extends FrameLayout {
+ public static final int FLAG_NONE = 0x00;
+ public static final int FLAG_LEFT = 0x01;
+ public static final int FLAG_RIGHT = 0x02;
+
+ private int mTouchSlop;
+
+ private int mEdgeWidth;
+ private int mListenEdges;
+
+ private boolean mListenLeft = false;
+ private boolean mListenRight = false;
+
+ private MotionEvent mDownStart;
+ private int mEdge = FLAG_NONE;
+
+ public static interface EdgeTriggerListener {
+ public void onTrigger(float downX, float downY, int edge);
+ }
+
+ private EdgeTriggerListener mListener;
+
+ /**
+ * Add a {@link EdgeTriggerListener} to watch for edge events.
+ */
+ public void setOnEdgeTriggerListener(EdgeTriggerListener listener) {
+ mListener = listener;
+ }
+
+ public EdgeTriggerView(Context context) {
+ this(context, null);
+ }
+
+ public EdgeTriggerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public EdgeTriggerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ setClickable(true);
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ // TODO: enable reading these values again once we move away from symlinks
+// TypedArray a = context.obtainStyledAttributes(attrs,
+// R.styleable.EdgeTriggerView, defStyle, -1);
+// mEdgeWidth = a.getDimensionPixelSize(R.styleable.EdgeTriggerView_edgeWidth, mTouchSlop);
+// mListenEdges = a.getInt(R.styleable.EdgeTriggerView_listenEdges, FLAG_LEFT);
+
+ mEdgeWidth = 80;
+ mListenEdges = FLAG_LEFT;
+
+ mListenLeft = (mListenEdges & FLAG_LEFT) == FLAG_LEFT;
+ mListenRight = (mListenEdges & FLAG_RIGHT) == FLAG_RIGHT;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ // Consider watching this touch event based on listening flags
+ final float x = event.getX();
+ if (mListenLeft && x < mEdgeWidth) {
+ mEdge = FLAG_LEFT;
+ } else if (mListenRight && x > getWidth() - mEdgeWidth) {
+ mEdge = FLAG_RIGHT;
+ } else {
+ mEdge = FLAG_NONE;
+ }
+
+ if (mEdge != FLAG_NONE) {
+ mDownStart = MotionEvent.obtain(event);
+ } else {
+ mDownStart = null;
+ }
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ if (mEdge != FLAG_NONE) {
+ // If moved far enough, capture touch event for ourselves
+ float delta = event.getX() - mDownStart.getX();
+ if (mEdge == FLAG_LEFT && delta > mTouchSlop) {
+ return true;
+ } else if (mEdge == FLAG_RIGHT && delta < -mTouchSlop) {
+ return true;
+ }
+ }
+ break;
+ }
+ }
+
+ // Otherwise let the event slip through to children
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ // Pass trigger event to listener and return true to consume
+ if (mEdge != FLAG_NONE && mListener != null) {
+ mListener.onTrigger(mDownStart.getX(), mDownStart.getY(), mEdge);
+
+ // Reset values so we don't sent twice
+ mEdge = FLAG_NONE;
+ mDownStart = null;
+ }
+ return true;
+ }
+}