diff options
-rwxr-xr-x | AndroidManifest.xml | 1 | ||||
-rw-r--r-- | res/layout/screenshot.xml | 8 | ||||
-rwxr-xr-x | res/values/strings.xml | 2 | ||||
-rwxr-xr-x | src/com/android/nfc/NdefP2pManager.java | 94 | ||||
-rw-r--r-- | src/com/android/nfc/ScreenshotWindowAnimator.java | 52 |
5 files changed, 113 insertions, 44 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fa21e41..410fced 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -18,6 +18,7 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + <uses-permission android:name="android.permission.VIBRATE" /> <permission android:name="com.android.nfc.permission.NFCEE_ADMIN" android:label="@string/permlab_nfcAdmin" diff --git a/res/layout/screenshot.xml b/res/layout/screenshot.xml index dc34ba3..2a6e242 100644 --- a/res/layout/screenshot.xml +++ b/res/layout/screenshot.xml @@ -18,6 +18,14 @@ android:layout_height="match_parent" android:background="#FF000000" > + <TextView android:id="@+id/touch" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="@string/touch" + android:paddingTop="20dip" + android:textSize="50px" + android:gravity="center" + /> <ImageView android:id="@+id/clone" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/res/values/strings.xml b/res/values/strings.xml index 248669c..4d71b99 100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -29,5 +29,5 @@ <!-- Content description of the NFC enabled notification icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_nfc_enabled">NFC enabled.</string> - + <string name="touch">Tap to share!</string> </resources> diff --git a/src/com/android/nfc/NdefP2pManager.java b/src/com/android/nfc/NdefP2pManager.java index 888dbe2..18cf3fe 100755 --- a/src/com/android/nfc/NdefP2pManager.java +++ b/src/com/android/nfc/NdefP2pManager.java @@ -37,16 +37,19 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.os.Vibrator; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Profile; import android.util.Log; +import android.view.OrientationEventListener; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; -public class NdefP2pManager implements Handler.Callback { +public class NdefP2pManager implements Handler.Callback, + ScreenshotWindowAnimator.Callback { // TODO dynamically assign SAP values static final int NDEFPUSH_SAP = 0x10; @@ -59,6 +62,7 @@ public class NdefP2pManager implements Handler.Callback { static final int MSG_RECEIVE_SUCCESS = 0; static final int MSG_TIMEOUT = 1; + static final long[] mVibPattern = {0, 100, 100, 150, 100, 250, 100, 10000}; static final String TAG = "P2PManager"; static final boolean DBG = true; @@ -70,33 +74,52 @@ public class NdefP2pManager implements Handler.Callback { final Context mContext; final P2pStatusListener mListener; + final Vibrator mVibrator; + // Used only from the UI thread PushTask mPushTask; Handler mHandler; int mSendState; int mReceiveState; boolean mAnimating; + boolean mSendStarted; final static Uri mProfileUri = Profile.CONTENT_VCARD_URI.buildUpon(). appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true"). build(); + NdefMessage mPushMsg; + /** Locked on NdefP2pManager.class */ NdefMessage mForegroundMsg; INdefPushCallback mCallback; + OrientationEventListener mOrientationListener; + public NdefP2pManager(Context context, P2pStatusListener listener) { mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); mDefaultSnepServer = new SnepServer(mDefaultSnepCallback); mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mPackageManager = context.getPackageManager(); mContext = context; - mScreenshot = new ScreenshotWindowAnimator(context); + mScreenshot = new ScreenshotWindowAnimator(context, this); mListener = listener; mAnimating = false; mHandler = new Handler(this); mSendState = STATE_WAITING; mReceiveState = STATE_WAITING; + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + mSendStarted = false; + + mOrientationListener = new OrientationEventListener(context) { + @Override + public void onOrientationChanged(int orientation) { + if (mAnimating && (orientation > 50 && orientation < 310)) { + onConfirmSend(); + } + } + }; + } public void enableNdefServer() { @@ -156,8 +179,11 @@ public class NdefP2pManager implements Handler.Callback { void llcpActivated() { if (DBG) Log.d(TAG, "LLCP connection up and running"); + mOrientationListener.enable(); + mVibrator.vibrate(mVibPattern, 6); mSendState = STATE_WAITING; mReceiveState = STATE_WAITING; + mHandler.removeMessages(MSG_TIMEOUT); NdefMessage foregroundMsg; synchronized (this) { @@ -197,29 +223,30 @@ public class NdefP2pManager implements Handler.Callback { } } - if (mPushTask != null) { - mPushTask.cancel(true); - } - mPushTask = new PushTask(foregroundMsg); - - // Start rendering the animation if we have something to send, - // otherwise just sound. - if (foregroundMsg != null) { + // If an animation is still running, we don't restart it to avoid + // nasty effects if at the edge of RF field. + if (foregroundMsg != null && !mAnimating) { + mScreenshot.stop(); mScreenshot.start(); mAnimating = true; + mSendStarted = false; + mListener.onP2pBegin(); } - mListener.onP2pBegin(); - mPushTask.execute(); + + mPushMsg = foregroundMsg; } void llcpDeactivated() { if (DBG) Log.d(TAG, "LLCP deactivated."); + mOrientationListener.disable(); + mVibrator.cancel(); if (mPushTask != null) { mPushTask.cancel(true); mPushTask = null; } - - finish(mSendState == STATE_SUCCESS, mReceiveState == STATE_SUCCESS); + // Don't cancel the animation immediately - retain it + // up to one second. + mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, 1000); } /** @@ -229,11 +256,7 @@ public class NdefP2pManager implements Handler.Callback { */ void onSendComplete(boolean success) { mSendState = success ? STATE_SUCCESS : STATE_FAILURE; - if (mReceiveState != STATE_WAITING) { - finish(success, mReceiveState == STATE_SUCCESS); - } else { - mHandler.sendEmptyMessageDelayed(MSG_TIMEOUT, RECEIVE_TIMEOUT_MS); - } + finish(success, mReceiveState == STATE_SUCCESS); } /** @@ -346,24 +369,18 @@ public class NdefP2pManager implements Handler.Callback { * Must be called on the UI thread. */ private void finish(boolean sendSuccess, boolean receiveSuccess) { - if (sendSuccess && receiveSuccess) { + + if (mSendStarted && sendSuccess) { if (mAnimating) { mScreenshot.finishWithSendReceive(); } mListener.onP2pEnd(); - } else if (sendSuccess) { - if (mAnimating) { - mScreenshot.finishWithSend(); - } - mListener.onP2pEnd(); - } else if (receiveSuccess) { - // TODO tricky case - sending failed, but we did receive something - // see if we can come up with a different animation for that. + } + else if (mSendStarted && !sendSuccess) { if (mAnimating) { mScreenshot.finishWithFailure(); } - mListener.onP2pEnd(); - + mListener.onP2pError(); } else { if (mAnimating) { mScreenshot.finishWithFailure(); @@ -374,6 +391,7 @@ public class NdefP2pManager implements Handler.Callback { mHandler.removeMessages(MSG_TIMEOUT); mHandler.removeMessages(MSG_RECEIVE_SUCCESS); + mVibrator.cancel(); mAnimating = false; } @@ -382,9 +400,6 @@ public class NdefP2pManager implements Handler.Callback { switch (msg.what) { case MSG_RECEIVE_SUCCESS: mReceiveState = STATE_SUCCESS; - if (mSendState != STATE_WAITING) { - finish(mSendState == STATE_SUCCESS, true); - } // else will wait for send to complete break; case MSG_TIMEOUT: finish(mSendState == STATE_SUCCESS, mReceiveState == STATE_SUCCESS); @@ -392,4 +407,17 @@ public class NdefP2pManager implements Handler.Callback { } return true; } + + @Override + public void onConfirmSend() { + if (!mSendStarted) { + // Start sending + if (mPushTask != null) { + mPushTask.cancel(true); + } + mSendStarted = true; + mPushTask = new PushTask(mPushMsg); + mPushTask.execute(); + } + } } diff --git a/src/com/android/nfc/ScreenshotWindowAnimator.java b/src/com/android/nfc/ScreenshotWindowAnimator.java index b8b123e..d61aa0a 100644 --- a/src/com/android/nfc/ScreenshotWindowAnimator.java +++ b/src/com/android/nfc/ScreenshotWindowAnimator.java @@ -36,6 +36,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; @@ -53,7 +54,8 @@ import java.util.List; * - what do we say in the Toast? Which icon do we get if the user uses another * type of gallery? */ -public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdateListener { +public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdateListener, + View.OnTouchListener { private static final String TAG = "ScreenshotWindowAnimator"; private static final float INITIAL_SCREENSHOT_SCALE = 0.7f; @@ -125,7 +127,15 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat boolean mStartAnimDone = false; boolean mEndRequested = false; - Handler mHandler; + final Handler mHandler; + final Callback mCallback; + + /* Interface to be used whenever the user confirms + * the send action. + */ + interface Callback { + public void onConfirmSend(); + } class StartAnimationListener extends AnimatorListenerAdapter { @Override @@ -212,9 +222,12 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat // Combine them into a set mFailureAnimatorSet = new AnimatorSet(); animList.clear(); + + /* animList.add(mCenterToLeftAnimator); animList.add(mLeftToRightAnimator); animList.add(mRightToCenterAnimator); + */ animList.add(mFailureAnimator); mFailureAnimatorSet.playSequentially(animList); mFailureAnimatorSet.addListener(mEndListener); @@ -232,8 +245,9 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat /** * @param context everything needs a context :( */ - public ScreenshotWindowAnimator(Context context) { + public ScreenshotWindowAnimator(Context context, Callback callback) { mContext = context; + mCallback = callback; mHandler = new Handler(this); mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -257,10 +271,12 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat */ // Setup the window that we are going to use + // TODO Figure out how to do OnTouch using TYPE_SYSTEM_OVERLAY + // and re-add TYPE_SYSTEM_OVERLAY to layout params below. mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, - WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + 0, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM @@ -271,6 +287,12 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat mWindowLayoutParams.setTitle("ScreenshotAnimation"); mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); mDisplay = mWindowManager.getDefaultDisplay(); + + mScreenshotView.setOnTouchListener(this); + mClonedView.setOnTouchListener(this); + + + } /** @@ -297,6 +319,10 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat // We need to orient the screenshot correctly (and the Surface api seems to // take screenshots // only in the natural orientation of the device :!) + + // Make sure any existing animations are ended + endAnimations(); + mDisplay.getRealMetrics(mDisplayMetrics); float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; float degrees = getDegreesForRotation(mDisplay.getRotation()); @@ -342,9 +368,6 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat mScreenshotWidth = mScreenBitmap.getWidth(); - // Make sure any existing animations are ended - endAnimations(); - // At this point no anims are running, no need to sync these mResult = RESULT_WAITING; mWaitingForResult = true; @@ -373,6 +396,11 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat if (mFailureAnimatorSet != null) { mFailureAnimatorSet.end(); } + + if (mAttached) { + mWindowManager.removeView(mScreenshotLayout); + mAttached = false; + } } private void postResult(int result) { @@ -413,7 +441,7 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat * Must be called from the UI thread. */ public void stop() { - mHandler.sendEmptyMessage(MSG_STOP_ANIMATIONS); + endAnimations(); } private void onStartAnimationUpdate(ValueAnimator animation) { @@ -443,8 +471,6 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat float scaleT = INITIAL_SCREENSHOT_SCALE - (scale * (INITIAL_SCREENSHOT_SCALE - FINAL_SCREENSHOT_SCALE)); - float cloneAlpha = mAlphaDownInterpolator.getInterpolation(t) * 0.4f; - mClonedView.setScaleX(scaleT); mClonedView.setScaleY(scaleT); } @@ -553,4 +579,10 @@ public class ScreenshotWindowAnimator implements Handler.Callback, AnimatorUpdat } return true; } + + @Override + public boolean onTouch(View v, MotionEvent event) { + mCallback.onConfirmSend(); + return true; + } } |