diff options
-rw-r--r-- | res/drawable-hdpi/howto_sharetap.png | bin | 0 -> 7792 bytes | |||
-rw-r--r-- | res/layout/holding_it_wrong.xml | 32 | ||||
-rw-r--r-- | res/layout/screenshot.xml | 20 | ||||
-rw-r--r-- | res/raw/start.ogg | bin | 18515 -> 23068 bytes | |||
-rw-r--r-- | src/com/android/nfc/DeviceHost.java | 4 | ||||
-rw-r--r-- | src/com/android/nfc/HoldingItWrongUi.java | 62 | ||||
-rw-r--r-- | src/com/android/nfc/P2pAnimationActivity.java | 525 | ||||
-rw-r--r-- | src/com/android/nfc/P2pEventManager.java | 169 | ||||
-rw-r--r-- | src/com/android/nfc/SendUi.java | 232 |
9 files changed, 397 insertions, 647 deletions
diff --git a/res/drawable-hdpi/howto_sharetap.png b/res/drawable-hdpi/howto_sharetap.png Binary files differnew file mode 100644 index 0000000..b78329d --- /dev/null +++ b/res/drawable-hdpi/howto_sharetap.png diff --git a/res/layout/holding_it_wrong.xml b/res/layout/holding_it_wrong.xml new file mode 100644 index 0000000..4cd86ad --- /dev/null +++ b/res/layout/holding_it_wrong.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/padding" + android:orientation="vertical" + android:gravity="center" +> + + <ImageView android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingBottom="15px" + android:src="@drawable/howto_sharetap" + android:paddingTop="10px" + /> + +</LinearLayout> + + diff --git a/res/layout/screenshot.xml b/res/layout/screenshot.xml index 9f27b7f..e29aff4 100644 --- a/res/layout/screenshot.xml +++ b/res/layout/screenshot.xml @@ -24,29 +24,9 @@ android:adjustViewBounds="true" android:src="@drawable/p2p_bg" /> - <RelativeLayout android:layout_width="match_parent" - android:layout_height="match_parent" - > - <TextView android:id="@+id/calltoaction" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:text="@string/touch" - android:textSize="35px" - android:layout_alignParentTop="true" - android:paddingTop="30dip" - android:gravity="center" - /> - </RelativeLayout> - - <ImageView android:id="@+id/clone" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:adjustViewBounds="true" - /> <ImageView android:id="@+id/screenshot" android:layout_width="wrap_content" android:layout_height="wrap_content" android:adjustViewBounds="true" /> - </FrameLayout> diff --git a/res/raw/start.ogg b/res/raw/start.ogg Binary files differindex baa38ba..e08e155 100644 --- a/res/raw/start.ogg +++ b/res/raw/start.ogg diff --git a/src/com/android/nfc/DeviceHost.java b/src/com/android/nfc/DeviceHost.java index d1e7064..d8192b7 100644 --- a/src/com/android/nfc/DeviceHost.java +++ b/src/com/android/nfc/DeviceHost.java @@ -16,10 +16,6 @@ package com.android.nfc; -import com.android.nfc.nxp.NativeLlcpConnectionlessSocket; -import com.android.nfc.nxp.NativeLlcpServiceSocket; -import com.android.nfc.nxp.NativeLlcpSocket; - import android.nfc.NdefMessage; import android.os.Bundle; diff --git a/src/com/android/nfc/HoldingItWrongUi.java b/src/com/android/nfc/HoldingItWrongUi.java new file mode 100644 index 0000000..e915662 --- /dev/null +++ b/src/com/android/nfc/HoldingItWrongUi.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.view.View; +import android.view.WindowManager; + +import com.android.nfc3.R; + +public class HoldingItWrongUi implements DialogInterface.OnDismissListener { + + AlertDialog mDialog; + + /** Must call from UI thread */ + public void show(Context context) { + if (mDialog != null) { + return; + } + + View v = View.inflate(context, R.layout.holding_it_wrong, null); + + AlertDialog.Builder b = new AlertDialog.Builder(context); + b.setCancelable(false); + b.setView(v); + + AlertDialog d = b.create(); + d.setOnDismissListener(this); + d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + d.show(); + mDialog = d; + } + + /** Must call from UI thread */ + public void dismiss() { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + } + + @Override + public void onDismiss(DialogInterface dialog) { + mDialog = null; + } +} diff --git a/src/com/android/nfc/P2pAnimationActivity.java b/src/com/android/nfc/P2pAnimationActivity.java deleted file mode 100644 index 6bc3fb9..0000000 --- a/src/com/android/nfc/P2pAnimationActivity.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright (C) 2011 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.nfc; - -import com.android.nfc3.R; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.WindowManager; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -public class P2pAnimationActivity extends Activity implements Handler.Callback, - AnimatorUpdateListener, View.OnTouchListener { - private static final float INITIAL_SCREENSHOT_SCALE = 0.6f; - private static final float FINAL_SCREENSHOT_SCALE = 0.0f; - - private static final int MSG_RESULT_FAILURE = 1; - private static final int MSG_RESULT_SEND = 2; - private static final int MSG_RESULT_RECEIVE = 3; - - private static final int STATE_WAITING = 0; - private static final int STATE_START_ANIM_DONE = 1; - private static final int STATE_SEND_SUCCESS = 2; - private static final int STATE_RECEIVE_SUCCESS = 3; - private static final int STATE_FAILURE = 4; - - private static final int DURATION_MS = 1400; - - Context mContext; - LayoutInflater mLayoutInflater; - View mScreenshotLayout; - ImageView mScreenshotView; - ImageView mClonedView; - ImageView mStars; - TextView mShareText; - - int mScreenshotWidth; - - StartAnimationListener mStartListener; - EndAnimationListener mEndListener; - - // Start animator, always played - AnimatorSet mStartAnimatorSet; - ValueAnimator mStartAnimator; - ValueAnimator mArrowStarsAnimator; - - // Send only animation - AnimatorSet mSendAnimatorSet; - ValueAnimator mScaleDownAnimator; - ValueAnimator mScaleUpAnimator; - - // Receive animation - ValueAnimator mReceiveAnimator; - - // Failure animation - ValueAnimator mFailureAnimator; - - DecelerateInterpolator mDecelerateInterpolator; - AccelerateInterpolator mAccelerateInterpolator; - - // Variables below are all read/written on the UI thread, so no - // need to synchronize these. - static int mAnimationState; - static Bitmap sScreenBitmap; - static P2pEventListener.Callback sCallback; - - // sHandler is initialized in onCreate() and can be read from - // multiple threads - synchronized on P2pAnimationActivity.class - static Handler sHandler; - - // These are initialized by calls to the static method createScreenshot() - // and are synchronized on P2pAnimationActivity.class - static Display mDisplay; - static DisplayMetrics mDisplayMetrics; - static Matrix mDisplayMatrix; - static WindowManager mWindowManager; - - class StartAnimationListener extends AnimatorListenerAdapter { - @Override - // Note that this will be called on the UI thread! - public void onAnimationEnd(Animator animation) { - if (mAnimationState != STATE_WAITING) { // Result already in - playEndAnimation(); - } else { - mAnimationState = STATE_START_ANIM_DONE; - } - } - } - - class EndAnimationListener extends AnimatorListenerAdapter { - @Override - public void onAnimationEnd(Animator animation) { - finish(); - overridePendingTransition(0, 0); - } - } - - void playEndAnimation() { - mShareText.setVisibility(View.GONE); - mArrowStarsAnimator.cancel(); - switch (mAnimationState) { - case STATE_SEND_SUCCESS: - mSendAnimatorSet.start(); - break; - case STATE_RECEIVE_SUCCESS: - mReceiveAnimator.start(); - break; - default: - mFailureAnimator.start(); - break; - } - } - - ValueAnimator getFloatAnimation(int duration, AnimatorUpdateListener updateListener, - AnimatorListener listener) { - ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); - anim.setInterpolator(null); - anim.setDuration(duration); - if (updateListener != null) { - anim.addUpdateListener(updateListener); - } - if (listener != null) { - anim.addListener(listener); - } - - return anim; - } - - void createAnimators() { - mStartListener = new StartAnimationListener(); - mEndListener = new EndAnimationListener(); - - - mStartAnimator = getFloatAnimation(500, this, mStartListener); - mArrowStarsAnimator = getFloatAnimation(DURATION_MS, this, null); - mArrowStarsAnimator.setRepeatCount(ValueAnimator.INFINITE); - - mStartAnimatorSet = new AnimatorSet(); - List<Animator> animList = new ArrayList<Animator>(); - animList.add(mStartAnimator); - animList.add(mArrowStarsAnimator); - mStartAnimatorSet.playSequentially(animList); - - - mScaleDownAnimator = getFloatAnimation(500, this, null); - mScaleUpAnimator = getFloatAnimation(500, this, null); - // Combine the two in a set - mSendAnimatorSet = new AnimatorSet(); - animList = new ArrayList<Animator>(); - animList.add(mScaleDownAnimator); - animList.add(mScaleUpAnimator); - mSendAnimatorSet.playSequentially(animList); - mSendAnimatorSet.addListener(mEndListener); - - mFailureAnimator = getFloatAnimation(500, this, mEndListener); - mReceiveAnimator = getFloatAnimation(200, this, mEndListener); - - mDecelerateInterpolator = new DecelerateInterpolator(1.5f); - mAccelerateInterpolator = new AccelerateInterpolator(1.5f); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - synchronized(P2pAnimationActivity.class) { - // Note that we will always need to allocate a new handler, - // since the handler is bound a specific activity instance - // which will be destroyed and recreated - sHandler = new Handler(this); - } - - // Inflate the screenshot layout - mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); - - mStars = (ImageView) mScreenshotLayout.findViewById(R.id.stars); - - mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); - mScreenshotView.setOnTouchListener(this); - - mClonedView = (ImageView) mScreenshotLayout.findViewById(R.id.clone); - mClonedView.setOnTouchListener(this); - - mShareText = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction); - - setContentView(mScreenshotLayout); - - createAnimators(); - - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Do nothing to ignore orientation changes - } - - @Override - protected void onResume() { - super.onResume(); - - // Lock rotation - final int orientation = getResources().getConfiguration().orientation; - setRequestedOrientation(orientation); - - if (sScreenBitmap != null) { - mClonedView.setImageBitmap(sScreenBitmap); - mClonedView.setVisibility(View.GONE); - mScreenshotView.setImageBitmap(sScreenBitmap); - mScreenshotWidth = sScreenBitmap.getWidth(); - - startAnimating(); - } - } - - @Override - protected void onPause() { - super.onPause(); - - mStars.setVisibility(View.GONE); - if (mStartAnimator != null) { - mStartAnimator.end(); - } - if (mSendAnimatorSet != null) { - mSendAnimatorSet.end(); - } - if (mReceiveAnimator != null) { - mReceiveAnimator.end(); - } - if (mFailureAnimator != null) { - mFailureAnimator.end(); - } - if (mStartAnimatorSet != null) { - mStartAnimatorSet.end(); - } - } - - /** - * Takes a screenshot of the current display and shows an animation. - * - * Must be called from the UI thread. - */ - public void startAnimating() { - // At this point no anims are running, no need to sync these - mAnimationState = STATE_WAITING; - mStartAnimatorSet.start(); - } - - /** - * Finalizes the running animation with a failure animation. - */ - public static void finishWithFailure() { - synchronized (P2pAnimationActivity.class) { - if (sHandler != null) { - sHandler.sendEmptyMessage(MSG_RESULT_FAILURE); - } - } - } - - /** - * Finalizes the running animation with the send animation. - */ - public static void finishWithSend() { - synchronized (P2pAnimationActivity.class) { - if (sHandler != null) { - sHandler.sendEmptyMessage(MSG_RESULT_SEND); - } - } - } - - /** - * Finalizes the running animation with the received animation. - */ - public static void finishWithReceive() { - synchronized (P2pAnimationActivity.class) { - if (sHandler != null) { - sHandler.sendEmptyMessage(MSG_RESULT_RECEIVE); - } - } - } - - /** - * Creates and sets the screenshot to be animated. - * Must be called on the UI thread. - * @param screenshot to be animated - */ - public static void makeScreenshot(Context context) { - sScreenBitmap = createScreenshot(context); - } - - public static void setCallback(P2pEventListener.Callback callback) { - sCallback = callback; - } - - private void onStartAnimationUpdate(ValueAnimator animation) { - // Just scale the screenshot down - float t = ((Float) animation.getAnimatedValue()).floatValue(); - - float scale = mDecelerateInterpolator.getInterpolation(t); - float scaleT = INITIAL_SCREENSHOT_SCALE + (1f - scale) * - (1 - INITIAL_SCREENSHOT_SCALE); - - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - } - - private void onSuccessCloneAnimationUpdate(ValueAnimator animation) { - // Clone the screenshot - if (mClonedView.getVisibility() != View.VISIBLE) { - mClonedView.setVisibility(View.VISIBLE); - mScreenshotView.setAlpha(0.5f); - } - - float t = ((Float) animation.getAnimatedValue()).floatValue(); - float scale = mDecelerateInterpolator.getInterpolation(t); - float scaleT = INITIAL_SCREENSHOT_SCALE - (scale * - (INITIAL_SCREENSHOT_SCALE - FINAL_SCREENSHOT_SCALE)); - - mClonedView.setScaleX(scaleT); - mClonedView.setScaleY(scaleT); - } - - private void onSuccessUpUpdate(ValueAnimator animation) { - // Scale the screenshot all the way back to the front, - // scale the clone down to zero. - float t = ((Float) animation.getAnimatedValue()).floatValue(); - float scale = mDecelerateInterpolator.getInterpolation(t); - float scaleT = INITIAL_SCREENSHOT_SCALE + - (scale * (1.0f - INITIAL_SCREENSHOT_SCALE)); - float alpha = 0.5f + (0.5f * mAccelerateInterpolator.getInterpolation(t)); - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - mScreenshotView.setAlpha(alpha); - } - - private void onFailureUpdate(ValueAnimator animation) { - // Scale back from initial scale to normal scale - float t = ((Float) animation.getAnimatedValue()).floatValue(); - float scale = mDecelerateInterpolator.getInterpolation(t); - float scaleT = INITIAL_SCREENSHOT_SCALE + (scale * - (1.0f - INITIAL_SCREENSHOT_SCALE)); - - mScreenshotView.setScaleX(scaleT); - mScreenshotView.setScaleY(scaleT); - - } - - private void onReceiveUpdate(ValueAnimator animation) { - float t = ((Float) animation.getAnimatedValue()).floatValue(); - float offset = mDecelerateInterpolator.getInterpolation(t); - - mScreenshotView.setX(offset * mScreenshotWidth); - mShareText.setX(offset * mScreenshotWidth); - } - - private void onArrowStarsAnimationUpdate(ValueAnimator animation) { - - float t = ((Float) animation.getAnimatedValue()).floatValue(); - - float scale = 1.0f + (0.5f * t); - mStars.setScaleX(scale); - mStars.setScaleY(scale); - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - if (animation == mStartAnimator) { - onStartAnimationUpdate(animation); - } else if (animation == mArrowStarsAnimator) { - onArrowStarsAnimationUpdate(animation); - } else if (animation == mScaleDownAnimator) { - onSuccessCloneAnimationUpdate(animation); - } else if (animation == mScaleUpAnimator) { - onSuccessUpUpdate(animation); - } else if (animation == mFailureAnimator) { - onFailureUpdate(animation); - } else if (animation == mReceiveAnimator) { - onReceiveUpdate(animation); - } - } - - @Override - public boolean handleMessage(Message msg) { - int oldState = mAnimationState; - switch (msg.what) { - case MSG_RESULT_FAILURE: { - mAnimationState = STATE_FAILURE; - break; - } - case MSG_RESULT_SEND: { - mAnimationState = STATE_SEND_SUCCESS; - break; - } - case MSG_RESULT_RECEIVE: { - mAnimationState = STATE_RECEIVE_SUCCESS; - break; - } - } - - if (oldState == STATE_START_ANIM_DONE) { - // Start animaton was already completed, play - // ending animation now. - playEndAnimation(); - } else { - // The ending animation will be played whenever the - // start animation is finished. - } - return true; - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (sCallback != null && P2pEventManager.TAP_ENABLED) { - sCallback.onP2pSendConfirmed(); - } - return true; - } - - /** - * @return the current display rotation in degrees - */ - static float getDegreesForRotation(int value) { - switch (value) { - case Surface.ROTATION_90: - return 90f; - case Surface.ROTATION_180: - return 180f; - case Surface.ROTATION_270: - return 270f; - } - return 0f; - } - - /** - * Returns a screenshot of the current display contents. - * @param context Context. - * @return - */ - static Bitmap createScreenshot(Context context) { - synchronized(P2pAnimationActivity.class) { - if (mDisplay == null) { - // First time, init statics - mDisplayMetrics = new DisplayMetrics(); - mDisplayMatrix = new Matrix(); - mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - mDisplay = mWindowManager.getDefaultDisplay(); - } - } - // We need to orient the screenshot correctly (and the Surface api seems to - // take screenshots only in the natural orientation of the device :!) - mDisplay.getRealMetrics(mDisplayMetrics); - float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; - float degrees = getDegreesForRotation(mDisplay.getRotation()); - boolean requiresRotation = (degrees > 0); - if (requiresRotation) { - // Get the dimensions of the device in its native orientation - mDisplayMatrix.reset(); - mDisplayMatrix.preRotate(-degrees); - mDisplayMatrix.mapPoints(dims); - dims[0] = Math.abs(dims[0]); - dims[1] = Math.abs(dims[1]); - } - - Bitmap bitmap = Surface.screenshot((int) dims[0], (int) dims[1]); - // Bail if we couldn't take the screenshot - if (bitmap == null) { - return null; - } - - if (requiresRotation) { - // Rotate the screenshot to the current orientation - Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, - mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(ss); - c.translate(ss.getWidth() / 2, ss.getHeight() / 2); - c.rotate(360f - degrees); - c.translate(-dims[0] / 2, -dims[1] / 2); - c.drawBitmap(bitmap, 0, 0, null); - - bitmap = ss; - } - - return bitmap; - } - -} diff --git a/src/com/android/nfc/P2pEventManager.java b/src/com/android/nfc/P2pEventManager.java index 05fc234..66d8a92 100644 --- a/src/com/android/nfc/P2pEventManager.java +++ b/src/com/android/nfc/P2pEventManager.java @@ -37,7 +37,7 @@ import com.android.nfc3.R; /** * Manages vibration, sound and animation for P2P events. */ -public class P2pEventManager implements P2pEventListener { +public class P2pEventManager implements P2pEventListener, SendUi.Callback { static final String TAG = "NfcP2pEventManager"; static final boolean DBG = true; @@ -46,9 +46,6 @@ public class P2pEventManager implements P2pEventListener { static final long[] VIBRATION_PATTERN = {0, 100, 10000}; - public static final boolean TILT_ENABLED = true; - public static final boolean TAP_ENABLED = true; - final Context mContext; final P2pEventListener.Callback mCallback; final SharedPreferences mPrefs; @@ -59,10 +56,12 @@ public class P2pEventManager implements P2pEventListener { final Vibrator mVibrator; final TiltDetector mTiltDetector; final NotificationManager mNotificationManager; + final HoldingItWrongUi mHoldingItWrongUi; + final SendUi mSendUi; // only used on UI thread boolean mPrefsFirstShare; - boolean mAnimating; + boolean mSending; /** Detect if the screen is facing up or down */ class TiltDetector implements SensorEventListener { @@ -76,15 +75,12 @@ public class P2pEventManager implements P2pEventListener { // Only used on UI thread boolean mSensorEnabled; - boolean mTriggerEnabled; float mLastValue; public TiltDetector(Context context) { mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorEnabled = false; - mTriggerEnabled = false; - mLastValue = Float.MIN_VALUE; } public void enable() { if (mSensorEnabled) { @@ -94,37 +90,33 @@ public class P2pEventManager implements P2pEventListener { mLastValue = Float.MIN_VALUE; mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_UI); } - public boolean enableTrigger() { - if (!mSensorEnabled || mTriggerEnabled) { - return false; - } - mTriggerEnabled = true; - return checkTrigger(); - } public void disable() { if (!mSensorEnabled) { return; } mSensorManager.unregisterListener(this, mSensor); mSensorEnabled = false; - mTriggerEnabled = false; - } - boolean checkTrigger() { - if (!mTriggerEnabled || - 100.0 * mLastValue / SensorManager.GRAVITY_EARTH < THRESHOLD_PERCENT) { - return false; - } - disable(); - mVibrator.vibrate(VIBRATION_PATTERN, -1); - mCallback.onP2pSendConfirmed(); - return true; } @Override public void onSensorChanged(SensorEvent event) { // always called on UI thread - mLastValue = event.values[2]; - if (DBG) Log.d(TAG, "z=" + mLastValue); - checkTrigger(); + if (!mSensorEnabled) { + return; + } + final float z = event.values[2]; + final boolean triggered = 100.0 * z / SensorManager.GRAVITY_EARTH > THRESHOLD_PERCENT; + //TODO: apply a low pass filter so we get something closer to real gravity + if (DBG) Log.d(TAG, "z=" + z + (triggered ? " TRIGGERED" : "")); + if (mLastValue == Float.MIN_VALUE && !triggered) { + // Received first value, and you're holding it wrong + mHoldingItWrongUi.show(mContext); + } + mLastValue = z; + if (triggered) { + disable(); + onSendConfirmed(); + } + return; } @Override public void onAccuracyChanged(Sensor arg0, int arg1) { } @@ -153,105 +145,86 @@ public class P2pEventManager implements P2pEventListener { mPrefsFirstShare = false; } - P2pAnimationActivity.setCallback(mCallback); - mAnimating = false; + mSending = false; + mHoldingItWrongUi = new HoldingItWrongUi(); + mSendUi = new SendUi(context, this); } @Override public void onP2pInRange() { - if (TILT_ENABLED) { - mTiltDetector.enable(); - } - mVibrator.vibrate(VIBRATION_PATTERN, -1); - P2pAnimationActivity.makeScreenshot(mContext); + mSendUi.takeScreenshot(); } @Override public void onP2pSendConfirmationRequested() { - if (TILT_ENABLED && mTiltDetector.enableTrigger()) { - return; - } - - mAnimating = true; - // Start the animation activity - Intent animIntent = new Intent(); - animIntent.setClass(mContext, P2pAnimationActivity.class); - animIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivity(animIntent); - playSound(mStartSound); + mTiltDetector.enable(); } @Override public void onP2pSendComplete() { - playSound(mEndSound); checkFirstShare(); - finish(true, false); + playSound(mEndSound); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + mSendUi.showPostSend(); + mSending = false; } @Override public void onP2pReceiveComplete() { - if (TILT_ENABLED) { - mTiltDetector.disable(); - } - finish(false, true); + mHoldingItWrongUi.dismiss(); + mTiltDetector.disable(); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + playSound(mEndSound); } @Override public void onP2pOutOfRange() { - if (TILT_ENABLED) { - mTiltDetector.disable(); - } - if (mAnimating) { + mHoldingItWrongUi.dismiss(); + mTiltDetector.disable(); + if (mSending) { playSound(mErrorSound); + mSendUi.dismiss(); + mSending = false; } - finish(false, false); + mSendUi.releaseScreenshot(); } - /** - * Finish up the animation, if running. - * Must be called on the UI thread. - */ - void finish(boolean sendSuccess, boolean receiveSuccess) { - if (!mAnimating) { - return; - } - if (sendSuccess) { - P2pAnimationActivity.finishWithSend(); - } else if (receiveSuccess) { - P2pAnimationActivity.finishWithReceive(); - } else { - P2pAnimationActivity.finishWithFailure(); - } - mAnimating = false; + void onSendConfirmed() { + mVibrator.vibrate(VIBRATION_PATTERN, -1); + playSound(mStartSound); + mHoldingItWrongUi.dismiss(); + mSending = true; + mSendUi.showPreSend(); } - /** If first time, display up a notification */ - void checkFirstShare() { - synchronized (this) { - if (mPrefsFirstShare) { - mPrefsFirstShare = false; - SharedPreferences.Editor editor = mPrefs.edit(); - editor.putBoolean(PREF_FIRST_SHARE, false); - editor.apply(); + @Override + public void onPreFinished() { + mCallback.onP2pSendConfirmed(); + } - Intent intent = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); - PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT); - Notification notification = new Notification.Builder(mContext) - .setContentTitle(mContext.getString(R.string.first_share_title)) - .setContentText(mContext.getString(R.string.first_share_text)) - .setContentIntent(pi) - .setSmallIcon(R.drawable.stat_sys_nfc) - .setAutoCancel(true) - .getNotification(); - mNotificationManager.notify(NOTIFICATION_FIRST_SHARE, notification); - } + /** If first time, display a notification */ + void checkFirstShare() { + if (mPrefsFirstShare) { + mPrefsFirstShare = false; + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putBoolean(PREF_FIRST_SHARE, false); + editor.apply(); + + Intent intent = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); + PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + Notification notification = new Notification.Builder(mContext) + .setContentTitle(mContext.getString(R.string.first_share_title)) + .setContentText(mContext.getString(R.string.first_share_text)) + .setContentIntent(pi) + .setSmallIcon(R.drawable.stat_sys_nfc) + .setAutoCancel(true) + .getNotification(); + mNotificationManager.notify(NOTIFICATION_FIRST_SHARE, notification); } } void playSound(int sound) { - synchronized (this) { - mSoundPool.play(sound, 1.0f, 1.0f, 0, 0, 1.0f); - } + mSoundPool.play(sound, 1.0f, 1.0f, 0, 0, 1.0f); } } diff --git a/src/com/android/nfc/SendUi.java b/src/com/android/nfc/SendUi.java new file mode 100644 index 0000000..a9acb96 --- /dev/null +++ b/src/com/android/nfc/SendUi.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011 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.nfc; + +import com.android.nfc3.R; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.os.Binder; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.Surface; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; + +/** + * All methods must be called on UI thread + */ +public class SendUi implements Animator.AnimatorListener { + + static final float[] PRE_SCREENSHOT_SCALE = {1.0f, 0.6f}; + static final int PRE_DURATION_MS = 50; + + static final float[] POST_SCREENSHOT_SCALE = {0.6f, 0.0f}; + static final int POST_DURATION_MS = 200; + + // all members are only used on UI thread + final WindowManager mWindowManager; + final Context mContext; + final Display mDisplay; + final DisplayMetrics mDisplayMetrics; + final Matrix mDisplayMatrix; + final WindowManager.LayoutParams mWindowLayoutParams; + final LayoutInflater mLayoutInflater; + final View mScreenshotLayout; + final ImageView mScreenshotView; + final Callback mCallback; + final ObjectAnimator mPreAnimator; + final ObjectAnimator mPostAnimator; + + Bitmap mScreenshotBitmap; + boolean mAttached; + + interface Callback { + public void onPreFinished(); + } + + public SendUi(Context context, Callback callback) { + mContext = context; + mCallback = callback; + + mDisplayMetrics = new DisplayMetrics(); + mDisplayMatrix = new Matrix(); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + + mLayoutInflater = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); + mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); + mScreenshotLayout.setFocusable(true); + + mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, 0, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED_SYSTEM + | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, + PixelFormat.OPAQUE); + mWindowLayoutParams.token = new Binder(); + + PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE); + PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE); + mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY); + mPreAnimator.setInterpolator(null); // linear + mPreAnimator.setDuration(PRE_DURATION_MS); + mPreAnimator.addListener(this); + + PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", POST_SCREENSHOT_SCALE); + PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", POST_SCREENSHOT_SCALE); + mPostAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY); + mPostAnimator.setInterpolator(null); // linear + mPostAnimator.setDuration(POST_DURATION_MS); + mPostAnimator.addListener(this); + + mAttached = false; + } + + public void takeScreenshot() { + mScreenshotBitmap = createScreenshot(); + } + + /** Show pre-send animation, calls onPreFinished() when complete */ + public void showPreSend() { + if (mScreenshotBitmap == null || mAttached) { + return; + } + mScreenshotView.setImageBitmap(mScreenshotBitmap); + mScreenshotLayout.requestFocus(); + mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); + mAttached = true; + mPreAnimator.start(); + + //TODO: Lock rotation +// final int orientation = getResources().getConfiguration().orientation; +// setRequestedOrientation(orientation); + } + + /** Show post-send animation */ + public void showPostSend() { + if (!mAttached) { + return; + } + mPostAnimator.start(); + } + + public void dismiss() { + if (!mAttached) { + return; + } + mPreAnimator.cancel(); + mPostAnimator.cancel(); + mWindowManager.removeView(mScreenshotLayout); + mAttached = false; + releaseScreenshot(); + } + + public void releaseScreenshot() { + mScreenshotBitmap = null; + } + + /** + * @return the current display rotation in degrees + */ + static float getDegreesForRotation(int value) { + switch (value) { + case Surface.ROTATION_90: + return 90f; + case Surface.ROTATION_180: + return 180f; + case Surface.ROTATION_270: + return 270f; + } + return 0f; + } + + /** + * Returns a screenshot of the current display contents. + * @param context Context. + * @return + */ + Bitmap createScreenshot() { + // We need to orient the screenshot correctly (and the Surface api seems to + // take screenshots only in the natural orientation of the device :!) + mDisplay.getRealMetrics(mDisplayMetrics); + float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; + float degrees = getDegreesForRotation(mDisplay.getRotation()); + boolean requiresRotation = (degrees > 0); + if (requiresRotation) { + // Get the dimensions of the device in its native orientation + mDisplayMatrix.reset(); + mDisplayMatrix.preRotate(-degrees); + mDisplayMatrix.mapPoints(dims); + dims[0] = Math.abs(dims[0]); + dims[1] = Math.abs(dims[1]); + } + + Bitmap bitmap = Surface.screenshot((int) dims[0], (int) dims[1]); + // Bail if we couldn't take the screenshot + if (bitmap == null) { + return null; + } + + if (requiresRotation) { + // Rotate the screenshot to the current orientation + Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(ss); + c.translate(ss.getWidth() / 2, ss.getHeight() / 2); + c.rotate(360f - degrees); + c.translate(-dims[0] / 2, -dims[1] / 2); + c.drawBitmap(bitmap, 0, 0, null); + + bitmap = ss; + } + + return bitmap; + } + + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + if (animation == mPreAnimator) { + mCallback.onPreFinished(); + } else if (animation == mPostAnimator) { + dismiss(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { } + + @Override + public void onAnimationRepeat(Animator animation) { } +} |