summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJeff Hamilton <jham@android.com>2011-07-22 15:16:58 -0500
committerJeff Hamilton <jham@android.com>2011-07-26 16:11:15 -0500
commitd2d5dddf17ac2008547172cd72faa034a89d569b (patch)
treecaa6d552626a3285b7518d3895974ba06bdcf893 /src
parent09a978f1e8869341029913fe8502cdcc9b9dd6a9 (diff)
downloadpackages_apps_nfc-d2d5dddf17ac2008547172cd72faa034a89d569b.zip
packages_apps_nfc-d2d5dddf17ac2008547172cd72faa034a89d569b.tar.gz
packages_apps_nfc-d2d5dddf17ac2008547172cd72faa034a89d569b.tar.bz2
Play an animation for outbound NFC P2P.
Clean up NFC service sound playing. Cancel P2P tasks when the LLCP link is broken. Bug: 4941390 Change-Id: I0710bc7dd9d09ed47a540cb4739890bcc3a4f905
Diffstat (limited to 'src')
-rwxr-xr-xsrc/com/android/nfc/NdefP2pManager.java100
-rwxr-xr-xsrc/com/android/nfc/NfcService.java54
-rw-r--r--src/com/android/nfc/ScreenshotWindowAnimator.java239
-rwxr-xr-xsrc/com/android/nfc/ndefpush/NdefPushClient.java8
4 files changed, 364 insertions, 37 deletions
diff --git a/src/com/android/nfc/NdefP2pManager.java b/src/com/android/nfc/NdefP2pManager.java
index 8afbd75..9f1dadc 100755
--- a/src/com/android/nfc/NdefP2pManager.java
+++ b/src/com/android/nfc/NdefP2pManager.java
@@ -57,7 +57,7 @@ public class NdefP2pManager {
private static final String TAG = "P2PManager";
private static final boolean DBG = true;
- private final INfcAdapter.Stub mNfcAdapter;
+ final ScreenshotWindowAnimator mScreenshot;
private final NdefPushServer mNdefPushServer;
private final SnepServer mDefaultSnepServer;
private final SnepServer mAndroidSnepServer;
@@ -65,6 +65,9 @@ public class NdefP2pManager {
private final ActivityManager mActivityManager;
private final PackageManager mPackageManager;
private final Context mContext;
+ final P2pStatusListener mListener;
+
+ P2pTask mActiveTask;
final static Uri mProfileUri = Profile.CONTENT_VCARD_URI.buildUpon().
appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true").
@@ -74,8 +77,7 @@ public class NdefP2pManager {
NdefMessage mForegroundMsg;
INdefPushCallback mCallback;
- public NdefP2pManager(Context context, INfcAdapter.Stub adapter) {
- mNfcAdapter = adapter;
+ public NdefP2pManager(Context context, P2pStatusListener listener) {
mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP);
mDefaultSnepServer = new SnepServer(mDefaultSnepCallback);
mAndroidSnepServer = new SnepServer(ANDROID_SNEP_SERVICE, ANDROIDSNEP_SAP,
@@ -84,6 +86,8 @@ public class NdefP2pManager {
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mPackageManager = context.getPackageManager();
mContext = context;
+ mScreenshot = new ScreenshotWindowAnimator(context);
+ mListener = listener;
}
public void enableNdefServer() {
@@ -173,6 +177,8 @@ public class NdefP2pManager {
/*package*/ void llcpActivated() {
if (DBG) Log.d(TAG, "LLCP connection up and running");
+ mListener.onP2pBegin();
+
NdefMessage foregroundMsg;
synchronized (this) {
foregroundMsg = mForegroundMsg;
@@ -210,39 +216,64 @@ public class NdefP2pManager {
}
}
- if (foregroundMsg != null) {
- if (DBG) Log.d(TAG, "sending foreground");
- new P2pTask().execute(foregroundMsg);
- } else {
- if (DBG) Log.d(TAG, "no foreground msg");
- new P2pTask().execute();
- }
+ mActiveTask = new P2pTask(foregroundMsg);
+ mActiveTask.execute();
}
/*package*/ void llcpDeactivated() {
if (DBG) Log.d(TAG, "LLCP deactivated.");
+
+ // Call error here since a previous call to onP2pEnd() will have played the success
+ // sound and this will be ignored. If no one has called that and the LLCP link
+ // is broken we want to play the error sound.
+ mListener.onP2pError();
+
+ if (mActiveTask != null) {
+ mActiveTask.cancel(true);
+ mActiveTask = null;
+ }
}
- final class P2pTask extends AsyncTask<NdefMessage, Void, Void> {
+ final class P2pTask extends AsyncTask<Void, Void, Void> {
+ final private NdefMessage mMessage;
+
+ private boolean mSuccess = false;
+
+ public P2pTask(NdefMessage msg) {
+ mMessage = msg;
+ }
+
+ @Override
+ public void onPreExecute() {
+ if (mMessage != null) {
+ mScreenshot.start();
+ }
+ }
+
@Override
- public Void doInBackground(NdefMessage... msgs) {
+ public Void doInBackground(Void... args) {
//TODO: call getDropboxTarget() here for large NDEF transfer
NdefMessage dropboxTarget = null;
try {
- if (msgs.length > 0) {
- NdefMessage foregroundMsg = msgs[0];
+ if (mMessage != null) {
if (dropboxTarget != null &&
- foregroundMsg.toByteArray().length > MAX_SNEP_SIZE_BYTES) {
+ mMessage.toByteArray().length > MAX_SNEP_SIZE_BYTES) {
if (DBG) Log.d(TAG, "Sending large ndef to dropbox");
- mBluetoothDropbox.sendContent(dropboxTarget, foregroundMsg);
+ mBluetoothDropbox.sendContent(dropboxTarget, mMessage);
+ // TODO set mSuccess
} else {
if (DBG) Log.d(TAG, "Sending ndef via SNEP");
- doSnepProtocol(msgs[0]);
+ mSuccess = doSnepProtocol(mMessage);
}
}
} catch (IOException e) {
if (DBG) Log.d(TAG, "Failed to connect over SNEP, trying NPP");
- new NdefPushClient().push(msgs);
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ mSuccess = new NdefPushClient().push(mMessage);
}
INdefPushCallback callback;
@@ -258,12 +289,19 @@ public class NdefP2pManager {
}
}
+ if (isCancelled()) {
+ return null;
+ }
+
// Me profile
dropboxTarget = getDropboxTarget();
NdefMessage me = getMeProfile();
if (dropboxTarget != null && me != null) {
if (DBG) Log.d(TAG, "Sending me profile");
try {
+ if (isCancelled()) {
+ return null;
+ }
mBluetoothDropbox.handleOutboundMeProfile(dropboxTarget, me);
} catch (IOException e) {
if (DBG) Log.d(TAG, "Failed to send me profile");
@@ -272,9 +310,31 @@ public class NdefP2pManager {
return null;
}
+
+ @Override
+ public void onCancelled() {
+ if (mMessage != null) {
+ mScreenshot.stop();
+ }
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ // Make sure to stop the screenshot animation
+ if (mMessage != null) {
+ mScreenshot.stop();
+ }
+
+ // Play the end sound if successful
+ if (mMessage != null && mSuccess) {
+ mListener.onP2pEnd();
+ }
+
+ mActiveTask = null;
+ }
}
- void doSnepProtocol(NdefMessage msg) throws IOException {
+ boolean doSnepProtocol(NdefMessage msg) throws IOException {
SnepClient snepClient = new SnepClient();
try {
snepClient.connect();
@@ -286,11 +346,13 @@ public class NdefP2pManager {
try {
snepClient.put(msg);
+ return true;
} catch (IOException e) {
// SNEP available but had errors, don't fall back to NPP.
} finally {
snepClient.close();
}
+ return false;
}
/**
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index efe5f8b..51f1456 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -31,7 +31,6 @@ import com.android.nfc3.R;
import android.app.Application;
import android.app.KeyguardManager;
import android.app.PendingIntent;
-import android.app.StatusBarManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -55,7 +54,6 @@ import android.nfc.IP2pInitiator;
import android.nfc.IP2pTarget;
import android.nfc.LlcpPacket;
import android.nfc.NdefMessage;
-import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.TechListParcel;
@@ -72,11 +70,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
-import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -84,7 +80,13 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-public class NfcService extends Application implements DeviceHostListener {
+interface P2pStatusListener {
+ void onP2pBegin();
+ void onP2pEnd();
+ void onP2pError();
+}
+
+public class NfcService extends Application implements DeviceHostListener, P2pStatusListener {
private static final String ACTION_MASTER_CLEAR_NOTIFICATION = "android.intent.action.MASTER_CLEAR_NOTIFICATION";
static final boolean DBG = true;
@@ -161,6 +163,9 @@ public class NfcService extends Application implements DeviceHostListener {
private OpenSecureElement mOpenEe; // null when EE closed
private int mEeRoutingState; // contactless interface routing
+ // fields below must be used only on the UI thread and therefore aren't synchronized
+ boolean mP2pStarted = false;
+
// fields below are used in multiple threads and protected by synchronized(this)
private final HashMap<Integer, Object> mObjectMap = new HashMap<Integer, Object>();
private final HashMap<Integer, Object> mSocketMap = new HashMap<Integer, Object>();
@@ -280,7 +285,7 @@ public class NfcService extends Application implements DeviceHostListener {
mDeviceHost = new NativeNfcManager(this, this);
mDeviceHost.initializeNativeStructure();
- mP2pManager = new NdefP2pManager(this, mNfcAdapter);
+ mP2pManager = new NdefP2pManager(this, this);
mNfcDispatcher = new NfcDispatcher(this, mP2pManager);
mSecureElement = new NativeNfcSecureElement();
@@ -325,13 +330,37 @@ public class NfcService extends Application implements DeviceHostListener {
t.start();
}
- public void playSound(int sound) {
+ private void playSound(int sound) {
synchronized (this) {
mSoundPool.play(sound, 1.0f, 1.0f, 0, 0, 1.0f);
}
}
@Override
+ public void onP2pBegin() {
+ if (!mP2pStarted) {
+ playSound(mStartSound);
+ mP2pStarted = true;
+ }
+ }
+
+ @Override
+ public void onP2pEnd() {
+ if (mP2pStarted) {
+ playSound(mEndSound);
+ mP2pStarted = false;
+ }
+ }
+
+ @Override
+ public void onP2pError() {
+ if (mP2pStarted) {
+ playSound(mErrorSound);
+ mP2pStarted = false;
+ }
+ }
+
+ @Override
public void onTerminate() {
super.onTerminate();
// NFC application is persistent, it should not be destroyed by framework
@@ -2134,9 +2163,7 @@ public class NfcService extends Application implements DeviceHostListener {
Log.d(TAG, tag.toString());
boolean delivered = mNfcDispatcher.dispatchTag(tag, new NdefMessage[] { ndefMsg });
if (delivered) {
- playSound(mEndSound);
- } else {
- playSound(mErrorSound);
+ onP2pEnd();
}
break;
}
@@ -2158,6 +2185,7 @@ public class NfcService extends Application implements DeviceHostListener {
} else {
Log.w(TAG, "Failed to connect to tag");
tag.disconnect();
+ playSound(mErrorSound);
}
}
break;
@@ -2211,11 +2239,7 @@ public class NfcService extends Application implements DeviceHostListener {
break;
case MSG_LLCP_LINK_ACTIVATION:
- if (llcpActivated((NfcDepEndpoint) msg.obj)) {
- playSound(mStartSound);
- } else {
- playSound(mErrorSound);
- }
+ llcpActivated((NfcDepEndpoint) msg.obj);
break;
case MSG_LLCP_LINK_DEACTIVATED:
diff --git a/src/com/android/nfc/ScreenshotWindowAnimator.java b/src/com/android/nfc/ScreenshotWindowAnimator.java
new file mode 100644
index 0000000..ce0a7bc
--- /dev/null
+++ b/src/com/android/nfc/ScreenshotWindowAnimator.java
@@ -0,0 +1,239 @@
+/*
+ * 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.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+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.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+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.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+
+/**
+ * TODO:
+ * - Performance when over gl surfaces? Ie. Gallery
+ * - what do we say in the Toast? Which icon do we get if the user uses another
+ * type of gallery?
+ */
+public class ScreenshotWindowAnimator implements AnimatorUpdateListener, Handler.Callback {
+ private static final String TAG = "ScreenshotWindowAnimator";
+
+ private static final int ANIMATION_DURATION_MS = 7500;
+ private static final float SCREENSHOT_SCALE = 0.5f;
+
+ private static final int MSG_START_ANIMATION = 1;
+ private static final int MSG_STOP_ANIMATION = 2;
+
+ Context mContext;
+ LayoutInflater mLayoutInflater;
+ WindowManager mWindowManager;
+ WindowManager.LayoutParams mWindowLayoutParams;
+ Display mDisplay;
+ DisplayMetrics mDisplayMetrics;
+ Matrix mDisplayMatrix;
+ Bitmap mScreenBitmap;
+ View mScreenshotLayout;
+ ImageView mScreenshotView;
+// Animator mScreenshotAnimator;
+ Listener mListener;
+ ValueAnimator mAnimator;
+ boolean mAttached = false;
+ Handler mHandler;
+
+ class Listener extends AnimatorListenerAdapter {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mAttached) {
+ mWindowManager.removeView(mScreenshotLayout);
+ mAttached = false;
+ }
+ }
+ }
+
+ /**
+ * @param context everything needs a context :(
+ */
+ public ScreenshotWindowAnimator(Context context) {
+ mContext = context;
+ mHandler = new Handler(this);
+ mLayoutInflater = (LayoutInflater)
+ context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ mListener = new Listener();
+
+ // Create the animation
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setInterpolator(new DecelerateInterpolator());
+ anim.setDuration(ANIMATION_DURATION_MS);
+ anim.addUpdateListener(this);
+ anim.addListener(mListener);
+ mAnimator = anim;
+
+ // Inflate the screenshot layout
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplayMatrix = new Matrix();
+ mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null);
+ mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot);
+ mScreenshotLayout.setFocusable(true);
+/* this doesn't work on the TYPE_SYSTEM_OVERLAY layer
+ mScreenshotLayout.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Intercept and ignore all touch events
+ return true;
+ }
+ });
+*/
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
+ 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,
+ PixelFormat.OPAQUE);
+ mWindowLayoutParams.token = new Binder();
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWindowManager.getDefaultDisplay();
+ }
+
+ /**
+ * @return the current display rotation in degrees
+ */
+ private 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;
+ }
+
+ /**
+ * Takes a screenshot of the current display and shows an animation.
+ *
+ * Must be called from the UI thread.
+ */
+ public void start() {
+ // 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]);
+ }
+ mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
+ 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(mScreenBitmap, 0, 0, null);
+ mScreenBitmap = ss;
+ }
+
+ // Bail if we couldn't take the screenshot
+ if (mScreenBitmap == null) {
+ Log.e(TAG, "couldn't get screenshot");
+ return;
+ }
+
+ // Start the post-screenshot animation
+ mScreenshotView.setImageBitmap(mScreenBitmap);
+ mScreenshotLayout.requestFocus();
+
+ // Setup the animation with the screenshot just taken
+ if (mAnimator != null) {
+ mAnimator.end();
+ }
+
+ // Add the view for the animation
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ mAttached = true;
+ mHandler.sendEmptyMessage(MSG_START_ANIMATION);
+ }
+
+ /**
+ * Stops the currently playing animation.
+ *
+ * Must be called from the UI thread.
+ */
+ public void stop() {
+ mHandler.sendEmptyMessage(MSG_STOP_ANIMATION);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = ((Float) animation.getAnimatedValue()).floatValue();
+ float scaleT = SCREENSHOT_SCALE + (1f - t) * SCREENSHOT_SCALE;
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_ANIMATION: {
+ mAnimator.start();
+ break;
+ }
+
+ case MSG_STOP_ANIMATION: {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ break;
+ }
+ }
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/nfc/ndefpush/NdefPushClient.java b/src/com/android/nfc/ndefpush/NdefPushClient.java
index ebe3ccc..1bb1bfe 100755
--- a/src/com/android/nfc/ndefpush/NdefPushClient.java
+++ b/src/com/android/nfc/ndefpush/NdefPushClient.java
@@ -35,12 +35,12 @@ public class NdefPushClient {
private static final int MIU = 128;
private static final boolean DBG = true;
- public void push(NdefMessage[] msgs) {
+ public boolean push(NdefMessage msg) {
NfcService service = NfcService.getInstance();
// We only handle a single immediate action for now
- NdefPushProtocol msg = new NdefPushProtocol(msgs[0], NdefPushProtocol.ACTION_IMMEDIATE);
- byte[] buffer = msg.toByteArray();
+ NdefPushProtocol proto = new NdefPushProtocol(msg, NdefPushProtocol.ACTION_IMMEDIATE);
+ byte[] buffer = proto.toByteArray();
int offset = 0;
int remoteMiu;
LlcpSocket sock = null;
@@ -63,6 +63,7 @@ public class NdefPushClient {
sock.send(tmpBuffer);
offset += length;
}
+ return true;
} catch (IOException e) {
Log.e(TAG, "couldn't send tag");
if (DBG) Log.d(TAG, "exception:", e);
@@ -80,5 +81,6 @@ public class NdefPushClient {
}
}
}
+ return false;
}
}