diff options
author | Jeff Hamilton <jham@android.com> | 2011-07-22 15:16:58 -0500 |
---|---|---|
committer | Jeff Hamilton <jham@android.com> | 2011-07-26 16:11:15 -0500 |
commit | d2d5dddf17ac2008547172cd72faa034a89d569b (patch) | |
tree | caa6d552626a3285b7518d3895974ba06bdcf893 /src | |
parent | 09a978f1e8869341029913fe8502cdcc9b9dd6a9 (diff) | |
download | packages_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-x | src/com/android/nfc/NdefP2pManager.java | 100 | ||||
-rwxr-xr-x | src/com/android/nfc/NfcService.java | 54 | ||||
-rw-r--r-- | src/com/android/nfc/ScreenshotWindowAnimator.java | 239 | ||||
-rwxr-xr-x | src/com/android/nfc/ndefpush/NdefPushClient.java | 8 |
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; } } |