texts = intent.getCharSequenceArrayListExtra(
+ Intent.EXTRA_TEXT);
+
+ if (uris != null && uris.size() > 0) {
+ for (Uri uri : uris) {
+ if (DBG) Log.d(TAG, "Found uri in ACTION_SEND_MULTIPLE intent.");
+ tryUri(uri);
+ }
+ } else if (texts != null && texts.size() > 0) {
+ // Try EXTRA_TEXT, but just for the first record
+ if (DBG) Log.d(TAG, "Found text in ACTION_SEND_MULTIPLE intent.");
+ tryText(texts.get(0).toString());
+ } else {
+ if (DBG) Log.d(TAG, "Did not find any shareable data in " +
+ "ACTION_SEND_MULTIPLE intent.");
+ }
+ }
+ }
+
+ BeamShareData shareData = null;
+ UserHandle myUserHandle = new UserHandle(UserHandle.myUserId());
+ if (mUris.size() > 0) {
+ // Uris have our first preference for sharing
+ Uri[] uriArray = new Uri[mUris.size()];
+ int numValidUris = 0;
+ for (Uri uri : mUris) {
+ try {
+ grantUriPermission("com.android.nfc", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ uriArray[numValidUris++] = uri;
+ if (DBG) Log.d(TAG, "Found uri: " + uri);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Security exception granting uri permission to NFC process.");
+ numValidUris = 0;
+ break;
+ }
+ }
+ if (numValidUris > 0) {
+ shareData = new BeamShareData(null, uriArray, myUserHandle, 0);
+ } else {
+ // No uris left
+ shareData = new BeamShareData(null, null, myUserHandle, 0);
+ }
+ } else if (mNdefMessage != null) {
+ shareData = new BeamShareData(mNdefMessage, null, myUserHandle, 0);
+ if (DBG) Log.d(TAG, "Created NDEF message:" + mNdefMessage.toString());
+ } else {
+ if (DBG) Log.d(TAG, "Could not find any data to parse.");
+ // Activity may have set something to share over NFC, so pass on anyway
+ shareData = new BeamShareData(null, null, myUserHandle, 0);
+ }
+ mNfcAdapter.invokeBeam(shareData);
+ finish();
+ }
+
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(intent.getAction())) {
+ int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
+ NfcAdapter.STATE_OFF);
+ if (state == NfcAdapter.STATE_ON) {
+ parseShareIntentAndFinish(mLaunchIntent);
+ }
+ }
+ }
+ };
+}
diff --git a/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java b/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java
new file mode 100644
index 0000000..5284796
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java
@@ -0,0 +1,155 @@
+package com.android.nfc;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.widget.Toast;
+
+public class ConfirmConnectToWifiNetworkActivity extends Activity
+ implements View.OnClickListener, DialogInterface.OnDismissListener {
+
+ public static final int ENABLE_WIFI_TIMEOUT_MILLIS = 5000;
+ private WifiConfiguration mCurrentWifiConfiguration;
+ private AlertDialog mAlertDialog;
+ private boolean mEnableWifiInProgress;
+ private Handler mHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ Intent intent = getIntent();
+ mCurrentWifiConfiguration =
+ intent.getParcelableExtra(NfcWifiProtectedSetup.EXTRA_WIFI_CONFIG);
+
+ String printableSsid = mCurrentWifiConfiguration.getPrintableSsid();
+ mAlertDialog = new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT)
+ .setTitle(R.string.title_connect_to_network)
+ .setMessage(
+ String.format(getResources().getString(R.string.prompt_connect_to_network),
+ printableSsid))
+ .setOnDismissListener(this)
+ .setNegativeButton(com.android.internal.R.string.cancel, null)
+ .setPositiveButton(R.string.wifi_connect, null)
+ .create();
+
+ mEnableWifiInProgress = false;
+ mHandler = new Handler();
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ registerReceiver(mBroadcastReceiver, intentFilter);
+
+ mAlertDialog.show();
+
+ super.onCreate(savedInstanceState);
+
+ mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
+ }
+
+
+ @Override
+ public void onClick(View v) {
+ WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+
+ if (!wifiManager.isWifiEnabled()) {
+ wifiManager.setWifiEnabled(true);
+ mEnableWifiInProgress = true;
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (getAndClearEnableWifiInProgress()) {
+ showFailToast();
+ ConfirmConnectToWifiNetworkActivity.this.finish();
+ }
+ }
+ }, ENABLE_WIFI_TIMEOUT_MILLIS);
+
+ } else {
+ doConnect(wifiManager);
+ }
+
+ mAlertDialog.dismiss();
+ }
+
+ private void doConnect(WifiManager wifiManager) {
+ int networkId = wifiManager.addNetwork(mCurrentWifiConfiguration);
+
+ if (networkId < 0) {
+ showFailToast();
+ } else {
+
+ wifiManager.connect(networkId,
+ new WifiManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ Toast.makeText(ConfirmConnectToWifiNetworkActivity.this,
+ R.string.status_wifi_connected, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ showFailToast();
+ }
+ });
+ }
+ }
+
+
+ private void showFailToast() {
+ Toast.makeText(ConfirmConnectToWifiNetworkActivity.this,
+ R.string.status_unable_to_connect, Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ if (!mEnableWifiInProgress) {
+ finish();
+ }
+ }
+
+
+ @Override
+ protected void onDestroy() {
+ ConfirmConnectToWifiNetworkActivity.this.unregisterReceiver(mBroadcastReceiver);
+ super.onDestroy();
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);
+ if (mCurrentWifiConfiguration != null
+ && wifiState == WifiManager.WIFI_STATE_ENABLED) {
+ if (getAndClearEnableWifiInProgress()) {
+ doConnect(
+ (WifiManager) ConfirmConnectToWifiNetworkActivity.this
+ .getSystemService(Context.WIFI_SERVICE));
+ }
+ }
+ }
+ }
+ };
+
+ private boolean getAndClearEnableWifiInProgress() {
+ boolean enableWifiInProgress;
+
+ synchronized (this) {
+ enableWifiInProgress = mEnableWifiInProgress;
+ mEnableWifiInProgress = false;
+ }
+
+ return enableWifiInProgress;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/DeviceHost.java b/NfcSony/src/com/android/nfc/DeviceHost.java
new file mode 100644
index 0000000..21d720e
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/DeviceHost.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 android.annotation.Nullable;
+import android.nfc.NdefMessage;
+import android.os.Bundle;
+
+import java.io.IOException;
+
+public interface DeviceHost {
+ public interface DeviceHostListener {
+ public void onRemoteEndpointDiscovered(TagEndpoint tag);
+
+ /**
+ */
+ public void onHostCardEmulationActivated();
+ public void onHostCardEmulationData(byte[] data);
+ public void onHostCardEmulationDeactivated();
+
+ /**
+ * Notifies P2P Device detected, to activate LLCP link
+ */
+ public void onLlcpLinkActivated(NfcDepEndpoint device);
+
+ /**
+ * Notifies P2P Device detected, to activate LLCP link
+ */
+ public void onLlcpLinkDeactivated(NfcDepEndpoint device);
+
+ public void onLlcpFirstPacketReceived(NfcDepEndpoint device);
+
+ public void onRemoteFieldActivated();
+
+ public void onRemoteFieldDeactivated();
+ }
+
+ public interface TagEndpoint {
+ boolean connect(int technology);
+ boolean reconnect();
+ boolean disconnect();
+
+ boolean presenceCheck();
+ boolean isPresent();
+ void startPresenceChecking(int presenceCheckDelay,
+ @Nullable TagDisconnectedCallback callback);
+
+ int[] getTechList();
+ void removeTechnology(int tech); // TODO remove this one
+ Bundle[] getTechExtras();
+ byte[] getUid();
+ int getHandle();
+
+ byte[] transceive(byte[] data, boolean raw, int[] returnCode);
+
+ boolean checkNdef(int[] out);
+ byte[] readNdef();
+ boolean writeNdef(byte[] data);
+ NdefMessage findAndReadNdef();
+ boolean formatNdef(byte[] key);
+ boolean isNdefFormatable();
+ boolean makeReadOnly();
+
+ int getConnectedTechnology();
+ }
+
+ public interface TagDisconnectedCallback {
+ void onTagDisconnected(long handle);
+ }
+
+ public interface NfceeEndpoint {
+ // TODO flesh out multi-EE and use this
+ }
+
+ public interface NfcDepEndpoint {
+
+ /**
+ * Peer-to-Peer Target
+ */
+ public static final short MODE_P2P_TARGET = 0x00;
+ /**
+ * Peer-to-Peer Initiator
+ */
+ public static final short MODE_P2P_INITIATOR = 0x01;
+ /**
+ * Invalid target mode
+ */
+ public static final short MODE_INVALID = 0xff;
+
+ public byte[] receive();
+
+ public boolean send(byte[] data);
+
+ public boolean connect();
+
+ public boolean disconnect();
+
+ public byte[] transceive(byte[] data);
+
+ public int getHandle();
+
+ public int getMode();
+
+ public byte[] getGeneralBytes();
+
+ public byte getLlcpVersion();
+ }
+
+ public interface LlcpSocket {
+ public void connectToSap(int sap) throws IOException;
+
+ public void connectToService(String serviceName) throws IOException;
+
+ public void close() throws IOException;
+
+ public void send(byte[] data) throws IOException;
+
+ public int receive(byte[] recvBuff) throws IOException;
+
+ public int getRemoteMiu();
+
+ public int getRemoteRw();
+
+ public int getLocalSap();
+
+ public int getLocalMiu();
+
+ public int getLocalRw();
+ }
+
+ public interface LlcpServerSocket {
+ public LlcpSocket accept() throws IOException, LlcpException;
+
+ public void close() throws IOException;
+ }
+
+ public interface LlcpConnectionlessSocket {
+ public int getLinkMiu();
+
+ public int getSap();
+
+ public void send(int sap, byte[] data) throws IOException;
+
+ public LlcpPacket receive() throws IOException;
+
+ public void close() throws IOException;
+ }
+
+ /**
+ * Called at boot if NFC is disabled to give the device host an opportunity
+ * to check the firmware version to see if it needs updating. Normally the firmware version
+ * is checked during {@link #initialize(boolean enableScreenOffSuspend)},
+ * but the firmware may need to be updated after an OTA update.
+ *
+ * This is called from a thread
+ * that may block for long periods of time during the update process.
+ */
+ public void checkFirmware();
+
+ public boolean initialize();
+
+ public boolean deinitialize();
+
+ public String getName();
+
+ public void enableDiscovery(NfcDiscoveryParameters params, boolean restart);
+
+ public void disableDiscovery();
+
+ public boolean sendRawFrame(byte[] data);
+
+ public boolean routeAid(byte[] aid, int route);
+
+ public boolean unrouteAid(byte[] aid);
+
+ public boolean commitRouting();
+
+ public LlcpConnectionlessSocket createLlcpConnectionlessSocket(int nSap, String sn)
+ throws LlcpException;
+
+ public LlcpServerSocket createLlcpServerSocket(int nSap, String sn, int miu,
+ int rw, int linearBufferLength) throws LlcpException;
+
+ public LlcpSocket createLlcpSocket(int sap, int miu, int rw,
+ int linearBufferLength) throws LlcpException;
+
+ public boolean doCheckLlcp();
+
+ public boolean doActivateLlcp();
+
+ public void resetTimeouts();
+
+ public boolean setTimeout(int technology, int timeout);
+
+ public int getTimeout(int technology);
+
+ public void doAbort();
+
+ boolean canMakeReadOnly(int technology);
+
+ int getMaxTransceiveLength(int technology);
+
+ void setP2pInitiatorModes(int modes);
+
+ void setP2pTargetModes(int modes);
+
+ boolean getExtendedLengthApdusSupported();
+
+ int getDefaultLlcpMiu();
+
+ int getDefaultLlcpRwSize();
+
+ String dump();
+
+ boolean enableScreenOffSuspend();
+
+ boolean disableScreenOffSuspend();
+}
diff --git a/NfcSony/src/com/android/nfc/ForegroundUtils.java b/NfcSony/src/com/android/nfc/ForegroundUtils.java
new file mode 100644
index 0000000..0202356
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ForegroundUtils.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014 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 java.util.ArrayList;
+import java.util.List;
+
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.IProcessObserver;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+public class ForegroundUtils extends IProcessObserver.Stub {
+ static final boolean DBG = false;
+ private final String TAG = "ForegroundUtils";
+ private final IActivityManager mIActivityManager;
+
+ private final Object mLock = new Object();
+ // We need to keep track of the individual PIDs per UID,
+ // since a single UID may have multiple processes running
+ // that transition into foreground/background state.
+ private final SparseArray mForegroundUidPids =
+ new SparseArray();
+ private final SparseArray> mBackgroundCallbacks =
+ new SparseArray>();
+
+ private static class Singleton {
+ private static final ForegroundUtils INSTANCE = new ForegroundUtils();
+ }
+
+ private ForegroundUtils() {
+ mIActivityManager = ActivityManagerNative.getDefault();
+ try {
+ mIActivityManager.registerProcessObserver(this);
+ } catch (RemoteException e) {
+ // Should not happen!
+ Log.e(TAG, "ForegroundUtils: could not get IActivityManager");
+ }
+ }
+
+ public interface Callback {
+ void onUidToBackground(int uid);
+ }
+
+ public static ForegroundUtils getInstance() {
+ return Singleton.INSTANCE;
+ }
+
+ /**
+ * Checks whether the specified UID has any activities running in the foreground,
+ * and if it does, registers a callback for when that UID no longer has any foreground
+ * activities. This is done atomically, so callers can be ensured that they will
+ * get a callback if this method returns true.
+ *
+ * @param callback Callback to be called
+ * @param uid The UID to be checked
+ * @return true when the UID has an Activity in the foreground and the callback
+ * , false otherwise
+ */
+ public boolean registerUidToBackgroundCallback(Callback callback, int uid) {
+ synchronized (mLock) {
+ if (!isInForegroundLocked(uid)) {
+ return false;
+ }
+ // This uid is in the foreground; register callback for when it moves
+ // into the background.
+ List callbacks = mBackgroundCallbacks.get(uid, new ArrayList());
+ callbacks.add(callback);
+ mBackgroundCallbacks.put(uid, callbacks);
+ return true;
+ }
+ }
+
+ /**
+ * @param uid The UID to be checked
+ * @return whether the UID has any activities running in the foreground
+ */
+ public boolean isInForeground(int uid) {
+ synchronized (mLock) {
+ return isInForegroundLocked(uid);
+ }
+ }
+
+ /**
+ * @return a list of UIDs currently in the foreground, or an empty list
+ * if none are found.
+ */
+ public List getForegroundUids() {
+ ArrayList uids = new ArrayList(mForegroundUidPids.size());
+ synchronized (mLock) {
+ for (int i = 0; i < mForegroundUidPids.size(); i++) {
+ uids.add(mForegroundUidPids.keyAt(i));
+ }
+ }
+ return uids;
+ }
+
+ private boolean isInForegroundLocked(int uid) {
+ return mForegroundUidPids.get(uid) != null;
+ }
+
+ private void handleUidToBackground(int uid) {
+ ArrayList pendingCallbacks = null;
+ synchronized (mLock) {
+ List callbacks = mBackgroundCallbacks.get(uid);
+ if (callbacks != null) {
+ pendingCallbacks = new ArrayList(callbacks);
+ // Only call them once
+ mBackgroundCallbacks.remove(uid);
+ }
+ }
+ // Release lock for callbacks
+ if (pendingCallbacks != null) {
+ for (Callback callback : pendingCallbacks) {
+ callback.onUidToBackground(uid);
+ }
+ }
+ }
+
+ @Override
+ public void onForegroundActivitiesChanged(int pid, int uid,
+ boolean hasForegroundActivities) throws RemoteException {
+ boolean uidToBackground = false;
+ synchronized (mLock) {
+ SparseBooleanArray foregroundPids = mForegroundUidPids.get(uid,
+ new SparseBooleanArray());
+ if (hasForegroundActivities) {
+ foregroundPids.put(pid, true);
+ } else {
+ foregroundPids.delete(pid);
+ }
+ if (foregroundPids.size() == 0) {
+ mForegroundUidPids.remove(uid);
+ uidToBackground = true;
+ } else {
+ mForegroundUidPids.put(uid, foregroundPids);
+ }
+ }
+ if (uidToBackground) {
+ handleUidToBackground(uid);
+ }
+ if (DBG) {
+ if (DBG) Log.d(TAG, "Foreground changed, PID: " + Integer.toString(pid) + " UID: " +
+ Integer.toString(uid) + " foreground: " +
+ hasForegroundActivities);
+ synchronized (mLock) {
+ Log.d(TAG, "Foreground UID/PID combinations:");
+ for (int i = 0; i < mForegroundUidPids.size(); i++) {
+ int foregroundUid = mForegroundUidPids.keyAt(i);
+ SparseBooleanArray foregroundPids = mForegroundUidPids.get(foregroundUid);
+ if (foregroundPids.size() == 0) {
+ Log.e(TAG, "No PIDS associated with foreground UID!");
+ }
+ for (int j = 0; j < foregroundPids.size(); j++)
+ Log.d(TAG, "UID: " + Integer.toString(foregroundUid) + " PID: " +
+ Integer.toString(foregroundPids.keyAt(j)));
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public void onProcessDied(int pid, int uid) throws RemoteException {
+ if (DBG) Log.d(TAG, "Process died; UID " + Integer.toString(uid) + " PID " +
+ Integer.toString(pid));
+ onForegroundActivitiesChanged(pid, uid, false);
+ }
+
+ @Override
+ public void onProcessStateChanged(int pid, int uid, int procState)
+ throws RemoteException {
+ // Don't care
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/LlcpException.java b/NfcSony/src/com/android/nfc/LlcpException.java
new file mode 100644
index 0000000..9757087
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/LlcpException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 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.nfc.ErrorCodes;
+
+/**
+ * Generic exception thrown in case something unexpected happened during an
+ * LLCP communication.
+ */
+public class LlcpException extends Exception {
+ /**
+ * Constructs a new LlcpException with the current stack trace and the
+ * specified detail message.
+ *
+ * @param s the detail message for this exception.
+ */
+ public LlcpException(String s) {
+ super(s);
+ }
+
+ public LlcpException(int error) {
+ super(ErrorCodes.asString(error));
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/LlcpPacket.java b/NfcSony/src/com/android/nfc/LlcpPacket.java
new file mode 100644
index 0000000..ea27e9f
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/LlcpPacket.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * Represents a LLCP packet received in a LLCP Connectionless communication;
+ */
+public class LlcpPacket {
+
+ private int mRemoteSap;
+
+ private byte[] mDataBuffer;
+
+ public LlcpPacket() {
+
+ }
+
+ /**
+ * Returns the remote Service Access Point number
+ */
+ public int getRemoteSap() {
+ return mRemoteSap;
+ }
+
+ /**
+ * Returns the data buffer
+ */
+ public byte[] getDataBuffer() {
+ return mDataBuffer;
+ }
+
+}
diff --git a/NfcSony/src/com/android/nfc/NfcApplication.java b/NfcSony/src/com/android/nfc/NfcApplication.java
new file mode 100644
index 0000000..33b5cab
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcApplication.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 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.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.Application;
+import android.os.Process;
+import android.os.UserHandle;
+import android.view.HardwareRenderer;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class NfcApplication extends Application {
+
+ static final String TAG = "NfcApplication";
+ static final String NFC_PROCESS = "com.android.nfc";
+
+ NfcService mNfcService;
+
+ public NfcApplication() {
+
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ boolean isMainProcess = false;
+ // We start a service in a separate process to do
+ // handover transfer. We don't want to instantiate an NfcService
+ // object in those cases, hence check the name of the process
+ // to determine whether we're the main NFC service, or the
+ // handover process
+ ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
+ List processes = am.getRunningAppProcesses();
+ Iterator i = processes.iterator();
+ while (i.hasNext()) {
+ RunningAppProcessInfo appInfo = (RunningAppProcessInfo)(i.next());
+ if (appInfo.pid == Process.myPid()) {
+ isMainProcess = (NFC_PROCESS.equals(appInfo.processName));
+ break;
+ }
+ }
+ if (UserHandle.myUserId() == 0 && isMainProcess) {
+ mNfcService = new NfcService(this);
+ HardwareRenderer.enableForegroundTrimming();
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcBackupAgent.java b/NfcSony/src/com/android/nfc/NfcBackupAgent.java
new file mode 100644
index 0000000..27fdf76
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcBackupAgent.java
@@ -0,0 +1,33 @@
+/*
+ * 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.backup.BackupAgentHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+
+public class NfcBackupAgent extends BackupAgentHelper {
+ // Backup identifier
+ static final String SHARED_PREFS_BACKUP_KEY = "shared_prefs";
+
+ @Override
+ public void onCreate() {
+ SharedPreferencesBackupHelper helper =
+ new SharedPreferencesBackupHelper(this, NfcService.PREF);
+ addHelper(SHARED_PREFS_BACKUP_KEY, helper);
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/NfcBootstrapService.java b/NfcSony/src/com/android/nfc/NfcBootstrapService.java
new file mode 100644
index 0000000..1b2b7b1
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcBootstrapService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * This is just a no-op service to allow Nfc app being started at boot time.
+ */
+public class NfcBootstrapService extends Service {
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ stopSelf(startId);
+ return Service.START_NOT_STICKY;
+ }
+
+}
diff --git a/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java b/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java
new file mode 100644
index 0000000..1149836
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * Parameters for enabling NFC tag discovery and polling,
+ * and host card emulation.
+ */
+public final class NfcDiscoveryParameters {
+
+ public static class Builder {
+
+ private NfcDiscoveryParameters mParameters;
+
+ private Builder() {
+ mParameters = new NfcDiscoveryParameters();
+ }
+
+ public NfcDiscoveryParameters.Builder setTechMask(int techMask) {
+ mParameters.mTechMask = techMask;
+ return this;
+ }
+
+ public NfcDiscoveryParameters.Builder setEnableLowPowerDiscovery(boolean enable) {
+ mParameters.mEnableLowPowerDiscovery = enable;
+ return this;
+ }
+
+ public NfcDiscoveryParameters.Builder setEnableReaderMode(boolean enable) {
+ mParameters.mEnableReaderMode = enable;
+
+ if (enable) {
+ mParameters.mEnableLowPowerDiscovery = false;
+ }
+
+ return this;
+ }
+
+ public NfcDiscoveryParameters.Builder setEnableHostRouting(boolean enable) {
+ mParameters.mEnableHostRouting = enable;
+ return this;
+ }
+
+ public NfcDiscoveryParameters.Builder setEnableP2p(boolean enable) {
+ mParameters.mEnableP2p = enable;
+ return this;
+ }
+
+ public NfcDiscoveryParameters build() {
+ if (mParameters.mEnableReaderMode &&
+ (mParameters.mEnableLowPowerDiscovery || mParameters.mEnableP2p)) {
+ throw new IllegalStateException("Can't enable LPTD/P2P and reader mode " +
+ "simultaneously");
+ }
+ return mParameters;
+ }
+ }
+
+ static final int NFC_POLL_DEFAULT = -1;
+
+ // NOTE: when adding a new field, don't forget to update equals() and toString() below
+ private int mTechMask = 0;
+ private boolean mEnableLowPowerDiscovery = true;
+ private boolean mEnableReaderMode = false;
+ private boolean mEnableHostRouting = false;
+ private boolean mEnableP2p = false;
+
+ public NfcDiscoveryParameters() {}
+
+ public int getTechMask() {
+ return mTechMask;
+ }
+
+ public boolean shouldEnableLowPowerDiscovery() {
+ return mEnableLowPowerDiscovery;
+ }
+
+ public boolean shouldEnableReaderMode() {
+ return mEnableReaderMode;
+ }
+
+ public boolean shouldEnableHostRouting() {
+ return mEnableHostRouting;
+ }
+
+ public boolean shouldEnableDiscovery() {
+ return mTechMask != 0 || mEnableHostRouting;
+ }
+
+ public boolean shouldEnableP2p() {
+ return mEnableP2p;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if ((obj == null) || (obj.getClass() != this.getClass())) {
+ return false;
+ }
+ NfcDiscoveryParameters params = (NfcDiscoveryParameters) obj;
+ return mTechMask == params.mTechMask &&
+ (mEnableLowPowerDiscovery == params.mEnableLowPowerDiscovery) &&
+ (mEnableReaderMode == params.mEnableReaderMode) &&
+ (mEnableHostRouting == params.mEnableHostRouting)
+ && (mEnableP2p == params.mEnableP2p);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (mTechMask == NFC_POLL_DEFAULT) {
+ sb.append("mTechMask: default\n");
+ } else {
+ sb.append("mTechMask: " + Integer.toString(mTechMask) + "\n");
+ }
+ sb.append("mEnableLPD: " + Boolean.toString(mEnableLowPowerDiscovery) + "\n");
+ sb.append("mEnableReader: " + Boolean.toString(mEnableReaderMode) + "\n");
+ sb.append("mEnableHostRouting: " + Boolean.toString(mEnableHostRouting) + "\n");
+ sb.append("mEnableP2p: " + Boolean.toString(mEnableP2p));
+ return sb.toString();
+ }
+
+ public static NfcDiscoveryParameters.Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static NfcDiscoveryParameters getDefaultInstance() {
+ return new NfcDiscoveryParameters();
+ }
+
+ public static NfcDiscoveryParameters getNfcOffParameters() {
+ return new NfcDiscoveryParameters();
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcDispatcher.java b/NfcSony/src/com/android/nfc/NfcDispatcher.java
new file mode 100644
index 0000000..53ba0f3
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcDispatcher.java
@@ -0,0 +1,669 @@
+/*
+ * 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.bluetooth.BluetoothAdapter;
+
+import com.android.nfc.RegisteredComponentCache.ComponentInfo;
+import com.android.nfc.handover.HandoverDataParser;
+import com.android.nfc.handover.PeripheralHandoverService;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.tech.Ndef;
+import android.nfc.tech.NfcBarcode;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Dispatch of NFC events to start activities
+ */
+class NfcDispatcher {
+ private static final boolean DBG = false;
+ private static final String TAG = "NfcDispatcher";
+
+ static final int DISPATCH_SUCCESS = 1;
+ static final int DISPATCH_FAIL = 2;
+ static final int DISPATCH_UNLOCK = 3;
+
+ private final Context mContext;
+ private final IActivityManager mIActivityManager;
+ private final RegisteredComponentCache mTechListFilters;
+ private final ContentResolver mContentResolver;
+ private final HandoverDataParser mHandoverDataParser;
+ private final String[] mProvisioningMimes;
+ private final ScreenStateHelper mScreenStateHelper;
+ private final NfcUnlockManager mNfcUnlockManager;
+ private final boolean mDeviceSupportsBluetooth;
+
+ // Locked on this
+ private PendingIntent mOverrideIntent;
+ private IntentFilter[] mOverrideFilters;
+ private String[][] mOverrideTechLists;
+ private boolean mProvisioningOnly;
+
+ NfcDispatcher(Context context,
+ HandoverDataParser handoverDataParser,
+ boolean provisionOnly) {
+ mContext = context;
+ mIActivityManager = ActivityManagerNative.getDefault();
+ mTechListFilters = new RegisteredComponentCache(mContext,
+ NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
+ mContentResolver = context.getContentResolver();
+ mHandoverDataParser = handoverDataParser;
+ mScreenStateHelper = new ScreenStateHelper(context);
+ mNfcUnlockManager = NfcUnlockManager.getInstance();
+ mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null;
+
+ synchronized (this) {
+ mProvisioningOnly = provisionOnly;
+ }
+ String[] provisionMimes = null;
+ if (provisionOnly) {
+ try {
+ // Get accepted mime-types
+ provisionMimes = context.getResources().
+ getStringArray(R.array.provisioning_mime_types);
+ } catch (NotFoundException e) {
+ provisionMimes = null;
+ }
+ }
+ mProvisioningMimes = provisionMimes;
+ }
+
+ public synchronized void setForegroundDispatch(PendingIntent intent,
+ IntentFilter[] filters, String[][] techLists) {
+ if (DBG) Log.d(TAG, "Set Foreground Dispatch");
+ mOverrideIntent = intent;
+ mOverrideFilters = filters;
+ mOverrideTechLists = techLists;
+ }
+
+ public synchronized void disableProvisioningMode() {
+ mProvisioningOnly = false;
+ }
+
+ /**
+ * Helper for re-used objects and methods during a single tag dispatch.
+ */
+ static class DispatchInfo {
+ public final Intent intent;
+
+ final Intent rootIntent;
+ final Uri ndefUri;
+ final String ndefMimeType;
+ final PackageManager packageManager;
+ final Context context;
+
+ public DispatchInfo(Context context, Tag tag, NdefMessage message) {
+ intent = new Intent();
+ intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
+ intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
+ if (message != null) {
+ intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
+ ndefUri = message.getRecords()[0].toUri();
+ ndefMimeType = message.getRecords()[0].toMimeType();
+ } else {
+ ndefUri = null;
+ ndefMimeType = null;
+ }
+
+ rootIntent = new Intent(context, NfcRootActivity.class);
+ rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
+ rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ this.context = context;
+ packageManager = context.getPackageManager();
+ }
+
+ public Intent setNdefIntent() {
+ intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
+ if (ndefUri != null) {
+ intent.setData(ndefUri);
+ return intent;
+ } else if (ndefMimeType != null) {
+ intent.setType(ndefMimeType);
+ return intent;
+ }
+ return null;
+ }
+
+ public Intent setTechIntent() {
+ intent.setData(null);
+ intent.setType(null);
+ intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
+ return intent;
+ }
+
+ public Intent setTagIntent() {
+ intent.setData(null);
+ intent.setType(null);
+ intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
+ return intent;
+ }
+
+ /**
+ * Launch the activity via a (single) NFC root task, so that it
+ * creates a new task stack instead of interfering with any existing
+ * task stack for that activity.
+ * NfcRootActivity acts as the task root, it immediately calls
+ * start activity on the intent it is passed.
+ */
+ boolean tryStartActivity() {
+ // Ideally we'd have used startActivityForResult() to determine whether the
+ // NfcRootActivity was able to launch the intent, but startActivityForResult()
+ // is not available on Context. Instead, we query the PackageManager beforehand
+ // to determine if there is an Activity to handle this intent, and base the
+ // result of off that.
+ List activities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+ ActivityManager.getCurrentUser());
+ if (activities.size() > 0) {
+ context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
+ return true;
+ }
+ return false;
+ }
+
+ boolean tryStartActivity(Intent intentToStart) {
+ List activities = packageManager.queryIntentActivitiesAsUser(
+ intentToStart, 0, ActivityManager.getCurrentUser());
+ if (activities.size() > 0) {
+ rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
+ context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /** Returns:
+ *
+ * DISPATCH_SUCCESS if dispatched to an activity,
+ * DISPATCH_FAIL if no activities were found to dispatch to,
+ * DISPATCH_UNLOCK if the tag was used to unlock the device
+ *
+ */
+ public int dispatchTag(Tag tag) {
+ PendingIntent overrideIntent;
+ IntentFilter[] overrideFilters;
+ String[][] overrideTechLists;
+ boolean provisioningOnly;
+
+ synchronized (this) {
+ overrideFilters = mOverrideFilters;
+ overrideIntent = mOverrideIntent;
+ overrideTechLists = mOverrideTechLists;
+ provisioningOnly = mProvisioningOnly;
+ }
+
+ boolean screenUnlocked = false;
+ if (!provisioningOnly &&
+ mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
+ screenUnlocked = handleNfcUnlock(tag);
+ if (!screenUnlocked) {
+ return DISPATCH_FAIL;
+ }
+ }
+
+ NdefMessage message = null;
+ Ndef ndef = Ndef.get(tag);
+ if (ndef != null) {
+ message = ndef.getCachedNdefMessage();
+ } else {
+ NfcBarcode nfcBarcode = NfcBarcode.get(tag);
+ if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) {
+ message = decodeNfcBarcodeUri(nfcBarcode);
+ }
+ }
+
+ if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
+
+ DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
+
+ resumeAppSwitches();
+
+ if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
+ overrideTechLists)) {
+ return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
+ }
+
+ if (tryPeripheralHandover(message)) {
+ if (DBG) Log.i(TAG, "matched BT HANDOVER");
+ return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
+ }
+
+ if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
+ if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
+ return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
+ }
+
+ if (tryNdef(dispatch, message, provisioningOnly)) {
+ return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
+ }
+
+ if (screenUnlocked) {
+ // We only allow NDEF-based mimeType matching in case of an unlock
+ return DISPATCH_UNLOCK;
+ }
+
+ if (provisioningOnly) {
+ // We only allow NDEF-based mimeType matching
+ return DISPATCH_FAIL;
+ }
+
+ // Only allow NDEF-based mimeType matching for unlock tags
+ if (tryTech(dispatch, tag)) {
+ return DISPATCH_SUCCESS;
+ }
+
+ dispatch.setTagIntent();
+ if (dispatch.tryStartActivity()) {
+ if (DBG) Log.i(TAG, "matched TAG");
+ return DISPATCH_SUCCESS;
+ }
+
+ if (DBG) Log.i(TAG, "no match");
+ return DISPATCH_FAIL;
+ }
+
+ private boolean handleNfcUnlock(Tag tag) {
+ return mNfcUnlockManager.tryUnlock(tag);
+ }
+
+ /**
+ * Checks for the presence of a URL stored in a tag with tech NfcBarcode.
+ * If found, decodes URL and returns NdefMessage message containing an
+ * NdefRecord containing the decoded URL. If not found, returns null.
+ *
+ * URLs are decoded as follows:
+ *
+ * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding
+ * to ISO/IEC 7816-6).
+ * The second byte describes the payload data format. There are four defined data
+ * format values that identify URL data. Depending on the data format value, the
+ * associated prefix is appended to the URL data:
+ *
+ * 0x01: URL with "http://www." prefix
+ * 0x02: URL with "https://www." prefix
+ * 0x03: URL with "http://" prefix
+ * 0x04: URL with "https://" prefix
+ *
+ * Other data format values do not identify URL data and are not handled by this function.
+ * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987.
+ * see http://www.ietf.org/rfc/rfc3987.txt
+ *
+ * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data,
+ * and are therefore not part of the payload. They are ignored in the decoding of a URL.
+ *
+ * The default assumption is that the URL occupies the entire payload of the NfcBarcode
+ * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes)
+ * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal
+ * an early end of the URL. Once this function reaches an early terminator byte 0xfe,
+ * URL decoding stops and the NdefMessage is created and returned. Any payload data after
+ * the first early terminator byte is ignored for the purposes of URL decoding.
+ */
+ private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) {
+ final byte URI_PREFIX_HTTP_WWW = (byte) 0x01; // "http://www."
+ final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www."
+ final byte URI_PREFIX_HTTP = (byte) 0x03; // "http://"
+ final byte URI_PREFIX_HTTPS = (byte) 0x04; // "https://"
+
+ NdefMessage message = null;
+ byte[] tagId = nfcBarcode.getTag().getId();
+ // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes
+ if (tagId.length >= 4
+ && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW
+ || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) {
+ // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to
+ // the end of the full NfcBarcode payload. No terminator means that the URI occupies the
+ // entire length of the payload field. Exclude checking the CRC in the final two bytes
+ // of the NfcBarcode tagId.
+ int end = 2;
+ for (; end < tagId.length - 2; end++) {
+ if (tagId[end] == (byte) 0xfe) {
+ break;
+ }
+ }
+ byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID)
+ System.arraycopy(tagId, 1, payload, 0, payload.length);
+ NdefRecord uriRecord = new NdefRecord(
+ NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload);
+ message = new NdefMessage(uriRecord);
+ }
+ return message;
+ }
+
+ boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
+ IntentFilter[] overrideFilters, String[][] overrideTechLists) {
+ if (overrideIntent == null) {
+ return false;
+ }
+ Intent intent;
+
+ // NDEF
+ if (message != null) {
+ intent = dispatch.setNdefIntent();
+ if (intent != null &&
+ isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
+ try {
+ overrideIntent.send(mContext, Activity.RESULT_OK, intent);
+ if (DBG) Log.i(TAG, "matched NDEF override");
+ return true;
+ } catch (CanceledException e) {
+ return false;
+ }
+ }
+ }
+
+ // TECH
+ intent = dispatch.setTechIntent();
+ if (isTechMatch(tag, overrideTechLists)) {
+ try {
+ overrideIntent.send(mContext, Activity.RESULT_OK, intent);
+ if (DBG) Log.i(TAG, "matched TECH override");
+ return true;
+ } catch (CanceledException e) {
+ return false;
+ }
+ }
+
+ // TAG
+ intent = dispatch.setTagIntent();
+ if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
+ try {
+ overrideIntent.send(mContext, Activity.RESULT_OK, intent);
+ if (DBG) Log.i(TAG, "matched TAG override");
+ return true;
+ } catch (CanceledException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
+ if (filters != null) {
+ for (IntentFilter filter : filters) {
+ if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
+ return true;
+ }
+ }
+ } else if (!hasTechFilter) {
+ return true; // always match if both filters and techlists are null
+ }
+ return false;
+ }
+
+ boolean isTechMatch(Tag tag, String[][] techLists) {
+ if (techLists == null) {
+ return false;
+ }
+
+ String[] tagTechs = tag.getTechList();
+ Arrays.sort(tagTechs);
+ for (String[] filterTechs : techLists) {
+ if (filterMatch(tagTechs, filterTechs)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean tryNdef(DispatchInfo dispatch, NdefMessage message, boolean provisioningOnly) {
+ if (message == null) {
+ return false;
+ }
+ Intent intent = dispatch.setNdefIntent();
+
+ // Bail out if the intent does not contain filterable NDEF data
+ if (intent == null) return false;
+
+ if (provisioningOnly) {
+ if (mProvisioningMimes == null ||
+ !(Arrays.asList(mProvisioningMimes).contains(intent.getType()))) {
+ Log.e(TAG, "Dropping NFC intent in provisioning mode.");
+ return false;
+ }
+ }
+
+ // Try to start AAR activity with matching filter
+ List aarPackages = extractAarPackages(message);
+ for (String pkg : aarPackages) {
+ dispatch.intent.setPackage(pkg);
+ if (dispatch.tryStartActivity()) {
+ if (DBG) Log.i(TAG, "matched AAR to NDEF");
+ return true;
+ }
+ }
+
+ // Try to perform regular launch of the first AAR
+ if (aarPackages.size() > 0) {
+ String firstPackage = aarPackages.get(0);
+ PackageManager pm;
+ try {
+ UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
+ pm = mContext.createPackageContextAsUser("android", 0,
+ currentUser).getPackageManager();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not create user package context");
+ return false;
+ }
+ Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
+ if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent)) {
+ if (DBG) Log.i(TAG, "matched AAR to application launch");
+ return true;
+ }
+ // Find the package in Market:
+ Intent marketIntent = getAppSearchIntent(firstPackage);
+ if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) {
+ if (DBG) Log.i(TAG, "matched AAR to market launch");
+ return true;
+ }
+ }
+
+ // regular launch
+ dispatch.intent.setPackage(null);
+ if (dispatch.tryStartActivity()) {
+ if (DBG) Log.i(TAG, "matched NDEF");
+ return true;
+ }
+
+ return false;
+ }
+
+ static List extractAarPackages(NdefMessage message) {
+ List aarPackages = new LinkedList();
+ for (NdefRecord record : message.getRecords()) {
+ String pkg = checkForAar(record);
+ if (pkg != null) {
+ aarPackages.add(pkg);
+ }
+ }
+ return aarPackages;
+ }
+
+ boolean tryTech(DispatchInfo dispatch, Tag tag) {
+ dispatch.setTechIntent();
+
+ String[] tagTechs = tag.getTechList();
+ Arrays.sort(tagTechs);
+
+ // Standard tech dispatch path
+ ArrayList matches = new ArrayList();
+ List registered = mTechListFilters.getComponents();
+
+ PackageManager pm;
+ try {
+ UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
+ pm = mContext.createPackageContextAsUser("android", 0,
+ currentUser).getPackageManager();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not create user package context");
+ return false;
+ }
+ // Check each registered activity to see if it matches
+ for (ComponentInfo info : registered) {
+ // Don't allow wild card matching
+ if (filterMatch(tagTechs, info.techs) &&
+ isComponentEnabled(pm, info.resolveInfo)) {
+ // Add the activity as a match if it's not already in the list
+ if (!matches.contains(info.resolveInfo)) {
+ matches.add(info.resolveInfo);
+ }
+ }
+ }
+
+ if (matches.size() == 1) {
+ // Single match, launch directly
+ ResolveInfo info = matches.get(0);
+ dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
+ if (dispatch.tryStartActivity()) {
+ if (DBG) Log.i(TAG, "matched single TECH");
+ return true;
+ }
+ dispatch.intent.setComponent(null);
+ } else if (matches.size() > 1) {
+ // Multiple matches, show a custom activity chooser dialog
+ Intent intent = new Intent(mContext, TechListChooserActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
+ intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
+ matches);
+ if (dispatch.tryStartActivity(intent)) {
+ if (DBG) Log.i(TAG, "matched multiple TECH");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean tryPeripheralHandover(NdefMessage m) {
+ if (m == null || !mDeviceSupportsBluetooth) return false;
+
+ if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
+
+ HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m);
+ if (handover == null || !handover.valid) return false;
+
+ Intent intent = new Intent(mContext, PeripheralHandoverService.class);
+ intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
+ intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
+ intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
+ mContext.startServiceAsUser(intent, UserHandle.CURRENT);
+
+ return true;
+ }
+
+
+ /**
+ * Tells the ActivityManager to resume allowing app switches.
+ *
+ * If the current app called stopAppSwitches() then our startActivity() can
+ * be delayed for several seconds. This happens with the default home
+ * screen. As a system service we can override this behavior with
+ * resumeAppSwitches().
+ */
+ void resumeAppSwitches() {
+ try {
+ mIActivityManager.resumeAppSwitches();
+ } catch (RemoteException e) { }
+ }
+
+ /** Returns true if the tech list filter matches the techs on the tag */
+ boolean filterMatch(String[] tagTechs, String[] filterTechs) {
+ if (filterTechs == null || filterTechs.length == 0) return false;
+
+ for (String tech : filterTechs) {
+ if (Arrays.binarySearch(tagTechs, tech) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static String checkForAar(NdefRecord record) {
+ if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
+ Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) {
+ return new String(record.getPayload(), StandardCharsets.US_ASCII);
+ }
+ return null;
+ }
+
+ /**
+ * Returns an intent that can be used to find an application not currently
+ * installed on the device.
+ */
+ static Intent getAppSearchIntent(String pkg) {
+ Intent market = new Intent(Intent.ACTION_VIEW);
+ market.setData(Uri.parse("market://details?id=" + pkg));
+ return market;
+ }
+
+ static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) {
+ boolean enabled = false;
+ ComponentName compname = new ComponentName(
+ info.activityInfo.packageName, info.activityInfo.name);
+ try {
+ // Note that getActivityInfo() will internally call
+ // isEnabledLP() to determine whether the component
+ // enabled. If it's not, null is returned.
+ if (pm.getActivityInfo(compname,0) != null) {
+ enabled = true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ enabled = false;
+ }
+ if (!enabled) {
+ Log.d(TAG, "Component not enabled: " + compname);
+ }
+ return enabled;
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ synchronized (this) {
+ pw.println("mOverrideIntent=" + mOverrideIntent);
+ pw.println("mOverrideFilters=" + mOverrideFilters);
+ pw.println("mOverrideTechLists=" + mOverrideTechLists);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcPermissions.java b/NfcSony/src/com/android/nfc/NfcPermissions.java
new file mode 100644
index 0000000..50adf23
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcPermissions.java
@@ -0,0 +1,35 @@
+package com.android.nfc;
+
+
+import android.content.Context;
+import android.os.UserHandle;
+
+public class NfcPermissions {
+
+ /**
+ * NFC ADMIN permission - only for system apps
+ */
+ private static final String ADMIN_PERM = android.Manifest.permission.WRITE_SECURE_SETTINGS;
+ private static final String ADMIN_PERM_ERROR = "WRITE_SECURE_SETTINGS permission required";
+
+ /**
+ * Regular NFC permission
+ */
+ static final String NFC_PERMISSION = android.Manifest.permission.NFC;
+ private static final String NFC_PERM_ERROR = "NFC permission required";
+
+ public static void validateUserId(int userId) {
+ if (userId != UserHandle.getCallingUserId()) {
+ throw new SecurityException("userId passed in is not the calling user.");
+ }
+ }
+
+ public static void enforceAdminPermissions(Context context) {
+ context.enforceCallingOrSelfPermission(ADMIN_PERM, ADMIN_PERM_ERROR);
+ }
+
+
+ public static void enforceUserPermissions(Context context) {
+ context.enforceCallingOrSelfPermission(NFC_PERMISSION, NFC_PERM_ERROR);
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcRootActivity.java b/NfcSony/src/com/android/nfc/NfcRootActivity.java
new file mode 100644
index 0000000..cc216f2
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcRootActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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.Activity;
+import android.app.ActivityManager;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+public class NfcRootActivity extends Activity {
+
+ static final String EXTRA_LAUNCH_INTENT = "launchIntent";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ if (intent != null && intent.hasExtra(EXTRA_LAUNCH_INTENT)) {
+ final Intent launchIntent = intent.getParcelableExtra(EXTRA_LAUNCH_INTENT);
+ if (launchIntent != null) {
+ try {
+ startActivityAsUser(launchIntent, UserHandle.CURRENT);
+ } catch (ActivityNotFoundException e) {
+ }
+ }
+ }
+ finish();
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcService.java b/NfcSony/src/com/android/nfc/NfcService.java
new file mode 100755
index 0000000..7ab83e5
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcService.java
@@ -0,0 +1,2121 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2010 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.ActivityManager;
+import android.app.Application;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources.NotFoundException;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.nfc.BeamShareData;
+import android.nfc.ErrorCodes;
+import android.nfc.FormatException;
+import android.nfc.IAppCallback;
+import android.nfc.INfcAdapter;
+import android.nfc.INfcAdapterExtras;
+import android.nfc.INfcCardEmulation;
+import android.nfc.INfcTag;
+import android.nfc.INfcUnlockHandler;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.TechListParcel;
+import android.nfc.TransceiveResult;
+import android.nfc.tech.Ndef;
+import android.nfc.tech.TagTechnology;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.nfc.DeviceHost.DeviceHostListener;
+import com.android.nfc.DeviceHost.LlcpConnectionlessSocket;
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.DeviceHost.NfcDepEndpoint;
+import com.android.nfc.DeviceHost.TagEndpoint;
+import com.android.nfc.cardemulation.CardEmulationManager;
+import com.android.nfc.dhimpl.NativeNfcManager;
+import com.android.nfc.handover.HandoverDataParser;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+
+public class NfcService implements DeviceHostListener {
+ static final boolean DBG = false;
+ static final String TAG = "NfcService";
+
+ public static final String SERVICE_NAME = "nfc";
+
+ public static final String PREF = "NfcServicePrefs";
+
+ static final String PREF_NFC_ON = "nfc_on";
+ static final boolean NFC_ON_DEFAULT = true;
+ static final String PREF_NDEF_PUSH_ON = "ndef_push_on";
+ static final boolean NDEF_PUSH_ON_DEFAULT = true;
+ static final String PREF_FIRST_BEAM = "first_beam";
+ static final String PREF_FIRST_BOOT = "first_boot";
+ static final String PREF_AIRPLANE_OVERRIDE = "airplane_override";
+
+ static final int MSG_NDEF_TAG = 0;
+ static final int MSG_LLCP_LINK_ACTIVATION = 1;
+ static final int MSG_LLCP_LINK_DEACTIVATED = 2;
+ static final int MSG_MOCK_NDEF = 3;
+ static final int MSG_LLCP_LINK_FIRST_PACKET = 4;
+ static final int MSG_ROUTE_AID = 5;
+ static final int MSG_UNROUTE_AID = 6;
+ static final int MSG_COMMIT_ROUTING = 7;
+ static final int MSG_INVOKE_BEAM = 8;
+ static final int MSG_RF_FIELD_ACTIVATED = 9;
+ static final int MSG_RF_FIELD_DEACTIVATED = 10;
+ static final int MSG_RESUME_POLLING = 11;
+
+ static final long MAX_POLLING_PAUSE_TIMEOUT = 40000;
+
+ static final int TASK_ENABLE = 1;
+ static final int TASK_DISABLE = 2;
+ static final int TASK_BOOT = 3;
+
+ // Polling technology masks
+ static final int NFC_POLL_A = 0x01;
+ static final int NFC_POLL_B = 0x02;
+ static final int NFC_POLL_F = 0x04;
+ static final int NFC_POLL_ISO15693 = 0x08;
+ static final int NFC_POLL_B_PRIME = 0x10;
+ static final int NFC_POLL_KOVIO = 0x20;
+
+ // minimum screen state that enables NFC polling
+ static final int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;
+
+ // Time to wait for NFC controller to initialize before watchdog
+ // goes off. This time is chosen large, because firmware download
+ // may be a part of initialization.
+ static final int INIT_WATCHDOG_MS = 90000;
+
+ // Time to wait for routing to be applied before watchdog
+ // goes off
+ static final int ROUTING_WATCHDOG_MS = 10000;
+
+ // Default delay used for presence checks
+ static final int DEFAULT_PRESENCE_CHECK_DELAY = 125;
+
+ // The amount of time we wait before manually launching
+ // the Beam animation when called through the share menu.
+ static final int INVOKE_BEAM_DELAY_MS = 1000;
+
+ // RF field events as defined in NFC extras
+ public static final String ACTION_RF_FIELD_ON_DETECTED =
+ "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED";
+ public static final String ACTION_RF_FIELD_OFF_DETECTED =
+ "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED";
+
+ // for use with playSound()
+ public static final int SOUND_START = 0;
+ public static final int SOUND_END = 1;
+ public static final int SOUND_ERROR = 2;
+
+ public static final String ACTION_LLCP_UP =
+ "com.android.nfc.action.LLCP_UP";
+
+ public static final String ACTION_LLCP_DOWN =
+ "com.android.nfc.action.LLCP_DOWN";
+
+ // Timeout to re-apply routing if a tag was present and we postponed it
+ private static final int APPLY_ROUTING_RETRY_TIMEOUT_MS = 5000;
+
+ private final UserManager mUserManager;
+
+ // NFC Execution Environment
+ // fields below are protected by this
+ private final ReaderModeDeathRecipient mReaderModeDeathRecipient =
+ new ReaderModeDeathRecipient();
+ private final NfcUnlockManager mNfcUnlockManager;
+
+ private final NfceeAccessControl mNfceeAccessControl;
+
+ List mInstalledPackages; // cached version of installed packages
+
+ // fields below are used in multiple threads and protected by synchronized(this)
+ final HashMap mObjectMap = new HashMap();
+ int mScreenState;
+ boolean mInProvisionMode; // whether we're in setup wizard and enabled NFC provisioning
+ boolean mIsNdefPushEnabled;
+ NfcDiscoveryParameters mCurrentDiscoveryParameters =
+ NfcDiscoveryParameters.getNfcOffParameters();
+
+ ReaderModeParams mReaderModeParams;
+
+ // mState is protected by this, however it is only modified in onCreate()
+ // and the default AsyncTask thread so it is read unprotected from that
+ // thread
+ int mState; // one of NfcAdapter.STATE_ON, STATE_TURNING_ON, etc
+ // fields below are final after onCreate()
+ Context mContext;
+ private DeviceHost mDeviceHost;
+ private SharedPreferences mPrefs;
+ private SharedPreferences.Editor mPrefsEditor;
+ private PowerManager.WakeLock mRoutingWakeLock;
+
+ int mStartSound;
+ int mEndSound;
+ int mErrorSound;
+ SoundPool mSoundPool; // playback synchronized on this
+ P2pLinkManager mP2pLinkManager;
+ TagService mNfcTagService;
+ NfcAdapterService mNfcAdapter;
+ boolean mIsAirplaneSensitive;
+ boolean mIsAirplaneToggleable;
+ boolean mIsDebugBuild;
+ boolean mIsHceCapable;
+ boolean mPollingPaused;
+
+ private NfcDispatcher mNfcDispatcher;
+ private PowerManager mPowerManager;
+ private KeyguardManager mKeyguard;
+ private HandoverDataParser mHandoverDataParser;
+ private ContentResolver mContentResolver;
+ private CardEmulationManager mCardEmulationManager;
+
+ private ScreenStateHelper mScreenStateHelper;
+ private ForegroundUtils mForegroundUtils;
+
+ private int mUserId;
+ private static NfcService sService;
+
+ public static NfcService getInstance() {
+ return sService;
+ }
+
+ @Override
+ public void onRemoteEndpointDiscovered(TagEndpoint tag) {
+ sendMessage(NfcService.MSG_NDEF_TAG, tag);
+ }
+
+ /**
+ * Notifies transaction
+ */
+ @Override
+ public void onHostCardEmulationActivated() {
+ if (mCardEmulationManager != null) {
+ mCardEmulationManager.onHostCardEmulationActivated();
+ }
+ }
+
+ @Override
+ public void onHostCardEmulationData(byte[] data) {
+ if (mCardEmulationManager != null) {
+ mCardEmulationManager.onHostCardEmulationData(data);
+ }
+ }
+
+ @Override
+ public void onHostCardEmulationDeactivated() {
+ if (mCardEmulationManager != null) {
+ mCardEmulationManager.onHostCardEmulationDeactivated();
+ }
+ }
+
+ /**
+ * Notifies P2P Device detected, to activate LLCP link
+ */
+ @Override
+ public void onLlcpLinkActivated(NfcDepEndpoint device) {
+ sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION, device);
+ }
+
+ /**
+ * Notifies P2P Device detected, to activate LLCP link
+ */
+ @Override
+ public void onLlcpLinkDeactivated(NfcDepEndpoint device) {
+ sendMessage(NfcService.MSG_LLCP_LINK_DEACTIVATED, device);
+ }
+
+ /**
+ * Notifies P2P Device detected, first packet received over LLCP link
+ */
+ @Override
+ public void onLlcpFirstPacketReceived(NfcDepEndpoint device) {
+ sendMessage(NfcService.MSG_LLCP_LINK_FIRST_PACKET, device);
+ }
+
+ @Override
+ public void onRemoteFieldActivated() {
+ sendMessage(NfcService.MSG_RF_FIELD_ACTIVATED, null);
+ }
+
+ @Override
+ public void onRemoteFieldDeactivated() {
+ sendMessage(NfcService.MSG_RF_FIELD_DEACTIVATED, null);
+ }
+
+ final class ReaderModeParams {
+ public int flags;
+ public IAppCallback callback;
+ public int presenceCheckDelay;
+ }
+
+ public NfcService(Application nfcApplication) {
+ mUserId = ActivityManager.getCurrentUser();
+ mContext = nfcApplication;
+
+ mNfcTagService = new TagService();
+ mNfcAdapter = new NfcAdapterService();
+ Log.i(TAG, "Starting NFC service");
+
+ sService = this;
+
+ mScreenStateHelper = new ScreenStateHelper(mContext);
+ mContentResolver = mContext.getContentResolver();
+ mDeviceHost = new NativeNfcManager(mContext, this);
+
+ mNfcUnlockManager = NfcUnlockManager.getInstance();
+
+ mHandoverDataParser = new HandoverDataParser();
+ boolean isNfcProvisioningEnabled = false;
+ try {
+ isNfcProvisioningEnabled = mContext.getResources().getBoolean(
+ R.bool.enable_nfc_provisioning);
+ } catch (NotFoundException e) {
+ }
+
+ if (isNfcProvisioningEnabled) {
+ mInProvisionMode = Settings.Secure.getInt(mContentResolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+ } else {
+ mInProvisionMode = false;
+ }
+
+ mNfcDispatcher = new NfcDispatcher(mContext, mHandoverDataParser, mInProvisionMode);
+ mP2pLinkManager = new P2pLinkManager(mContext, mHandoverDataParser,
+ mDeviceHost.getDefaultLlcpMiu(), mDeviceHost.getDefaultLlcpRwSize());
+
+ mPrefs = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE);
+ mPrefsEditor = mPrefs.edit();
+
+ mNfceeAccessControl = new NfceeAccessControl(mContext);
+
+ mState = NfcAdapter.STATE_OFF;
+ mIsNdefPushEnabled = mPrefs.getBoolean(PREF_NDEF_PUSH_ON, NDEF_PUSH_ON_DEFAULT);
+ setBeamShareActivityState(mIsNdefPushEnabled);
+
+ mIsDebugBuild = "userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE);
+
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+ mRoutingWakeLock = mPowerManager.newWakeLock(
+ PowerManager.PARTIAL_WAKE_LOCK, "NfcService:mRoutingWakeLock");
+
+ mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+ mScreenState = mScreenStateHelper.checkScreenState();
+
+ ServiceManager.addService(SERVICE_NAME, mNfcAdapter);
+
+ // Intents for all users
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ registerForAirplaneMode(filter);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
+
+ IntentFilter ownerFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ ownerFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiver(mOwnerReceiver, ownerFilter);
+
+ ownerFilter = new IntentFilter();
+ ownerFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ ownerFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ ownerFilter.addDataScheme("package");
+ mContext.registerReceiver(mOwnerReceiver, ownerFilter);
+
+ IntentFilter policyFilter = new IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ mContext.registerReceiverAsUser(mPolicyReceiver, UserHandle.ALL, policyFilter, null, null);
+
+ updatePackageCache();
+
+ PackageManager pm = mContext.getPackageManager();
+ mIsHceCapable = pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION);
+ if (mIsHceCapable) {
+ mCardEmulationManager = new CardEmulationManager(mContext);
+ }
+ mForegroundUtils = ForegroundUtils.getInstance();
+ new EnableDisableTask().execute(TASK_BOOT); // do blocking boot tasks
+ }
+
+ void initSoundPool() {
+ synchronized (this) {
+ if (mSoundPool == null) {
+ mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
+ mStartSound = mSoundPool.load(mContext, R.raw.start, 1);
+ mEndSound = mSoundPool.load(mContext, R.raw.end, 1);
+ mErrorSound = mSoundPool.load(mContext, R.raw.error, 1);
+ }
+ }
+ }
+
+ void releaseSoundPool() {
+ synchronized (this) {
+ if (mSoundPool != null) {
+ mSoundPool.release();
+ mSoundPool = null;
+ }
+ }
+ }
+
+ void registerForAirplaneMode(IntentFilter filter) {
+ final String airplaneModeRadios = Settings.System.getString(mContentResolver,
+ Settings.Global.AIRPLANE_MODE_RADIOS);
+ final String toggleableRadios = Settings.System.getString(mContentResolver,
+ Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+
+ mIsAirplaneSensitive = airplaneModeRadios == null ? true :
+ airplaneModeRadios.contains(Settings.Global.RADIO_NFC);
+ mIsAirplaneToggleable = toggleableRadios == null ? false :
+ toggleableRadios.contains(Settings.Global.RADIO_NFC);
+
+ if (mIsAirplaneSensitive) {
+ filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ }
+ }
+
+ void updatePackageCache() {
+ PackageManager pm = mContext.getPackageManager();
+ List packages = pm.getInstalledPackages(0, UserHandle.USER_OWNER);
+ synchronized (this) {
+ mInstalledPackages = packages;
+ }
+ }
+
+ boolean isSecHal() {
+ String nfcSecHal = SystemProperties.get("ro.nfc.sec_hal");
+ if (!TextUtils.isEmpty(nfcSecHal)) {
+ Log.i(TAG, "This device uses SEC NFC CHIP.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Manages tasks that involve turning on/off the NFC controller.
+ *
+ * All work that might turn the NFC adapter on or off must be done
+ * through this task, to keep the handling of mState simple.
+ * In other words, mState is only modified in these tasks (and we
+ * don't need a lock to read it in these tasks).
+ *
+ * These tasks are all done on the same AsyncTask background
+ * thread, so they are serialized. Each task may temporarily transition
+ * mState to STATE_TURNING_OFF or STATE_TURNING_ON, but must exit in
+ * either STATE_ON or STATE_OFF. This way each task can be guaranteed
+ * of starting in either STATE_OFF or STATE_ON, without needing to hold
+ * NfcService.this for the entire task.
+ *
+ * AsyncTask's are also implicitly queued. This is useful for corner
+ * cases like turning airplane mode on while TASK_ENABLE is in progress.
+ * The TASK_DISABLE triggered by airplane mode will be correctly executed
+ * immediately after TASK_ENABLE is complete. This seems like the most sane
+ * way to deal with these situations.
+ *
+ * {@link #TASK_ENABLE} enables the NFC adapter, without changing
+ * preferences
+ *
{@link #TASK_DISABLE} disables the NFC adapter, without changing
+ * preferences
+ *
{@link #TASK_BOOT} does first boot work and may enable NFC
+ */
+ class EnableDisableTask extends AsyncTask {
+ @Override
+ protected Void doInBackground(Integer... params) {
+ // Sanity check mState
+ switch (mState) {
+ case NfcAdapter.STATE_TURNING_OFF:
+ case NfcAdapter.STATE_TURNING_ON:
+ Log.e(TAG, "Processing EnableDisable task " + params[0] + " from bad state " +
+ mState);
+ return null;
+ }
+
+ /* AsyncTask sets this thread to THREAD_PRIORITY_BACKGROUND,
+ * override with the default. THREAD_PRIORITY_BACKGROUND causes
+ * us to service software I2C too slow for firmware download
+ * with the NXP PN544.
+ * TODO: move this to the DAL I2C layer in libnfc-nxp, since this
+ * problem only occurs on I2C platforms using PN544
+ */
+ Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+
+ switch (params[0].intValue()) {
+ case TASK_ENABLE:
+ enableInternal();
+ break;
+ case TASK_DISABLE:
+ disableInternal();
+ break;
+ case TASK_BOOT:
+ Log.d(TAG, "checking on firmware download");
+ boolean airplaneOverride = mPrefs.getBoolean(PREF_AIRPLANE_OVERRIDE, false);
+ if (mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT) &&
+ (!mIsAirplaneSensitive || !isAirplaneModeOn() || airplaneOverride)) {
+ Log.d(TAG, "NFC is on. Doing normal stuff");
+ enableInternal();
+ } else if (!isSecHal()) {
+ Log.d(TAG, "NFC is off. Checking firmware version");
+ mDeviceHost.checkFirmware();
+ }
+ if (mPrefs.getBoolean(PREF_FIRST_BOOT, true)) {
+ Log.i(TAG, "First Boot");
+ mPrefsEditor.putBoolean(PREF_FIRST_BOOT, false);
+ mPrefsEditor.apply();
+ }
+ break;
+ }
+
+ // Restore default AsyncTask priority
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ return null;
+ }
+
+ /**
+ * Enable NFC adapter functions.
+ * Does not toggle preferences.
+ */
+ boolean enableInternal() {
+ if (mState == NfcAdapter.STATE_ON) {
+ return true;
+ }
+ Log.i(TAG, "Enabling NFC");
+ updateState(NfcAdapter.STATE_TURNING_ON);
+
+ WatchDogThread watchDog = new WatchDogThread("enableInternal", INIT_WATCHDOG_MS);
+ watchDog.start();
+ try {
+ mRoutingWakeLock.acquire();
+ try {
+ if (!mDeviceHost.initialize()) {
+ Log.w(TAG, "Error enabling NFC");
+ updateState(NfcAdapter.STATE_OFF);
+ return false;
+ }
+ } finally {
+ mRoutingWakeLock.release();
+ }
+ } finally {
+ watchDog.cancel();
+ }
+
+ if (mIsHceCapable) {
+ // Generate the initial card emulation routing table
+ mCardEmulationManager.onNfcEnabled();
+ }
+
+ synchronized (NfcService.this) {
+ mObjectMap.clear();
+ mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true);
+ updateState(NfcAdapter.STATE_ON);
+ }
+
+ initSoundPool();
+
+ /* Start polling loop */
+
+ applyRouting(true);
+ return true;
+ }
+
+ /**
+ * Disable all NFC adapter functions.
+ * Does not toggle preferences.
+ */
+ boolean disableInternal() {
+ if (mState == NfcAdapter.STATE_OFF) {
+ return true;
+ }
+ Log.i(TAG, "Disabling NFC");
+ updateState(NfcAdapter.STATE_TURNING_OFF);
+
+ /* Sometimes mDeviceHost.deinitialize() hangs, use a watch-dog.
+ * Implemented with a new thread (instead of a Handler or AsyncTask),
+ * because the UI Thread and AsyncTask thread-pools can also get hung
+ * when the NFC controller stops responding */
+ WatchDogThread watchDog = new WatchDogThread("disableInternal", ROUTING_WATCHDOG_MS);
+ watchDog.start();
+
+ if (mIsHceCapable) {
+ mCardEmulationManager.onNfcDisabled();
+ }
+
+ mP2pLinkManager.enableDisable(false, false);
+
+ // Stop watchdog if tag present
+ // A convenient way to stop the watchdog properly consists of
+ // disconnecting the tag. The polling loop shall be stopped before
+ // to avoid the tag being discovered again.
+ maybeDisconnectTarget();
+
+ mNfcDispatcher.setForegroundDispatch(null, null, null);
+
+
+ boolean result = mDeviceHost.deinitialize();
+ if (DBG) Log.d(TAG, "mDeviceHost.deinitialize() = " + result);
+
+ watchDog.cancel();
+
+ synchronized (NfcService.this) {
+ mCurrentDiscoveryParameters = NfcDiscoveryParameters.getNfcOffParameters();
+ updateState(NfcAdapter.STATE_OFF);
+ }
+
+ releaseSoundPool();
+
+ return result;
+ }
+
+ void updateState(int newState) {
+ synchronized (NfcService.this) {
+ if (newState == mState) {
+ return;
+ }
+ mState = newState;
+ Intent intent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, mState);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+ }
+ }
+
+ void saveNfcOnSetting(boolean on) {
+ synchronized (NfcService.this) {
+ mPrefsEditor.putBoolean(PREF_NFC_ON, on);
+ mPrefsEditor.apply();
+ }
+ }
+
+ public void playSound(int sound) {
+ synchronized (this) {
+ if (mSoundPool == null) {
+ Log.w(TAG, "Not playing sound when NFC is disabled");
+ return;
+ }
+ switch (sound) {
+ case SOUND_START:
+ mSoundPool.play(mStartSound, 1.0f, 1.0f, 0, 0, 1.0f);
+ break;
+ case SOUND_END:
+ mSoundPool.play(mEndSound, 1.0f, 1.0f, 0, 0, 1.0f);
+ break;
+ case SOUND_ERROR:
+ mSoundPool.play(mErrorSound, 1.0f, 1.0f, 0, 0, 1.0f);
+ break;
+ }
+ }
+ }
+
+ synchronized int getUserId() {
+ return mUserId;
+ }
+
+ void setBeamShareActivityState(boolean enabled) {
+ UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ // Propagate the state change to all user profiles related to the current
+ // user. Note that the list returned by getUserProfiles contains the
+ // current user.
+ List luh = um.getUserProfiles();
+ for (UserHandle uh : luh){
+ enforceBeamShareActivityPolicy(mContext, uh, enabled);
+ }
+ }
+
+ void enforceBeamShareActivityPolicy(Context context, UserHandle uh,
+ boolean isGlobalEnabled){
+ UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ IPackageManager mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ boolean isActiveForUser =
+ (!um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM, uh)) &&
+ isGlobalEnabled;
+ if (DBG){
+ Log.d(TAG, "Enforcing a policy change on user: " + uh +
+ ", isActiveForUser = " + isActiveForUser);
+ }
+ try {
+ mIpm.setComponentEnabledSetting(new ComponentName(
+ BeamShareActivity.class.getPackageName$(),
+ BeamShareActivity.class.getName()),
+ isActiveForUser ?
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP,
+ uh.getIdentifier());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to change Beam status for user " + uh);
+ }
+ }
+
+ final class NfcAdapterService extends INfcAdapter.Stub {
+ /**
+ * An interface for vendor specific extensions
+ */
+ public IBinder getNfcAdapterVendorInterface(String vendor) {
+ return null;
+ }
+
+ @Override
+ public boolean enable() throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ saveNfcOnSetting(true);
+
+ if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ if (!mIsAirplaneToggleable) {
+ Log.i(TAG, "denying enable() request (airplane mode)");
+ return false;
+ }
+ // Make sure the override survives a reboot
+ mPrefsEditor.putBoolean(PREF_AIRPLANE_OVERRIDE, true);
+ mPrefsEditor.apply();
+ }
+ new EnableDisableTask().execute(TASK_ENABLE);
+
+ return true;
+ }
+
+ @Override
+ public boolean disable(boolean saveState) throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ if (saveState) {
+ saveNfcOnSetting(false);
+ }
+
+ new EnableDisableTask().execute(TASK_DISABLE);
+
+ return true;
+ }
+
+ @Override
+ public void pausePolling(int timeoutInMs) {
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ if (timeoutInMs <= 0 || timeoutInMs > MAX_POLLING_PAUSE_TIMEOUT) {
+ Log.e(TAG, "Refusing to pause polling for " + timeoutInMs + "ms.");
+ return;
+ }
+
+ synchronized (NfcService.this) {
+ mPollingPaused = true;
+ mDeviceHost.disableDiscovery();
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_RESUME_POLLING), timeoutInMs);
+ }
+ }
+
+ @Override
+ public void resumePolling() {
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ synchronized (NfcService.this) {
+ if (!mPollingPaused) {
+ return;
+ }
+
+ mHandler.removeMessages(MSG_RESUME_POLLING);
+ mPollingPaused = false;
+ new ApplyRoutingTask().execute();
+ }
+ }
+
+ @Override
+ public boolean isNdefPushEnabled() throws RemoteException {
+ synchronized (NfcService.this) {
+ return mState == NfcAdapter.STATE_ON && mIsNdefPushEnabled;
+ }
+ }
+
+ @Override
+ public boolean enableNdefPush() throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ synchronized (NfcService.this) {
+ if (mIsNdefPushEnabled) {
+ return true;
+ }
+ Log.i(TAG, "enabling NDEF Push");
+ mPrefsEditor.putBoolean(PREF_NDEF_PUSH_ON, true);
+ mPrefsEditor.apply();
+ mIsNdefPushEnabled = true;
+ setBeamShareActivityState(true);
+ if (isNfcEnabled()) {
+ mP2pLinkManager.enableDisable(true, true);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean disableNdefPush() throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ synchronized (NfcService.this) {
+ if (!mIsNdefPushEnabled) {
+ return true;
+ }
+ Log.i(TAG, "disabling NDEF Push");
+ mPrefsEditor.putBoolean(PREF_NDEF_PUSH_ON, false);
+ mPrefsEditor.apply();
+ mIsNdefPushEnabled = false;
+ setBeamShareActivityState(false);
+ if (isNfcEnabled()) {
+ mP2pLinkManager.enableDisable(false, true);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void setForegroundDispatch(PendingIntent intent,
+ IntentFilter[] filters, TechListParcel techListsParcel) {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ // Short-cut the disable path
+ if (intent == null && filters == null && techListsParcel == null) {
+ mNfcDispatcher.setForegroundDispatch(null, null, null);
+ return;
+ }
+
+ // Validate the IntentFilters
+ if (filters != null) {
+ if (filters.length == 0) {
+ filters = null;
+ } else {
+ for (IntentFilter filter : filters) {
+ if (filter == null) {
+ throw new IllegalArgumentException("null IntentFilter");
+ }
+ }
+ }
+ }
+
+ // Validate the tech lists
+ String[][] techLists = null;
+ if (techListsParcel != null) {
+ techLists = techListsParcel.getTechLists();
+ }
+
+ mNfcDispatcher.setForegroundDispatch(intent, filters, techLists);
+ }
+
+
+ @Override
+ public void setAppCallback(IAppCallback callback) {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ // don't allow Beam for managed profiles, or devices with a device owner or policy owner
+ UserInfo userInfo = mUserManager.getUserInfo(UserHandle.getCallingUserId());
+ if(!mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_OUTGOING_BEAM, userInfo.getUserHandle())) {
+ mP2pLinkManager.setNdefCallback(callback, Binder.getCallingUid());
+ } else if (DBG) {
+ Log.d(TAG, "Disabling default Beam behavior");
+ }
+ }
+
+ @Override
+ public void verifyNfcPermission() {
+ NfcPermissions.enforceUserPermissions(mContext);
+ }
+
+ @Override
+ public void invokeBeam() {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ if (mForegroundUtils.isInForeground(Binder.getCallingUid())) {
+ mP2pLinkManager.onManualBeamInvoke(null);
+ } else {
+ Log.e(TAG, "Calling activity not in foreground.");
+ }
+ }
+
+ @Override
+ public void invokeBeamInternal(BeamShareData shareData) {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ Message msg = Message.obtain();
+ msg.what = MSG_INVOKE_BEAM;
+ msg.obj = shareData;
+ // We have to send this message delayed for two reasons:
+ // 1) This is an IPC call from BeamShareActivity, which is
+ // running when the user has invoked Beam through the
+ // share menu. As soon as BeamShareActivity closes, the UI
+ // will need some time to rebuild the original Activity.
+ // Waiting here for a while gives a better chance of the UI
+ // having been rebuilt, which means the screenshot that the
+ // Beam animation is using will be more accurate.
+ // 2) Similarly, because the Activity that launched BeamShareActivity
+ // with an ACTION_SEND intent is now in paused state, the NDEF
+ // callbacks that it has registered may no longer be valid.
+ // Allowing the original Activity to resume will make sure we
+ // it has a chance to re-register the NDEF message / callback,
+ // so we share the right data.
+ //
+ // Note that this is somewhat of a hack because the delay may not actually
+ // be long enough for 2) on very slow devices, but there's no better
+ // way to do this right now without additional framework changes.
+ mHandler.sendMessageDelayed(msg, INVOKE_BEAM_DELAY_MS);
+ }
+
+ @Override
+ public INfcTag getNfcTagInterface() throws RemoteException {
+ return mNfcTagService;
+ }
+
+ @Override
+ public INfcCardEmulation getNfcCardEmulationInterface() {
+ if (mIsHceCapable) {
+ return mCardEmulationManager.getNfcCardEmulationInterface();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getState() throws RemoteException {
+ synchronized (NfcService.this) {
+ return mState;
+ }
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ NfcService.this.dump(fd, pw, args);
+ }
+
+ @Override
+ public void dispatch(Tag tag) throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ mNfcDispatcher.dispatchTag(tag);
+ }
+
+ @Override
+ public void setP2pModes(int initiatorModes, int targetModes) throws RemoteException {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ mDeviceHost.setP2pInitiatorModes(initiatorModes);
+ mDeviceHost.setP2pTargetModes(targetModes);
+ applyRouting(true);
+ }
+
+ @Override
+ public void setReaderMode(IBinder binder, IAppCallback callback, int flags, Bundle extras)
+ throws RemoteException {
+ synchronized (NfcService.this) {
+ if (!isNfcEnabled()) {
+ Log.e(TAG, "setReaderMode() called while NFC is not enabled.");
+ return;
+ }
+ if (flags != 0) {
+ try {
+ mReaderModeParams = new ReaderModeParams();
+ mReaderModeParams.callback = callback;
+ mReaderModeParams.flags = flags;
+ mReaderModeParams.presenceCheckDelay = extras != null
+ ? (extras.getInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY,
+ DEFAULT_PRESENCE_CHECK_DELAY))
+ : DEFAULT_PRESENCE_CHECK_DELAY;
+ binder.linkToDeath(mReaderModeDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote binder has already died.");
+ return;
+ }
+ } else {
+ try {
+ mReaderModeParams = null;
+ binder.unlinkToDeath(mReaderModeDeathRecipient, 0);
+ } catch (NoSuchElementException e) {
+ Log.e(TAG, "Reader mode Binder was never registered.");
+ }
+ }
+ applyRouting(false);
+ }
+ }
+
+ @Override
+ public INfcAdapterExtras getNfcAdapterExtrasInterface(String pkg) throws RemoteException {
+ // nfc-extras implementation is no longer present in AOSP.
+ return null;
+ }
+
+ @Override
+ public void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, int[] techList) {
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ int lockscreenPollMask = computeLockscreenPollMask(techList);
+ synchronized (NfcService.this) {
+ mNfcUnlockManager.addUnlockHandler(unlockHandler, lockscreenPollMask);
+ }
+
+ applyRouting(false);
+ }
+
+ @Override
+ public void removeNfcUnlockHandler(INfcUnlockHandler token) throws RemoteException {
+ synchronized (NfcService.this) {
+ mNfcUnlockManager.removeUnlockHandler(token.asBinder());
+ }
+
+ applyRouting(false);
+ }
+
+ private int computeLockscreenPollMask(int[] techList) {
+
+ Map techCodeToMask = new HashMap();
+
+ techCodeToMask.put(TagTechnology.NFC_A, NfcService.NFC_POLL_A);
+ techCodeToMask.put(TagTechnology.NFC_B, NfcService.NFC_POLL_B);
+ techCodeToMask.put(TagTechnology.NFC_V, NfcService.NFC_POLL_ISO15693);
+ techCodeToMask.put(TagTechnology.NFC_F, NfcService.NFC_POLL_F);
+ techCodeToMask.put(TagTechnology.NFC_BARCODE, NfcService.NFC_POLL_KOVIO);
+
+ int mask = 0;
+
+ for (int i = 0; i < techList.length; i++) {
+ if (techCodeToMask.containsKey(techList[i])) {
+ mask |= techCodeToMask.get(techList[i]).intValue();
+ }
+ }
+
+ return mask;
+ }
+ }
+
+ final class ReaderModeDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ synchronized (NfcService.this) {
+ if (mReaderModeParams != null) {
+ mReaderModeParams = null;
+ applyRouting(false);
+ }
+ }
+ }
+ }
+
+ final class TagService extends INfcTag.Stub {
+ @Override
+ public int close(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ /* Remove the device from the hmap */
+ unregisterObject(nativeHandle);
+ tag.disconnect();
+ return ErrorCodes.SUCCESS;
+ }
+ /* Restart polling loop for notification */
+ applyRouting(true);
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+
+ @Override
+ public int connect(int nativeHandle, int technology) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag == null) {
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+
+ if (!tag.isPresent()) {
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+
+ // Note that on most tags, all technologies are behind a single
+ // handle. This means that the connect at the lower levels
+ // will do nothing, as the tag is already connected to that handle.
+ if (tag.connect(technology)) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+ }
+
+ @Override
+ public int reconnect(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ if (tag.reconnect()) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+ }
+ return ErrorCodes.ERROR_DISCONNECT;
+ }
+
+ @Override
+ public int[] getTechList(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return null;
+ }
+
+ /* find the tag in the hmap */
+ TagEndpoint tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ return tag.getTechList();
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isPresent(int nativeHandle) throws RemoteException {
+ TagEndpoint tag = null;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return false;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag == null) {
+ return false;
+ }
+
+ return tag.isPresent();
+ }
+
+ @Override
+ public boolean isNdef(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return false;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ int[] ndefInfo = new int[2];
+ if (tag == null) {
+ return false;
+ }
+ return tag.checkNdef(ndefInfo);
+ }
+
+ @Override
+ public TransceiveResult transceive(int nativeHandle, byte[] data, boolean raw)
+ throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+ byte[] response;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return null;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ // Check if length is within limits
+ if (data.length > getMaxTransceiveLength(tag.getConnectedTechnology())) {
+ return new TransceiveResult(TransceiveResult.RESULT_EXCEEDED_LENGTH, null);
+ }
+ int[] targetLost = new int[1];
+ response = tag.transceive(data, raw, targetLost);
+ int result;
+ if (response != null) {
+ result = TransceiveResult.RESULT_SUCCESS;
+ } else if (targetLost[0] == 1) {
+ result = TransceiveResult.RESULT_TAGLOST;
+ } else {
+ result = TransceiveResult.RESULT_FAILURE;
+ }
+ return new TransceiveResult(result, response);
+ }
+ return null;
+ }
+
+ @Override
+ public NdefMessage ndefRead(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return null;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ byte[] buf = tag.readNdef();
+ if (buf == null) {
+ return null;
+ }
+
+ /* Create an NdefMessage */
+ try {
+ return new NdefMessage(buf);
+ } catch (FormatException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public int ndefWrite(int nativeHandle, NdefMessage msg) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag == null) {
+ return ErrorCodes.ERROR_IO;
+ }
+
+ if (msg == null) return ErrorCodes.ERROR_INVALID_PARAM;
+
+ if (tag.writeNdef(msg.toByteArray())) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_IO;
+ }
+
+ }
+
+ @Override
+ public boolean ndefIsWritable(int nativeHandle) throws RemoteException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int ndefMakeReadOnly(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag == null) {
+ return ErrorCodes.ERROR_IO;
+ }
+
+ if (tag.makeReadOnly()) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_IO;
+ }
+ }
+
+ @Override
+ public int formatNdef(int nativeHandle, byte[] key) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return ErrorCodes.ERROR_NOT_INITIALIZED;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag == null) {
+ return ErrorCodes.ERROR_IO;
+ }
+
+ if (tag.formatNdef(key)) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_IO;
+ }
+ }
+
+ @Override
+ public Tag rediscover(int nativeHandle) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ TagEndpoint tag = null;
+
+ // Check if NFC is enabled
+ if (!isNfcEnabled()) {
+ return null;
+ }
+
+ /* find the tag in the hmap */
+ tag = (TagEndpoint) findObject(nativeHandle);
+ if (tag != null) {
+ // For now the prime usecase for rediscover() is to be able
+ // to access the NDEF technology after formatting without
+ // having to remove the tag from the field, or similar
+ // to have access to NdefFormatable in case low-level commands
+ // were used to remove NDEF. So instead of doing a full stack
+ // rediscover (which is poorly supported at the moment anyway),
+ // we simply remove these two technologies and detect them
+ // again.
+ tag.removeTechnology(TagTechnology.NDEF);
+ tag.removeTechnology(TagTechnology.NDEF_FORMATABLE);
+ tag.findAndReadNdef();
+ // Build a new Tag object to return
+ Tag newTag = new Tag(tag.getUid(), tag.getTechList(),
+ tag.getTechExtras(), tag.getHandle(), this);
+ return newTag;
+ }
+ return null;
+ }
+
+ @Override
+ public int setTimeout(int tech, int timeout) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+ boolean success = mDeviceHost.setTimeout(tech, timeout);
+ if (success) {
+ return ErrorCodes.SUCCESS;
+ } else {
+ return ErrorCodes.ERROR_INVALID_PARAM;
+ }
+ }
+
+ @Override
+ public int getTimeout(int tech) throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ return mDeviceHost.getTimeout(tech);
+ }
+
+ @Override
+ public void resetTimeouts() throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+
+ mDeviceHost.resetTimeouts();
+ }
+
+ @Override
+ public boolean canMakeReadOnly(int ndefType) throws RemoteException {
+ return mDeviceHost.canMakeReadOnly(ndefType);
+ }
+
+ @Override
+ public int getMaxTransceiveLength(int tech) throws RemoteException {
+ return mDeviceHost.getMaxTransceiveLength(tech);
+ }
+
+ @Override
+ public boolean getExtendedLengthApdusSupported() throws RemoteException {
+ return mDeviceHost.getExtendedLengthApdusSupported();
+ }
+ }
+
+ boolean isNfcEnabledOrShuttingDown() {
+ synchronized (this) {
+ return (mState == NfcAdapter.STATE_ON || mState == NfcAdapter.STATE_TURNING_OFF);
+ }
+ }
+
+ boolean isNfcEnabled() {
+ synchronized (this) {
+ return mState == NfcAdapter.STATE_ON;
+ }
+ }
+
+ class WatchDogThread extends Thread {
+ final Object mCancelWaiter = new Object();
+ final int mTimeout;
+ boolean mCanceled = false;
+
+ public WatchDogThread(String threadName, int timeout) {
+ super(threadName);
+ mTimeout = timeout;
+ }
+
+ @Override
+ public void run() {
+ try {
+ synchronized (mCancelWaiter) {
+ mCancelWaiter.wait(mTimeout);
+ if (mCanceled) {
+ return;
+ }
+ }
+ } catch (InterruptedException e) {
+ // Should not happen; fall-through to abort.
+ Log.w(TAG, "Watchdog thread interruped.");
+ interrupt();
+ }
+ Log.e(TAG, "Watchdog triggered, aborting.");
+ mDeviceHost.doAbort();
+ }
+
+ public synchronized void cancel() {
+ synchronized (mCancelWaiter) {
+ mCanceled = true;
+ mCancelWaiter.notify();
+ }
+ }
+ }
+
+ static byte[] hexStringToBytes(String s) {
+ if (s == null || s.length() == 0) return null;
+ int len = s.length();
+ if (len % 2 != 0) {
+ s = '0' + s;
+ len++;
+ }
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ + Character.digit(s.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
+ /**
+ * Read mScreenState and apply NFC-C polling and NFC-EE routing
+ */
+ void applyRouting(boolean force) {
+ synchronized (this) {
+ if (!isNfcEnabledOrShuttingDown()) {
+ return;
+ }
+ WatchDogThread watchDog = new WatchDogThread("applyRouting", ROUTING_WATCHDOG_MS);
+ if (mInProvisionMode) {
+ mInProvisionMode = Settings.Secure.getInt(mContentResolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+ if (!mInProvisionMode) {
+ // Notify dispatcher it's fine to dispatch to any package now
+ // and allow handover transfers.
+ mNfcDispatcher.disableProvisioningMode();
+ }
+ }
+ // Special case: if we're transitioning to unlocked state while
+ // still talking to a tag, postpone re-configuration.
+ if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && isTagPresent()) {
+ Log.d(TAG, "Not updating discovery parameters, tag connected.");
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESUME_POLLING),
+ APPLY_ROUTING_RETRY_TIMEOUT_MS);
+ return;
+ }
+
+ try {
+ watchDog.start();
+ // Compute new polling parameters
+ NfcDiscoveryParameters newParams = computeDiscoveryParameters(mScreenState);
+ if (force || !newParams.equals(mCurrentDiscoveryParameters)) {
+ if (newParams.shouldEnableDiscovery()) {
+ boolean shouldRestart = mCurrentDiscoveryParameters.shouldEnableDiscovery();
+ mDeviceHost.enableDiscovery(newParams, shouldRestart);
+ } else {
+ mDeviceHost.disableDiscovery();
+ }
+ mCurrentDiscoveryParameters = newParams;
+ } else {
+ Log.d(TAG, "Discovery configuration equal, not updating.");
+ }
+ } finally {
+ watchDog.cancel();
+ }
+ }
+ }
+
+ private NfcDiscoveryParameters computeDiscoveryParameters(int screenState) {
+ // Recompute discovery parameters based on screen state
+ NfcDiscoveryParameters.Builder paramsBuilder = NfcDiscoveryParameters.newBuilder();
+ // Polling
+ if (screenState >= NFC_POLLING_MODE) {
+ // Check if reader-mode is enabled
+ if (mReaderModeParams != null) {
+ int techMask = 0;
+ if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_A) != 0)
+ techMask |= NFC_POLL_A;
+ if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_B) != 0)
+ techMask |= NFC_POLL_B;
+ if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_F) != 0)
+ techMask |= NFC_POLL_F;
+ if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_V) != 0)
+ techMask |= NFC_POLL_ISO15693;
+ if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_BARCODE) != 0)
+ techMask |= NFC_POLL_KOVIO;
+
+ paramsBuilder.setTechMask(techMask);
+ paramsBuilder.setEnableReaderMode(true);
+ } else {
+ paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT);
+ paramsBuilder.setEnableP2p(mIsNdefPushEnabled);
+ }
+ } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED && mInProvisionMode) {
+ paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT);
+ // enable P2P for MFM/EDU/Corp provisioning
+ paramsBuilder.setEnableP2p(true);
+ } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED &&
+ mNfcUnlockManager.isLockscreenPollingEnabled()) {
+ // For lock-screen tags, no low-power polling
+ paramsBuilder.setTechMask(mNfcUnlockManager.getLockscreenPollMask());
+ paramsBuilder.setEnableLowPowerDiscovery(false);
+ paramsBuilder.setEnableP2p(false);
+ }
+
+ if (mIsHceCapable && mScreenState >= ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
+ // Host routing is always enabled at lock screen or later
+ paramsBuilder.setEnableHostRouting(true);
+ }
+
+ return paramsBuilder.build();
+ }
+
+ private boolean isTagPresent() {
+ for (Object object : mObjectMap.values()) {
+ if (object instanceof TagEndpoint) {
+ return ((TagEndpoint) object).isPresent();
+ }
+ }
+ return false;
+ }
+ /**
+ * Disconnect any target if present
+ */
+ void maybeDisconnectTarget() {
+ if (!isNfcEnabledOrShuttingDown()) {
+ return;
+ }
+ Object[] objectsToDisconnect;
+ synchronized (this) {
+ Object[] objectValues = mObjectMap.values().toArray();
+ // Copy the array before we clear mObjectMap,
+ // just in case the HashMap values are backed by the same array
+ objectsToDisconnect = Arrays.copyOf(objectValues, objectValues.length);
+ mObjectMap.clear();
+ }
+ for (Object o : objectsToDisconnect) {
+ if (DBG) Log.d(TAG, "disconnecting " + o.getClass().getName());
+ if (o instanceof TagEndpoint) {
+ // Disconnect from tags
+ TagEndpoint tag = (TagEndpoint) o;
+ tag.disconnect();
+ } else if (o instanceof NfcDepEndpoint) {
+ // Disconnect from P2P devices
+ NfcDepEndpoint device = (NfcDepEndpoint) o;
+ if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
+ // Remote peer is target, request disconnection
+ device.disconnect();
+ } else {
+ // Remote peer is initiator, we cannot disconnect
+ // Just wait for field removal
+ }
+ }
+ }
+ }
+
+ Object findObject(int key) {
+ synchronized (this) {
+ Object device = mObjectMap.get(key);
+ if (device == null) {
+ Log.w(TAG, "Handle not found");
+ }
+ return device;
+ }
+ }
+
+ void registerTagObject(TagEndpoint tag) {
+ synchronized (this) {
+ mObjectMap.put(tag.getHandle(), tag);
+ }
+ }
+
+ void unregisterObject(int handle) {
+ synchronized (this) {
+ mObjectMap.remove(handle);
+ }
+ }
+
+ /**
+ * For use by code in this process
+ */
+ public LlcpSocket createLlcpSocket(int sap, int miu, int rw, int linearBufferLength)
+ throws LlcpException {
+ return mDeviceHost.createLlcpSocket(sap, miu, rw, linearBufferLength);
+ }
+
+ /**
+ * For use by code in this process
+ */
+ public LlcpConnectionlessSocket createLlcpConnectionLessSocket(int sap, String sn)
+ throws LlcpException {
+ return mDeviceHost.createLlcpConnectionlessSocket(sap, sn);
+ }
+
+ /**
+ * For use by code in this process
+ */
+ public LlcpServerSocket createLlcpServerSocket(int sap, String sn, int miu, int rw,
+ int linearBufferLength) throws LlcpException {
+ return mDeviceHost.createLlcpServerSocket(sap, sn, miu, rw, linearBufferLength);
+ }
+
+ public void sendMockNdefTag(NdefMessage msg) {
+ sendMessage(MSG_MOCK_NDEF, msg);
+ }
+
+ public void routeAids(String aid, int route) {
+ Message msg = mHandler.obtainMessage();
+ msg.what = MSG_ROUTE_AID;
+ msg.arg1 = route;
+ msg.obj = aid;
+ mHandler.sendMessage(msg);
+ }
+
+ public void unrouteAids(String aid) {
+ sendMessage(MSG_UNROUTE_AID, aid);
+ }
+
+ public void commitRouting() {
+ mHandler.sendEmptyMessage(MSG_COMMIT_ROUTING);
+ }
+
+ public boolean sendData(byte[] data) {
+ return mDeviceHost.sendRawFrame(data);
+ }
+
+ void sendMessage(int what, Object obj) {
+ Message msg = mHandler.obtainMessage();
+ msg.what = what;
+ msg.obj = obj;
+ mHandler.sendMessage(msg);
+ }
+
+ final class NfcServiceHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ROUTE_AID: {
+ int route = msg.arg1;
+ String aid = (String) msg.obj;
+ mDeviceHost.routeAid(hexStringToBytes(aid), route);
+ // Restart polling config
+ break;
+ }
+ case MSG_UNROUTE_AID: {
+ String aid = (String) msg.obj;
+ mDeviceHost.unrouteAid(hexStringToBytes(aid));
+ break;
+ }
+ case MSG_INVOKE_BEAM: {
+ mP2pLinkManager.onManualBeamInvoke((BeamShareData)msg.obj);
+ break;
+ }
+ case MSG_COMMIT_ROUTING: {
+ boolean commit = false;
+ synchronized (NfcService.this) {
+ if (mCurrentDiscoveryParameters.shouldEnableDiscovery()) {
+ commit = true;
+ } else {
+ Log.d(TAG, "Not committing routing because discovery is disabled.");
+ }
+ }
+ if (commit) {
+ mDeviceHost.commitRouting();
+ }
+ break;
+ }
+ case MSG_MOCK_NDEF: {
+ NdefMessage ndefMsg = (NdefMessage) msg.obj;
+ Bundle extras = new Bundle();
+ extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ndefMsg);
+ extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, 0);
+ extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, Ndef.NDEF_MODE_READ_ONLY);
+ extras.putInt(Ndef.EXTRA_NDEF_TYPE, Ndef.TYPE_OTHER);
+ Tag tag = Tag.createMockTag(new byte[]{0x00},
+ new int[]{TagTechnology.NDEF},
+ new Bundle[]{extras});
+ Log.d(TAG, "mock NDEF tag, starting corresponding activity");
+ Log.d(TAG, tag.toString());
+ int dispatchStatus = mNfcDispatcher.dispatchTag(tag);
+ if (dispatchStatus == NfcDispatcher.DISPATCH_SUCCESS) {
+ playSound(SOUND_END);
+ } else if (dispatchStatus == NfcDispatcher.DISPATCH_FAIL) {
+ playSound(SOUND_ERROR);
+ }
+ break;
+ }
+
+ case MSG_NDEF_TAG:
+ if (DBG) Log.d(TAG, "Tag detected, notifying applications");
+ TagEndpoint tag = (TagEndpoint) msg.obj;
+ ReaderModeParams readerParams = null;
+ int presenceCheckDelay = DEFAULT_PRESENCE_CHECK_DELAY;
+ DeviceHost.TagDisconnectedCallback callback =
+ new DeviceHost.TagDisconnectedCallback() {
+ @Override
+ public void onTagDisconnected(long handle) {
+ applyRouting(false);
+ }
+ };
+ synchronized (NfcService.this) {
+ readerParams = mReaderModeParams;
+ }
+ if (readerParams != null) {
+ presenceCheckDelay = readerParams.presenceCheckDelay;
+ if ((readerParams.flags & NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK) != 0) {
+ if (DBG) Log.d(TAG, "Skipping NDEF detection in reader mode");
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ break;
+ }
+ }
+
+ boolean playSound = readerParams == null ||
+ (readerParams.flags & NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS) == 0;
+ if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && playSound) {
+ playSound(SOUND_START);
+ }
+ if (tag.getConnectedTechnology() == TagTechnology.NFC_BARCODE) {
+ // When these tags start containing NDEF, they will require
+ // the stack to deal with them in a different way, since
+ // they are activated only really shortly.
+ // For now, don't consider NDEF on these.
+ if (DBG) Log.d(TAG, "Skipping NDEF detection for NFC Barcode");
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ break;
+ }
+ NdefMessage ndefMsg = tag.findAndReadNdef();
+
+ if (ndefMsg != null) {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ } else {
+ if (tag.reconnect()) {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ } else {
+ tag.disconnect();
+ playSound(SOUND_ERROR);
+ }
+ }
+ break;
+ case MSG_LLCP_LINK_ACTIVATION:
+ if (mIsDebugBuild) {
+ Intent actIntent = new Intent(ACTION_LLCP_UP);
+ mContext.sendBroadcast(actIntent);
+ }
+ llcpActivated((NfcDepEndpoint) msg.obj);
+ break;
+
+ case MSG_LLCP_LINK_DEACTIVATED:
+ if (mIsDebugBuild) {
+ Intent deactIntent = new Intent(ACTION_LLCP_DOWN);
+ mContext.sendBroadcast(deactIntent);
+ }
+ NfcDepEndpoint device = (NfcDepEndpoint) msg.obj;
+ boolean needsDisconnect = false;
+
+ Log.d(TAG, "LLCP Link Deactivated message. Restart polling loop.");
+ synchronized (NfcService.this) {
+ /* Check if the device has been already unregistered */
+ if (mObjectMap.remove(device.getHandle()) != null) {
+ /* Disconnect if we are initiator */
+ if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
+ if (DBG) Log.d(TAG, "disconnecting from target");
+ needsDisconnect = true;
+ } else {
+ if (DBG) Log.d(TAG, "not disconnecting from initiator");
+ }
+ }
+ }
+ if (needsDisconnect) {
+ device.disconnect(); // restarts polling loop
+ }
+
+ mP2pLinkManager.onLlcpDeactivated();
+ break;
+ case MSG_LLCP_LINK_FIRST_PACKET:
+ mP2pLinkManager.onLlcpFirstPacketReceived();
+ break;
+ case MSG_RF_FIELD_ACTIVATED:
+ Intent fieldOnIntent = new Intent(ACTION_RF_FIELD_ON_DETECTED);
+ sendNfcEeAccessProtectedBroadcast(fieldOnIntent);
+ break;
+ case MSG_RF_FIELD_DEACTIVATED:
+ Intent fieldOffIntent = new Intent(ACTION_RF_FIELD_OFF_DETECTED);
+ sendNfcEeAccessProtectedBroadcast(fieldOffIntent);
+ break;
+ case MSG_RESUME_POLLING:
+ mNfcAdapter.resumePolling();
+ break;
+ default:
+ Log.e(TAG, "Unknown message received");
+ break;
+ }
+ }
+
+ private void sendNfcEeAccessProtectedBroadcast(Intent intent) {
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ // Resume app switches so the receivers can start activites without delay
+ mNfcDispatcher.resumeAppSwitches();
+ ArrayList matchingPackages = new ArrayList();
+ ArrayList preferredPackages = new ArrayList();
+ synchronized (this) {
+ for (PackageInfo pkg : mInstalledPackages) {
+ if (pkg != null && pkg.applicationInfo != null) {
+ if (mNfceeAccessControl.check(pkg.applicationInfo)) {
+ matchingPackages.add(pkg.packageName);
+ if (mCardEmulationManager != null &&
+ mCardEmulationManager.packageHasPreferredService(pkg.packageName)) {
+ preferredPackages.add(pkg.packageName);
+ }
+ }
+ }
+ }
+ if (preferredPackages.size() > 0) {
+ // If there's any packages in here which are preferred, only
+ // send field events to those packages, to prevent other apps
+ // with signatures in nfcee_access.xml from acting upon the events.
+ for (String packageName : preferredPackages){
+ intent.setPackage(packageName);
+ mContext.sendBroadcast(intent);
+ }
+ } else {
+ for (String packageName : matchingPackages){
+ intent.setPackage(packageName);
+ mContext.sendBroadcast(intent);
+ }
+ }
+ }
+ }
+
+ private boolean llcpActivated(NfcDepEndpoint device) {
+ Log.d(TAG, "LLCP Activation message");
+
+ if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
+ if (DBG) Log.d(TAG, "NativeP2pDevice.MODE_P2P_TARGET");
+ if (device.connect()) {
+ /* Check LLCP compliancy */
+ if (mDeviceHost.doCheckLlcp()) {
+ /* Activate LLCP Link */
+ if (mDeviceHost.doActivateLlcp()) {
+ if (DBG) Log.d(TAG, "Initiator Activate LLCP OK");
+ synchronized (NfcService.this) {
+ // Register P2P device
+ mObjectMap.put(device.getHandle(), device);
+ }
+ mP2pLinkManager.onLlcpActivated(device.getLlcpVersion());
+ return true;
+ } else {
+ /* should not happen */
+ Log.w(TAG, "Initiator LLCP activation failed. Disconnect.");
+ device.disconnect();
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Remote Target does not support LLCP. Disconnect.");
+ device.disconnect();
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Cannot connect remote Target. Polling loop restarted.");
+ /*
+ * The polling loop should have been restarted in failing
+ * doConnect
+ */
+ }
+ } else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) {
+ if (DBG) Log.d(TAG, "NativeP2pDevice.MODE_P2P_INITIATOR");
+ /* Check LLCP compliancy */
+ if (mDeviceHost.doCheckLlcp()) {
+ /* Activate LLCP Link */
+ if (mDeviceHost.doActivateLlcp()) {
+ if (DBG) Log.d(TAG, "Target Activate LLCP OK");
+ synchronized (NfcService.this) {
+ // Register P2P device
+ mObjectMap.put(device.getHandle(), device);
+ }
+ mP2pLinkManager.onLlcpActivated(device.getLlcpVersion());
+ return true;
+ }
+ } else {
+ Log.w(TAG, "checkLlcp failed");
+ }
+ }
+
+ return false;
+ }
+
+ private void dispatchTagEndpoint(TagEndpoint tagEndpoint, ReaderModeParams readerParams) {
+ Tag tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(),
+ tagEndpoint.getTechExtras(), tagEndpoint.getHandle(), mNfcTagService);
+ registerTagObject(tagEndpoint);
+ if (readerParams != null) {
+ try {
+ if ((readerParams.flags & NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS) == 0) {
+ playSound(SOUND_END);
+ }
+ if (readerParams.callback != null) {
+ readerParams.callback.onTagDiscovered(tag);
+ return;
+ } else {
+ // Follow normal dispatch below
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Reader mode remote has died, falling back.", e);
+ // Intentional fall-through
+ } catch (Exception e) {
+ // Catch any other exception
+ Log.e(TAG, "App exception, not dispatching.", e);
+ return;
+ }
+ }
+ int dispatchResult = mNfcDispatcher.dispatchTag(tag);
+ if (dispatchResult == NfcDispatcher.DISPATCH_FAIL) {
+ unregisterObject(tagEndpoint.getHandle());
+ playSound(SOUND_ERROR);
+ } else if (dispatchResult == NfcDispatcher.DISPATCH_SUCCESS) {
+ playSound(SOUND_END);
+ }
+ }
+ }
+
+ private NfcServiceHandler mHandler = new NfcServiceHandler();
+
+ class ApplyRoutingTask extends AsyncTask {
+ @Override
+ protected Void doInBackground(Integer... params) {
+ synchronized (NfcService.this) {
+ if (params == null || params.length != 1) {
+ // force apply current routing
+ applyRouting(true);
+ return null;
+ }
+ mScreenState = params[0].intValue();
+
+ mRoutingWakeLock.acquire();
+ try {
+ applyRouting(false);
+ } finally {
+ mRoutingWakeLock.release();
+ }
+ return null;
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_SCREEN_ON)
+ || action.equals(Intent.ACTION_SCREEN_OFF)
+ || action.equals(Intent.ACTION_USER_PRESENT)) {
+ // Perform applyRouting() in AsyncTask to serialize blocking calls
+ int screenState = ScreenStateHelper.SCREEN_STATE_OFF;
+ if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+ screenState = ScreenStateHelper.SCREEN_STATE_OFF;
+ } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+ screenState = mKeyguard.isKeyguardLocked()
+ ? ScreenStateHelper.SCREEN_STATE_ON_LOCKED
+ : ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;
+ } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
+ screenState = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;
+ }
+
+ new ApplyRoutingTask().execute(Integer.valueOf(screenState));
+ } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
+ // Query the airplane mode from Settings.System just to make sure that
+ // some random app is not sending this intent
+ if (isAirplaneModeOn != isAirplaneModeOn()) {
+ return;
+ }
+ if (!mIsAirplaneSensitive) {
+ return;
+ }
+ mPrefsEditor.putBoolean(PREF_AIRPLANE_OVERRIDE, false);
+ mPrefsEditor.apply();
+ if (isAirplaneModeOn) {
+ new EnableDisableTask().execute(TASK_DISABLE);
+ } else if (!isAirplaneModeOn && mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT)) {
+ new EnableDisableTask().execute(TASK_ENABLE);
+ }
+ } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+ synchronized (this) {
+ mUserId = userId;
+ }
+ mP2pLinkManager.onUserSwitched(getUserId());
+ if (mIsHceCapable) {
+ mCardEmulationManager.onUserSwitched(getUserId());
+ }
+ }
+ }
+ };
+
+
+ private final BroadcastReceiver mOwnerReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_PACKAGE_REMOVED) ||
+ action.equals(Intent.ACTION_PACKAGE_ADDED) ||
+ action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE) ||
+ action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
+ updatePackageCache();
+
+ if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ // Clear the NFCEE access cache in case a UID gets recycled
+ mNfceeAccessControl.invalidateCache();
+ }
+ }
+ }
+ };
+
+ private final BroadcastReceiver mPolicyReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent){
+ String action = intent.getAction();
+ if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+ .equals(action)) {
+ enforceBeamShareActivityPolicy(context,
+ new UserHandle(getSendingUserId()), mIsNdefPushEnabled);
+ }
+ }
+ };
+
+ /**
+ * Returns true if airplane mode is currently on
+ */
+ boolean isAirplaneModeOn() {
+ return Settings.System.getInt(mContentResolver,
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ /**
+ * for debugging only - no i18n
+ */
+ static String stateToString(int state) {
+ switch (state) {
+ case NfcAdapter.STATE_OFF:
+ return "off";
+ case NfcAdapter.STATE_TURNING_ON:
+ return "turning on";
+ case NfcAdapter.STATE_ON:
+ return "on";
+ case NfcAdapter.STATE_TURNING_OFF:
+ return "turning off";
+ default:
+ return "";
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump nfc from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+
+ synchronized (this) {
+ pw.println("mState=" + stateToString(mState));
+ pw.println("mIsZeroClickRequested=" + mIsNdefPushEnabled);
+ pw.println("mScreenState=" + ScreenStateHelper.screenStateToString(mScreenState));
+ pw.println("mIsAirplaneSensitive=" + mIsAirplaneSensitive);
+ pw.println("mIsAirplaneToggleable=" + mIsAirplaneToggleable);
+ pw.println(mCurrentDiscoveryParameters);
+ mP2pLinkManager.dump(fd, pw, args);
+ if (mIsHceCapable) {
+ mCardEmulationManager.dump(fd, pw, args);
+ }
+ mNfcDispatcher.dump(fd, pw, args);
+ pw.println(mDeviceHost.dump());
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfcUnlockManager.java b/NfcSony/src/com/android/nfc/NfcUnlockManager.java
new file mode 100644
index 0000000..8d2b249
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcUnlockManager.java
@@ -0,0 +1,99 @@
+package com.android.nfc;
+
+import android.nfc.INfcUnlockHandler;
+import android.nfc.Tag;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Singleton for handling NFC Unlock related logic and state.
+ */
+class NfcUnlockManager {
+ private static final String TAG = "NfcUnlockManager";
+
+ private final HashMap mUnlockHandlers;
+ private int mLockscreenPollMask;
+
+ private static class UnlockHandlerWrapper {
+ final INfcUnlockHandler mUnlockHandler;
+ final int mPollMask;
+
+
+ private UnlockHandlerWrapper(INfcUnlockHandler unlockHandler, int pollMask) {
+ mUnlockHandler = unlockHandler;
+ mPollMask = pollMask;
+ }
+ }
+
+ public static NfcUnlockManager getInstance() {
+ return Singleton.INSTANCE;
+ }
+
+
+ synchronized int addUnlockHandler(INfcUnlockHandler unlockHandler, int pollMask) {
+ if (mUnlockHandlers.containsKey(unlockHandler.asBinder())) {
+ return mLockscreenPollMask;
+ }
+
+ mUnlockHandlers.put(unlockHandler.asBinder(),
+ new UnlockHandlerWrapper(unlockHandler, pollMask));
+ return (mLockscreenPollMask |= pollMask);
+ }
+
+ synchronized int removeUnlockHandler(IBinder unlockHandler) {
+ if (mUnlockHandlers.containsKey(unlockHandler)) {
+ mUnlockHandlers.remove(unlockHandler);
+ mLockscreenPollMask = recomputePollMask();
+ }
+
+ return mLockscreenPollMask;
+ }
+
+ synchronized boolean tryUnlock(Tag tag) {
+ Iterator iterator = mUnlockHandlers.keySet().iterator();
+ while (iterator.hasNext()) {
+ try {
+ IBinder binder = iterator.next();
+ UnlockHandlerWrapper handlerWrapper = mUnlockHandlers.get(binder);
+ if (handlerWrapper.mUnlockHandler.onUnlockAttempted(tag)) {
+ return true;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "failed to communicate with unlock handler, removing", e);
+ iterator.remove();
+ mLockscreenPollMask = recomputePollMask();
+ }
+ }
+
+ return false;
+ }
+
+ private int recomputePollMask() {
+ int pollMask = 0;
+ for (UnlockHandlerWrapper wrapper : mUnlockHandlers.values()) {
+ pollMask |= wrapper.mPollMask;
+ }
+ return pollMask;
+ }
+
+ synchronized int getLockscreenPollMask() {
+ return mLockscreenPollMask;
+ }
+
+ synchronized boolean isLockscreenPollingEnabled() {
+ return mLockscreenPollMask != 0;
+ }
+
+ private static class Singleton {
+ private static final NfcUnlockManager INSTANCE = new NfcUnlockManager();
+ }
+
+ private NfcUnlockManager() {
+ mUnlockHandlers = new HashMap();
+ mLockscreenPollMask = 0;
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java b/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java
new file mode 100644
index 0000000..d642175
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java
@@ -0,0 +1,170 @@
+/*
+* Copyright (C) 2014 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.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.tech.Ndef;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.BitSet;
+
+public final class NfcWifiProtectedSetup {
+
+ public static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc";
+
+ public static final String EXTRA_WIFI_CONFIG = "com.android.nfc.WIFI_CONFIG_EXTRA";
+
+ /*
+ * ID into configuration record for SSID and Network Key in hex.
+ * Obtained from WFA Wifi Simple Configuration Technical Specification v2.0.2.1.
+ */
+ private static final short CREDENTIAL_FIELD_ID = 0x100E;
+ private static final short SSID_FIELD_ID = 0x1045;
+ private static final short NETWORK_KEY_FIELD_ID = 0x1027;
+ private static final short AUTH_TYPE_FIELD_ID = 0x1003;
+
+ private static final short AUTH_TYPE_EXPECTED_SIZE = 2;
+
+ private static final short AUTH_TYPE_OPEN = 0;
+ private static final short AUTH_TYPE_WPA_PSK = 0x0002;
+ private static final short AUTH_TYPE_WPA_EAP = 0x0008;
+ private static final short AUTH_TYPE_WPA2_EAP = 0x0010;
+ private static final short AUTH_TYPE_WPA2_PSK = 0x0020;
+
+ private static final int MAX_NETWORK_KEY_SIZE_BYTES = 64;
+
+ private NfcWifiProtectedSetup() {}
+
+ public static boolean tryNfcWifiSetup(Ndef ndef, Context context) {
+
+ if (ndef == null || context == null) {
+ return false;
+ }
+
+ NdefMessage cachedNdefMessage = ndef.getCachedNdefMessage();
+ if (cachedNdefMessage == null) {
+ return false;
+ }
+
+ final WifiConfiguration wifiConfiguration;
+ try {
+ wifiConfiguration = parse(cachedNdefMessage);
+ } catch (BufferUnderflowException e) {
+ // malformed payload
+ return false;
+ }
+
+ if (wifiConfiguration != null &&!UserManager.get(context).hasUserRestriction(
+ UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT)) {
+ Intent configureNetworkIntent = new Intent()
+ .putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration)
+ .setClass(context, ConfirmConnectToWifiNetworkActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static WifiConfiguration parse(NdefMessage message) {
+ NdefRecord[] records = message.getRecords();
+
+ for (NdefRecord record : records) {
+ if (new String(record.getType()).equals(NFC_TOKEN_MIME_TYPE)) {
+ ByteBuffer payload = ByteBuffer.wrap(record.getPayload());
+ while (payload.hasRemaining()) {
+ short fieldId = payload.getShort();
+ short fieldSize = payload.getShort();
+ if (fieldId == CREDENTIAL_FIELD_ID) {
+ return parseCredential(payload, fieldSize);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static WifiConfiguration parseCredential(ByteBuffer payload, short size) {
+ int startPosition = payload.position();
+ WifiConfiguration result = new WifiConfiguration();
+ while (payload.position() < startPosition + size) {
+ short fieldId = payload.getShort();
+ short fieldSize = payload.getShort();
+
+ // sanity check
+ if (payload.position() + fieldSize > startPosition + size) {
+ return null;
+ }
+
+ switch (fieldId) {
+ case SSID_FIELD_ID:
+ byte[] ssid = new byte[fieldSize];
+ payload.get(ssid);
+ result.SSID = "\"" + new String(ssid) + "\"";
+ break;
+ case NETWORK_KEY_FIELD_ID:
+ if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) {
+ return null;
+ }
+ byte[] networkKey = new byte[fieldSize];
+ payload.get(networkKey);
+ result.preSharedKey = "\"" + new String(networkKey) + "\"";
+ break;
+ case AUTH_TYPE_FIELD_ID:
+ if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) {
+ // corrupt data
+ return null;
+ }
+
+ short authType = payload.getShort();
+ populateAllowedKeyManagement(result.allowedKeyManagement, authType);
+ break;
+ default:
+ // unknown / unparsed tag
+ payload.position(payload.position() + fieldSize);
+ break;
+ }
+ }
+
+ if (result.preSharedKey != null && result.SSID != null) {
+ return result;
+ }
+
+ return null;
+ }
+
+ private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) {
+ if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK) {
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
+ } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) {
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+ } else if (authType == AUTH_TYPE_OPEN) {
+ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/NfceeAccessControl.java b/NfcSony/src/com/android/nfc/NfceeAccessControl.java
new file mode 100644
index 0000000..5da1202
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/NfceeAccessControl.java
@@ -0,0 +1,289 @@
+/*
+ * 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 java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
+import android.util.Log;
+
+public class NfceeAccessControl {
+ static final String TAG = "NfceeAccess";
+ static final boolean DBG = false;
+
+ public static final String NFCEE_ACCESS_PATH = "/etc/nfcee_access.xml";
+
+ /**
+ * Map of signatures to valid packages names, as read from nfcee_access.xml.
+ * An empty list of package names indicates that any package
+ * with this signature is allowed.
+ */
+ final HashMap mNfceeAccess; // contents final after onCreate()
+
+ /**
+ * Map from UID to NFCEE access, used as a cache.
+ * Note: if a UID contains multiple packages they must all be
+ * signed with the same certificate so in effect UID == certificate
+ * used to sign the package.
+ */
+ final HashMap mUidCache; // contents guarded by this
+
+ final Context mContext;
+ final boolean mDebugPrintSignature;
+
+ NfceeAccessControl(Context context) {
+ mContext = context;
+ mNfceeAccess = new HashMap();
+ mUidCache = new HashMap();
+ mDebugPrintSignature = parseNfceeAccess();
+ }
+
+ /**
+ * Check if the {uid, pkg} combination may use NFCEE.
+ * Also verify with package manager that this {uid, pkg} combination
+ * is valid if it is not cached.
+ */
+ public boolean check(int uid, String pkg) {
+ synchronized (this) {
+ Boolean cached = mUidCache.get(uid);
+ if (cached != null) {
+ return cached;
+ }
+
+ boolean access = false;
+
+ // Ensure the claimed package is present in the calling UID
+ PackageManager pm = mContext.getPackageManager();
+ String[] pkgs = pm.getPackagesForUid(uid);
+ for (String uidPkg : pkgs) {
+ if (uidPkg.equals(pkg)) {
+ // Ensure the package has access permissions
+ if (checkPackageNfceeAccess(pkg)) {
+ access = true;
+ }
+ break;
+ }
+ }
+
+ mUidCache.put(uid, access);
+ return access;
+ }
+ }
+
+ /**
+ * Check if the given ApplicationInfo may use the NFCEE.
+ * Assumes ApplicationInfo came from package manager,
+ * so no need to confirm {uid, pkg} is valid.
+ */
+ public boolean check(ApplicationInfo info) {
+ synchronized (this) {
+ Boolean access = mUidCache.get(info.uid);
+ if (access == null) {
+ access = checkPackageNfceeAccess(info.packageName);
+ mUidCache.put(info.uid, access);
+ }
+ return access;
+ }
+ }
+
+ public void invalidateCache() {
+ synchronized (this) {
+ mUidCache.clear();
+ }
+ }
+
+ /**
+ * Check with package manager if the pkg may use NFCEE.
+ * Does not use cache.
+ */
+ boolean checkPackageNfceeAccess(String pkg) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
+ if (info.signatures == null) {
+ return false;
+ }
+
+ for (Signature s : info.signatures){
+ if (s == null) {
+ continue;
+ }
+ String[] packages = mNfceeAccess.get(s);
+ if (packages == null) {
+ continue;
+ }
+ if (packages.length == 0) {
+ // wildcard access
+ if (DBG) Log.d(TAG, "Granted NFCEE access to " + pkg + " (wildcard)");
+ return true;
+ }
+ for (String p : packages) {
+ if (pkg.equals(p)) {
+ // explicit package access
+ if (DBG) Log.d(TAG, "Granted access to " + pkg + " (explicit)");
+ return true;
+ }
+ }
+ }
+
+ if (mDebugPrintSignature) {
+ Log.w(TAG, "denied NFCEE access for " + pkg + " with signature:");
+ for (Signature s : info.signatures) {
+ if (s != null) {
+ Log.w(TAG, s.toCharsString());
+ }
+ }
+ }
+ } catch (NameNotFoundException e) {
+ // ignore
+ }
+ return false;
+ }
+
+ /**
+ * Parse nfcee_access.xml, populate mNfceeAccess
+ * Policy is to ignore unexpected XML elements and continue processing,
+ * except for obvious errors within a group since they might cause
+ * package names to by ignored and therefore wildcard access granted
+ * by mistake. Those errors invalidate the entire group.
+ */
+ boolean parseNfceeAccess() {
+ File file = new File(Environment.getRootDirectory(), NFCEE_ACCESS_PATH);
+ FileReader reader = null;
+ boolean debug = false;
+ try {
+ reader = new FileReader(file);
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ XmlPullParser parser = factory.newPullParser();
+ parser.setInput(reader);
+
+ int event;
+ ArrayList packages = new ArrayList();
+ Signature signature = null;
+ parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
+ while (true) {
+ event = parser.next();
+ String tag = parser.getName();
+ if (event == XmlPullParser.START_TAG && "signer".equals(tag)) {
+ signature = null;
+ packages.clear();
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if ("android:signature".equals(parser.getAttributeName(i))) {
+ signature = new Signature(parser.getAttributeValue(i));
+ break;
+ }
+ }
+ if (signature == null) {
+ Log.w(TAG, "signer tag is missing android:signature attribute, igorning");
+ continue;
+ }
+ if (mNfceeAccess.containsKey(signature)) {
+ Log.w(TAG, "duplicate signature, ignoring");
+ signature = null;
+ continue;
+ }
+ } else if (event == XmlPullParser.END_TAG && "signer".equals(tag)) {
+ if (signature == null) {
+ Log.w(TAG, "mis-matched signer tag");
+ continue;
+ }
+ mNfceeAccess.put(signature, packages.toArray(new String[0]));
+ packages.clear();
+ } else if (event == XmlPullParser.START_TAG && "package".equals(tag)) {
+ if (signature == null) {
+ Log.w(TAG, "ignoring unnested packge tag");
+ continue;
+ }
+ String name = null;
+ for (int i = 0; i < parser.getAttributeCount(); i++) {
+ if ("android:name".equals(parser.getAttributeName(i))) {
+ name = parser.getAttributeValue(i);
+ break;
+ }
+ }
+ if (name == null) {
+ Log.w(TAG, "package missing android:name, ignoring signer group");
+ signature = null; // invalidate signer
+ continue;
+ }
+ // check for duplicate package names
+ if (packages.contains(name)) {
+ Log.w(TAG, "duplicate package name in signer group, ignoring");
+ continue;
+ }
+ packages.add(name);
+ } else if (event == XmlPullParser.START_TAG && "debug".equals(tag)) {
+ debug = true;
+ } else if (event == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "failed to load NFCEE access list", e);
+ mNfceeAccess.clear(); // invalidate entire access list
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "could not find " + NFCEE_ACCESS_PATH + ", no NFCEE access allowed");
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to load NFCEE access list", e);
+ mNfceeAccess.clear(); // invalidate entire access list
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e2) { }
+ }
+ }
+ Log.i(TAG, "read " + mNfceeAccess.size() + " signature(s) for NFCEE access");
+ return debug;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("mNfceeAccess=");
+ for (Signature s : mNfceeAccess.keySet()) {
+ pw.printf("\t%s [", s.toCharsString());
+ String[] ps = mNfceeAccess.get(s);
+ for (String p : ps) {
+ pw.printf("%s, ", p);
+ }
+ pw.println("]");
+ }
+ synchronized (this) {
+ pw.println("mNfceeUidCache=");
+ for (Integer uid : mUidCache.keySet()) {
+ Boolean b = mUidCache.get(uid);
+ pw.printf("\t%d %s\n", uid, b);
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/P2pEventManager.java b/NfcSony/src/com/android/nfc/P2pEventManager.java
new file mode 100644
index 0000000..1f84947
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/P2pEventManager.java
@@ -0,0 +1,210 @@
+/*
+ * 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.nfc.beam.SendUi;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Vibrator;
+
+/**
+ * Manages vibration, sound and animation for P2P events.
+ */
+public class P2pEventManager implements P2pEventListener, SendUi.Callback {
+ static final String TAG = "NfcP2pEventManager";
+ static final boolean DBG = true;
+
+ static final long[] VIBRATION_PATTERN = {0, 100, 10000};
+
+ final Context mContext;
+ final NfcService mNfcService;
+ final P2pEventListener.Callback mCallback;
+ final Vibrator mVibrator;
+ final NotificationManager mNotificationManager;
+ final SendUi mSendUi;
+
+ // only used on UI thread
+ boolean mSending;
+ boolean mNdefSent;
+ boolean mNdefReceived;
+ boolean mInDebounce;
+
+ public P2pEventManager(Context context, P2pEventListener.Callback callback) {
+ mNfcService = NfcService.getInstance();
+ mContext = context;
+ mCallback = callback;
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+
+ mSending = false;
+ final int uiModeType = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK;
+ if (uiModeType == Configuration.UI_MODE_TYPE_APPLIANCE) {
+ // "Appliances" don't intrinsically have a way of confirming this, so we
+ // don't use the UI and just autoconfirm where necessary.
+ // Don't instantiate SendUi or else we'll use memory and never reclaim it.
+ mSendUi = null;
+ } else {
+ mSendUi = new SendUi(context, this);
+ }
+ }
+
+ @Override
+ public void onP2pInRange() {
+ mNdefSent = false;
+ mNdefReceived = false;
+ mInDebounce = false;
+
+ if (mSendUi != null) {
+ mSendUi.takeScreenshot();
+ }
+ }
+
+ @Override
+ public void onP2pNfcTapRequested() {
+ mNfcService.playSound(NfcService.SOUND_START);
+ mNdefSent = false;
+ mNdefReceived = false;
+ mInDebounce = false;
+
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ if (mSendUi != null) {
+ mSendUi.takeScreenshot();
+ mSendUi.showPreSend(true);
+ }
+ }
+
+ @Override
+ public void onP2pTimeoutWaitingForLink() {
+ if (mSendUi != null) {
+ mSendUi.finish(SendUi.FINISH_SCALE_UP);
+ }
+ }
+
+ @Override
+ public void onP2pSendConfirmationRequested() {
+ mNfcService.playSound(NfcService.SOUND_START);
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ if (mSendUi != null) {
+ mSendUi.showPreSend(false);
+ } else {
+ mCallback.onP2pSendConfirmed();
+ }
+ }
+
+ @Override
+ public void onP2pSendComplete() {
+ mNfcService.playSound(NfcService.SOUND_END);
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ if (mSendUi != null) {
+ mSendUi.finish(SendUi.FINISH_SEND_SUCCESS);
+ }
+ mSending = false;
+ mNdefSent = true;
+ }
+
+ @Override
+ public void onP2pHandoverNotSupported() {
+ mNfcService.playSound(NfcService.SOUND_ERROR);
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ mSendUi.finishAndToast(SendUi.FINISH_SCALE_UP,
+ mContext.getString(R.string.beam_handover_not_supported));
+ mSending = false;
+ mNdefSent = false;
+ }
+
+ @Override
+ public void onP2pHandoverBusy() {
+ mNfcService.playSound(NfcService.SOUND_ERROR);
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ mSendUi.finishAndToast(SendUi.FINISH_SCALE_UP, mContext.getString(R.string.beam_busy));
+ mSending = false;
+ mNdefSent = false;
+ }
+
+ @Override
+ public void onP2pReceiveComplete(boolean playSound) {
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ if (playSound) mNfcService.playSound(NfcService.SOUND_END);
+ if (mSendUi != null) {
+ // TODO we still don't have a nice receive solution
+ // The sanest solution right now is just to scale back up what we had
+ // and start the new activity. It is not perfect, but at least it is
+ // consistent behavior. All other variants involve making the old
+ // activity screenshot disappear, and then removing the animation
+ // window hoping the new activity has started by then. This just goes
+ // wrong too often and can look weird.
+ mSendUi.finish(SendUi.FINISH_SCALE_UP);
+ }
+ mNdefReceived = true;
+ }
+
+ @Override
+ public void onP2pOutOfRange() {
+ if (mSending) {
+ mNfcService.playSound(NfcService.SOUND_ERROR);
+ mSending = false;
+ }
+ if (!mNdefSent && !mNdefReceived && mSendUi != null) {
+ mSendUi.finish(SendUi.FINISH_SCALE_UP);
+ }
+ mInDebounce = false;
+ }
+
+ @Override
+ public void onSendConfirmed() {
+ if (!mSending) {
+ if (mSendUi != null) {
+ mSendUi.showStartSend();
+ }
+ mCallback.onP2pSendConfirmed();
+ }
+ mSending = true;
+
+ }
+
+ @Override
+ public void onCanceled() {
+ mSendUi.finish(SendUi.FINISH_SCALE_UP);
+ mCallback.onP2pCanceled();
+ }
+
+ @Override
+ public void onP2pSendDebounce() {
+ mInDebounce = true;
+ mNfcService.playSound(NfcService.SOUND_ERROR);
+ if (mSendUi != null) {
+ mSendUi.showSendHint();
+ }
+ }
+
+ @Override
+ public void onP2pResumeSend() {
+ mVibrator.vibrate(VIBRATION_PATTERN, -1);
+ mNfcService.playSound(NfcService.SOUND_START);
+ if (mInDebounce) {
+ if (mSendUi != null) {
+ mSendUi.showStartSend();
+ }
+ }
+ mInDebounce = false;
+ }
+
+}
diff --git a/NfcSony/src/com/android/nfc/P2pLinkManager.java b/NfcSony/src/com/android/nfc/P2pLinkManager.java
new file mode 100755
index 0000000..b083549
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/P2pLinkManager.java
@@ -0,0 +1,1124 @@
+/*
+ * 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.content.Intent;
+import android.content.pm.UserInfo;
+
+import com.android.nfc.beam.BeamManager;
+import com.android.nfc.beam.BeamSendService;
+import com.android.nfc.beam.BeamTransferRecord;
+
+import android.os.UserManager;
+import com.android.nfc.echoserver.EchoServer;
+import com.android.nfc.handover.HandoverClient;
+import com.android.nfc.handover.HandoverDataParser;
+import com.android.nfc.handover.HandoverServer;
+import com.android.nfc.ndefpush.NdefPushClient;
+import com.android.nfc.ndefpush.NdefPushServer;
+import com.android.nfc.snep.SnepClient;
+import com.android.nfc.snep.SnepMessage;
+import com.android.nfc.snep.SnepServer;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.nfc.BeamShareData;
+import android.nfc.IAppCallback;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Interface to listen for P2P events.
+ * All callbacks are made from the UI thread.
+ */
+interface P2pEventListener {
+ /**
+ * Indicates the user has expressed an intent to share
+ * over NFC, but a remote device has not come into range
+ * yet. Prompt the user to NFC tap.
+ */
+ public void onP2pNfcTapRequested();
+
+ /**
+ * Indicates the user has expressed an intent to share over
+ * NFC, but the link hasn't come up yet and we no longer
+ * want to wait for it
+ */
+ public void onP2pTimeoutWaitingForLink();
+
+ /**
+ * Indicates a P2P device is in range.
+ * onP2pInRange() and onP2pOutOfRange() will always be called
+ * alternately.
+ */
+ public void onP2pInRange();
+
+ /**
+ * Called when a NDEF payload is prepared to send, and confirmation is
+ * required. Call Callback.onP2pSendConfirmed() to make the confirmation.
+ */
+ public void onP2pSendConfirmationRequested();
+
+ /**
+ * Called to indicate a send was successful.
+ */
+ public void onP2pSendComplete();
+
+ /**
+ *
+ * Called to indicate the link has broken while we were trying to send
+ * a message. We'll start a debounce timer for the user to get the devices
+ * back together. UI may show a hint to achieve that
+ */
+ public void onP2pSendDebounce();
+
+ /**
+ * Called to indicate a link has come back up after being temporarily
+ * broken, and sending is resuming
+ */
+ public void onP2pResumeSend();
+
+ /**
+ * Called to indicate the remote device does not support connection handover
+ */
+ public void onP2pHandoverNotSupported();
+
+ /**
+ * Called to indicate the device is busy with another handover transfer
+ */
+ public void onP2pHandoverBusy();
+
+ /**
+ * Called to indicate a receive was successful.
+ */
+ public void onP2pReceiveComplete(boolean playSound);
+
+ /**
+ * Indicates the P2P device went out of range.
+ */
+ public void onP2pOutOfRange();
+
+ public interface Callback {
+ public void onP2pSendConfirmed();
+ public void onP2pCanceled();
+ }
+}
+
+/**
+ * Manages sending and receiving NDEF message over LLCP link.
+ * Does simple debouncing of the LLCP link - so that even if the link
+ * drops and returns the user does not know.
+ */
+class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback {
+ static final String TAG = "NfcP2pLinkManager";
+ static final boolean DBG = true;
+
+ /** Include this constant as a meta-data entry in the manifest
+ * of an application to disable beaming the market/AAR link, like this:
+ *
{@code
+ *
+ *
+ *
+ * }
+ */
+ static final String DISABLE_BEAM_DEFAULT = "android.nfc.disable_beam_default";
+
+ /** Enables the LLCP EchoServer, which can be used to test the android
+ * LLCP stack against nfcpy.
+ */
+ static final boolean ECHOSERVER_ENABLED = false;
+
+ // TODO dynamically assign SAP values
+ static final int NDEFPUSH_SAP = 0x10;
+ static final int HANDOVER_SAP = 0x14;
+
+ static final int LINK_SEND_PENDING_DEBOUNCE_MS = 3000;
+ static final int LINK_SEND_CONFIRMED_DEBOUNCE_MS = 5000;
+ static final int LINK_SEND_COMPLETE_DEBOUNCE_MS = 500;
+ static final int LINK_SEND_CANCELED_DEBOUNCE_MS = 250;
+
+ // The amount of time we wait for the link to come up
+ // after a user has manually invoked Beam.
+ static final int WAIT_FOR_LINK_TIMEOUT_MS = 10000;
+
+ static final int MSG_DEBOUNCE_TIMEOUT = 1;
+ static final int MSG_RECEIVE_COMPLETE = 2;
+ static final int MSG_RECEIVE_HANDOVER = 3;
+ static final int MSG_SEND_COMPLETE = 4;
+ static final int MSG_START_ECHOSERVER = 5;
+ static final int MSG_STOP_ECHOSERVER = 6;
+ static final int MSG_HANDOVER_NOT_SUPPORTED = 7;
+ static final int MSG_SHOW_CONFIRMATION_UI = 8;
+ static final int MSG_WAIT_FOR_LINK_TIMEOUT = 9;
+ static final int MSG_HANDOVER_BUSY = 10;
+
+ // values for mLinkState
+ static final int LINK_STATE_DOWN = 1;
+ static final int LINK_STATE_UP = 2;
+ static final int LINK_STATE_DEBOUNCE = 3;
+
+ // values for mSendState
+ static final int SEND_STATE_NOTHING_TO_SEND = 1;
+ static final int SEND_STATE_NEED_CONFIRMATION = 2;
+ static final int SEND_STATE_PENDING = 3;
+ static final int SEND_STATE_SENDING = 4;
+ static final int SEND_STATE_COMPLETE = 5;
+ static final int SEND_STATE_CANCELED = 6;
+
+ // return values for doSnepProtocol
+ static final int SNEP_SUCCESS = 0;
+ static final int SNEP_FAILURE = 1;
+
+ // return values for doHandover
+ static final int HANDOVER_SUCCESS = 0;
+ static final int HANDOVER_FAILURE = 1;
+ static final int HANDOVER_UNSUPPORTED = 2;
+ static final int HANDOVER_BUSY = 3;
+
+ final NdefPushServer mNdefPushServer;
+ final SnepServer mDefaultSnepServer;
+ final HandoverServer mHandoverServer;
+ final EchoServer mEchoServer;
+ final Context mContext;
+ final P2pEventListener mEventListener;
+ final Handler mHandler;
+ final HandoverDataParser mHandoverDataParser;
+ final ForegroundUtils mForegroundUtils;
+
+ final int mDefaultMiu;
+ final int mDefaultRwSize;
+
+ // Locked on NdefP2pManager.this
+ PackageManager mPackageManager;
+ int mLinkState;
+ int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE
+ boolean mIsSendEnabled;
+ boolean mIsReceiveEnabled;
+ NdefMessage mMessageToSend; // not valid in SEND_STATE_NOTHING_TO_SEND
+ Uri[] mUrisToSend; // not valid in SEND_STATE_NOTHING_TO_SEND
+ UserHandle mUserHandle; // not valid in SEND_STATE_NOTHING_TO_SEND
+ int mSendFlags; // not valid in SEND_STATE_NOTHING_TO_SEND
+ IAppCallback mCallbackNdef;
+ int mNdefCallbackUid;
+ SendTask mSendTask;
+ SharedPreferences mPrefs;
+ SnepClient mSnepClient;
+ HandoverClient mHandoverClient;
+ NdefPushClient mNdefPushClient;
+ ConnectTask mConnectTask;
+ boolean mLlcpServicesConnected;
+ long mLastLlcpActivationTime;
+ byte mPeerLlcpVersion;
+
+ public P2pLinkManager(Context context, HandoverDataParser handoverDataParser, int defaultMiu,
+ int defaultRwSize) {
+ mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
+ mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize);
+ mHandoverServer = new HandoverServer(context, HANDOVER_SAP, handoverDataParser, mHandoverCallback);
+
+ if (ECHOSERVER_ENABLED) {
+ mEchoServer = new EchoServer();
+ } else {
+ mEchoServer = null;
+ }
+ mPackageManager = context.getPackageManager();
+ mContext = context;
+ mEventListener = new P2pEventManager(context, this);
+ mHandler = new Handler(this);
+ mLinkState = LINK_STATE_DOWN;
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ mIsSendEnabled = false;
+ mIsReceiveEnabled = false;
+ mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE);
+ mHandoverDataParser = handoverDataParser;
+ mDefaultMiu = defaultMiu;
+ mDefaultRwSize = defaultRwSize;
+ mLlcpServicesConnected = false;
+ mNdefCallbackUid = -1;
+ mForegroundUtils = ForegroundUtils.getInstance();
+ }
+
+ /**
+ * May be called from any thread.
+ * Assumes that NFC is already on if any parameter is true.
+ */
+ public void enableDisable(boolean sendEnable, boolean receiveEnable) {
+ synchronized (this) {
+ if (!mIsReceiveEnabled && receiveEnable) {
+ mDefaultSnepServer.start();
+ mNdefPushServer.start();
+ mHandoverServer.start();
+ if (mEchoServer != null) {
+ mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
+ }
+ } else if (mIsReceiveEnabled && !receiveEnable) {
+ if (DBG) Log.d(TAG, "enableDisable: llcp deactivate");
+ onLlcpDeactivated ();
+ mDefaultSnepServer.stop();
+ mNdefPushServer.stop();
+ mHandoverServer.stop();
+ if (mEchoServer != null) {
+ mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER);
+ }
+ }
+ mIsSendEnabled = sendEnable;
+ mIsReceiveEnabled = receiveEnable;
+ }
+ }
+
+ /**
+ * May be called from any thread.
+ * @return whether the LLCP link is in an active or debounce state
+ */
+ public boolean isLlcpActive() {
+ synchronized (this) {
+ return mLinkState != LINK_STATE_DOWN;
+ }
+ }
+
+ /**
+ * Set NDEF callback for sending.
+ * May be called from any thread.
+ * NDEF callbacks may be set at any time (even if NFC is
+ * currently off or P2P send is currently off). They will become
+ * active as soon as P2P send is enabled.
+ */
+ public void setNdefCallback(IAppCallback callbackNdef, int callingUid) {
+ synchronized (this) {
+ mCallbackNdef = callbackNdef;
+ mNdefCallbackUid = callingUid;
+ }
+ }
+
+
+ public void onManualBeamInvoke(BeamShareData shareData) {
+ synchronized (P2pLinkManager.this) {
+ if (mLinkState != LINK_STATE_DOWN) {
+ return;
+ }
+ if (mForegroundUtils.getForegroundUids().contains(mNdefCallbackUid)) {
+ // Try to get data from the registered NDEF callback
+ prepareMessageToSend(false);
+ } else {
+ mMessageToSend = null;
+ mUrisToSend = null;
+ }
+ if (mMessageToSend == null && mUrisToSend == null && shareData != null) {
+ // No data from the NDEF callback, get data from ShareData
+ if (shareData.uris != null) {
+ mUrisToSend = shareData.uris;
+ } else if (shareData.ndefMessage != null) {
+ mMessageToSend = shareData.ndefMessage;
+ }
+
+ mUserHandle = shareData.userHandle;
+ }
+ if (mMessageToSend != null ||
+ (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
+ mSendState = SEND_STATE_PENDING;
+ mEventListener.onP2pNfcTapRequested();
+ scheduleTimeoutLocked(MSG_WAIT_FOR_LINK_TIMEOUT, WAIT_FOR_LINK_TIMEOUT_MS);
+ }
+ }
+ }
+
+ /**
+ * Must be called on UI Thread.
+ */
+ public void onLlcpActivated(byte peerLlcpVersion) {
+ Log.i(TAG, "LLCP activated");
+ synchronized (P2pLinkManager.this) {
+ if (mEchoServer != null) {
+ mEchoServer.onLlcpActivated();
+ }
+ mLastLlcpActivationTime = SystemClock.elapsedRealtime();
+ mPeerLlcpVersion = peerLlcpVersion;
+ switch (mLinkState) {
+ case LINK_STATE_DOWN:
+ if (DBG) Log.d(TAG, "onP2pInRange()");
+ // Start taking a screenshot
+ mEventListener.onP2pInRange();
+ mLinkState = LINK_STATE_UP;
+ // If we had a pending send (manual Beam invoke),
+ // mark it as sending
+ if (mSendState == SEND_STATE_PENDING) {
+ mSendState = SEND_STATE_SENDING;
+ mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT);
+ // Immediately try to connect LLCP services
+ connectLlcpServices();
+ } else {
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ prepareMessageToSend(true);
+ if (mMessageToSend != null ||
+ (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) {
+ // We have data to send, connect LLCP services
+ connectLlcpServices();
+ if ((mSendFlags & NfcAdapter.FLAG_NDEF_PUSH_NO_CONFIRM) != 0) {
+ mSendState = SEND_STATE_SENDING;
+ } else {
+ mSendState = SEND_STATE_NEED_CONFIRMATION;
+ }
+ }
+ }
+ break;
+ case LINK_STATE_UP:
+ if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()");
+ return;
+ case LINK_STATE_DEBOUNCE:
+ // Immediately connect and try to send again
+ mLinkState = LINK_STATE_UP;
+ if (mSendState == SEND_STATE_SENDING ||
+ mSendState == SEND_STATE_NEED_CONFIRMATION) {
+ // If we have something to send still, connect LLCP
+ connectLlcpServices();
+ }
+ mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Must be called on UI Thread.
+ */
+ public void onLlcpFirstPacketReceived() {
+ synchronized (P2pLinkManager.this) {
+ long totalTime = SystemClock.elapsedRealtime() - mLastLlcpActivationTime;
+ if (DBG) Log.d(TAG, "Took " + Long.toString(totalTime) + " to get first LLCP PDU");
+ }
+ }
+
+ public void onUserSwitched(int userId) {
+ // Update the cached package manager in case of user switch
+ synchronized (P2pLinkManager.this) {
+ try {
+ mPackageManager = mContext.createPackageContextAsUser("android", 0,
+ new UserHandle(userId)).getPackageManager();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Failed to retrieve PackageManager for user");
+ }
+ }
+ }
+
+ void prepareMessageToSend(boolean generatePlayLink) {
+ synchronized (P2pLinkManager.this) {
+ mMessageToSend = null;
+ mUrisToSend = null;
+ if (!mIsSendEnabled) {
+ return;
+ }
+
+ List foregroundUids = mForegroundUtils.getForegroundUids();
+ if (foregroundUids.isEmpty()) {
+ Log.e(TAG, "Could not determine foreground UID.");
+ return;
+ }
+
+ if (isBeamDisabled(foregroundUids.get(0))) {
+ if (DBG) Log.d(TAG, "Beam is disabled by policy.");
+ return;
+ }
+
+ if (mCallbackNdef != null) {
+ if (foregroundUids.contains(mNdefCallbackUid)) {
+ try {
+ BeamShareData shareData = mCallbackNdef.createBeamShareData(mPeerLlcpVersion);
+ mMessageToSend = shareData.ndefMessage;
+ mUrisToSend = shareData.uris;
+ mUserHandle = shareData.userHandle;
+ mSendFlags = shareData.flags;
+ return;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed NDEF callback: ", e);
+ }
+ } else {
+ // This is not necessarily an error - we no longer unset callbacks from
+ // the app process itself (to prevent IPC calls on every pause).
+ // Hence it may simply be a stale callback.
+ if (DBG) Log.d(TAG, "Last registered callback is not running in the foreground.");
+ }
+ }
+
+ // fall back to default NDEF for the foreground activity, unless the
+ // application disabled this explicitly in their manifest.
+ String[] pkgs = mPackageManager.getPackagesForUid(foregroundUids.get(0));
+ if (pkgs != null && pkgs.length >= 1) {
+ if (!generatePlayLink || beamDefaultDisabled(pkgs[0])) {
+ if (DBG) Log.d(TAG, "Disabling default Beam behavior");
+ mMessageToSend = null;
+ mUrisToSend = null;
+ } else {
+ mMessageToSend = createDefaultNdef(pkgs[0]);
+ mUrisToSend = null;
+ }
+ }
+
+ if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend);
+ if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend);
+ }
+ }
+
+ private boolean isBeamDisabled(int uid) {
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ UserInfo userInfo = userManager.getUserInfo(UserHandle.getUserId(uid));
+ return userManager.hasUserRestriction(
+ UserManager.DISALLOW_OUTGOING_BEAM, userInfo.getUserHandle());
+
+ }
+
+ boolean beamDefaultDisabled(String pkgName) {
+ try {
+ ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgName,
+ PackageManager.GET_META_DATA);
+ if (ai == null || ai.metaData == null) {
+ return false;
+ }
+ return ai.metaData.getBoolean(DISABLE_BEAM_DEFAULT);
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ NdefMessage createDefaultNdef(String pkgName) {
+ NdefRecord appUri = NdefRecord.createUri(Uri.parse(
+ "http://play.google.com/store/apps/details?id=" + pkgName + "&feature=beam"));
+ NdefRecord appRecord = NdefRecord.createApplicationRecord(pkgName);
+ return new NdefMessage(new NdefRecord[] { appUri, appRecord });
+ }
+
+ void disconnectLlcpServices() {
+ synchronized (this) {
+ if (mConnectTask != null) {
+ mConnectTask.cancel(true);
+ mConnectTask = null;
+ }
+ // Close any already connected LLCP clients
+ if (mNdefPushClient != null) {
+ mNdefPushClient.close();
+ mNdefPushClient = null;
+ }
+ if (mSnepClient != null) {
+ mSnepClient.close();
+ mSnepClient = null;
+ }
+ if (mHandoverClient != null) {
+ mHandoverClient.close();
+ mHandoverClient = null;
+ }
+ mLlcpServicesConnected = false;
+ }
+ }
+
+ /**
+ * Must be called on UI Thread.
+ */
+ public void onLlcpDeactivated() {
+ Log.i(TAG, "LLCP deactivated.");
+ synchronized (this) {
+ if (mEchoServer != null) {
+ mEchoServer.onLlcpDeactivated();
+ }
+
+ switch (mLinkState) {
+ case LINK_STATE_DOWN:
+ case LINK_STATE_DEBOUNCE:
+ Log.i(TAG, "Duplicate onLlcpDectivated()");
+ break;
+ case LINK_STATE_UP:
+ // Debounce
+ mLinkState = LINK_STATE_DEBOUNCE;
+ int debounceTimeout = 0;
+ switch (mSendState) {
+ case SEND_STATE_NOTHING_TO_SEND:
+ debounceTimeout = 0;
+ break;
+ case SEND_STATE_NEED_CONFIRMATION:
+ debounceTimeout = LINK_SEND_PENDING_DEBOUNCE_MS;
+ break;
+ case SEND_STATE_SENDING:
+ debounceTimeout = LINK_SEND_CONFIRMED_DEBOUNCE_MS;
+ break;
+ case SEND_STATE_COMPLETE:
+ debounceTimeout = LINK_SEND_COMPLETE_DEBOUNCE_MS;
+ break;
+ case SEND_STATE_CANCELED:
+ debounceTimeout = LINK_SEND_CANCELED_DEBOUNCE_MS;
+ }
+ scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, debounceTimeout);
+ if (mSendState == SEND_STATE_SENDING) {
+ Log.e(TAG, "onP2pSendDebounce()");
+ mEventListener.onP2pSendDebounce();
+ }
+ cancelSendNdefMessage();
+ disconnectLlcpServices();
+ break;
+ }
+ }
+ }
+
+ void onHandoverUnsupported() {
+ mHandler.sendEmptyMessage(MSG_HANDOVER_NOT_SUPPORTED);
+ }
+
+ void onHandoverBusy() {
+ mHandler.sendEmptyMessage(MSG_HANDOVER_BUSY);
+ }
+
+ void onSendComplete(NdefMessage msg, long elapsedRealtime) {
+ // Make callbacks on UI thread
+ mHandler.sendEmptyMessage(MSG_SEND_COMPLETE);
+ }
+
+ void sendNdefMessage() {
+ synchronized (this) {
+ cancelSendNdefMessage();
+ mSendTask = new SendTask();
+ mSendTask.execute();
+ }
+ }
+
+ void cancelSendNdefMessage() {
+ synchronized (P2pLinkManager.this) {
+ if (mSendTask != null) {
+ mSendTask.cancel(true);
+ }
+ }
+ }
+
+ void connectLlcpServices() {
+ synchronized (P2pLinkManager.this) {
+ if (mConnectTask != null) {
+ Log.e(TAG, "Still had a reference to mConnectTask!");
+ }
+ mConnectTask = new ConnectTask();
+ mConnectTask.execute();
+ }
+ }
+
+ // Must be called on UI-thread
+ void onLlcpServicesConnected() {
+ if (DBG) Log.d(TAG, "onLlcpServicesConnected");
+ synchronized (P2pLinkManager.this) {
+ if (mLinkState != LINK_STATE_UP) {
+ return;
+ }
+ mLlcpServicesConnected = true;
+ if (mSendState == SEND_STATE_NEED_CONFIRMATION) {
+ if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()");
+ mEventListener.onP2pSendConfirmationRequested();
+ } else if (mSendState == SEND_STATE_SENDING) {
+ mEventListener.onP2pResumeSend();
+ sendNdefMessage();
+ } else {
+ // Either nothing to send or canceled/complete, ignore
+ }
+ }
+ }
+
+ final class ConnectTask extends AsyncTask {
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (isCancelled()) {
+ if (DBG) Log.d(TAG, "ConnectTask was cancelled");
+ return;
+ }
+ if (result) {
+ onLlcpServicesConnected();
+ } else {
+ Log.e(TAG, "Could not connect required NFC transports");
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ boolean needsHandover = false;
+ boolean needsNdef = false;
+ boolean success = false;
+ HandoverClient handoverClient = null;
+ SnepClient snepClient = null;
+ NdefPushClient nppClient = null;
+
+ synchronized(P2pLinkManager.this) {
+ if (mUrisToSend != null) {
+ needsHandover = true;
+ }
+
+ if (mMessageToSend != null) {
+ needsNdef = true;
+ }
+ }
+ // We know either is requested - otherwise this task
+ // wouldn't have been started.
+ if (needsHandover) {
+ handoverClient = new HandoverClient();
+ try {
+ handoverClient.connect();
+ success = true; // Regardless of NDEF result
+ } catch (IOException e) {
+ handoverClient = null;
+ }
+ }
+
+ if (needsNdef || (needsHandover && handoverClient == null)) {
+ snepClient = new SnepClient();
+ try {
+ snepClient.connect();
+ success = true;
+ } catch (IOException e) {
+ snepClient = null;
+ }
+
+ if (!success) {
+ nppClient = new NdefPushClient();
+ try {
+ nppClient.connect();
+ success = true;
+ } catch (IOException e) {
+ nppClient = null;
+ }
+ }
+ }
+
+ synchronized (P2pLinkManager.this) {
+ if (isCancelled()) {
+ // Cancelled by onLlcpDeactivated on UI thread
+ if (handoverClient != null) {
+ handoverClient.close();
+ }
+ if (snepClient != null) {
+ snepClient.close();
+ }
+ if (nppClient != null) {
+ nppClient.close();
+ }
+ return false;
+ } else {
+ // Once assigned, these are the responsibility of
+ // the code on the UI thread to release - typically
+ // through onLlcpDeactivated().
+ mHandoverClient = handoverClient;
+ mSnepClient = snepClient;
+ mNdefPushClient = nppClient;
+ return success;
+ }
+ }
+ }
+ };
+
+ final class SendTask extends AsyncTask {
+ NdefPushClient nppClient;
+ SnepClient snepClient;
+ HandoverClient handoverClient;
+
+ int doHandover(Uri[] uris, UserHandle userHandle) throws IOException {
+ NdefMessage response = null;
+ BeamManager beamManager = BeamManager.getInstance();
+
+ if (beamManager.isBeamInProgress()) {
+ return HANDOVER_BUSY;
+ }
+
+ NdefMessage request = mHandoverDataParser.createHandoverRequestMessage();
+ if (request != null) {
+ if (handoverClient != null) {
+ response = handoverClient.sendHandoverRequest(request);
+ }
+ if (response == null && snepClient != null) {
+ // Remote device may not support handover service,
+ // try the (deprecated) SNEP GET implementation
+ // for devices running Android 4.1
+ SnepMessage snepResponse = snepClient.get(request);
+ response = snepResponse.getNdefMessage();
+ }
+ if (response == null) {
+ return HANDOVER_UNSUPPORTED;
+ }
+ } else {
+ return HANDOVER_UNSUPPORTED;
+ }
+
+ if (!beamManager.startBeamSend(mContext,
+ mHandoverDataParser.getOutgoingHandoverData(response), uris, userHandle)) {
+ return HANDOVER_BUSY;
+ }
+
+ return HANDOVER_SUCCESS;
+ }
+
+ int doSnepProtocol(NdefMessage msg) throws IOException {
+ if (msg != null) {
+ snepClient.put(msg);
+ return SNEP_SUCCESS;
+ } else {
+ return SNEP_FAILURE;
+ }
+ }
+
+ @Override
+ public Void doInBackground(Void... args) {
+ NdefMessage m;
+ Uri[] uris;
+ UserHandle userHandle;
+ boolean result = false;
+
+ synchronized (P2pLinkManager.this) {
+ if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) {
+ return null;
+ }
+ m = mMessageToSend;
+ uris = mUrisToSend;
+ userHandle = mUserHandle;
+ snepClient = mSnepClient;
+ handoverClient = mHandoverClient;
+ nppClient = mNdefPushClient;
+ }
+
+ long time = SystemClock.elapsedRealtime();
+
+ if (uris != null) {
+ if (DBG) Log.d(TAG, "Trying handover request");
+ try {
+ int handoverResult = doHandover(uris, userHandle);
+ switch (handoverResult) {
+ case HANDOVER_SUCCESS:
+ result = true;
+ break;
+ case HANDOVER_FAILURE:
+ result = false;
+ break;
+ case HANDOVER_UNSUPPORTED:
+ result = false;
+ onHandoverUnsupported();
+ break;
+ case HANDOVER_BUSY:
+ result = false;
+ onHandoverBusy();
+ break;
+ }
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+
+ if (!result && m != null && snepClient != null) {
+ if (DBG) Log.d(TAG, "Sending ndef via SNEP");
+ try {
+ int snepResult = doSnepProtocol(m);
+ switch (snepResult) {
+ case SNEP_SUCCESS:
+ result = true;
+ break;
+ case SNEP_FAILURE:
+ result = false;
+ break;
+ default:
+ result = false;
+ }
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+
+ if (!result && m != null && nppClient != null) {
+ result = nppClient.push(m);
+ }
+
+ time = SystemClock.elapsedRealtime() - time;
+ if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time);
+ if (result) {
+ onSendComplete(m, time);
+ }
+
+ return null;
+ }
+ };
+
+
+ final HandoverServer.Callback mHandoverCallback = new HandoverServer.Callback() {
+ @Override
+ public void onHandoverRequestReceived() {
+ onReceiveHandover();
+ }
+
+ @Override
+ public void onHandoverBusy() {
+ P2pLinkManager.this.onHandoverBusy();
+ }
+ };
+
+ final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() {
+ @Override
+ public void onMessageReceived(NdefMessage msg) {
+ onReceiveComplete(msg);
+ }
+ };
+
+ final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() {
+ @Override
+ public SnepMessage doPut(NdefMessage msg) {
+ onReceiveComplete(msg);
+ return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);
+ }
+
+ @Override
+ public SnepMessage doGet(int acceptableLength, NdefMessage msg) {
+ // The NFC Forum Default SNEP server is not allowed to respond to
+ // SNEP GET requests - see SNEP 1.0 TS section 6.1. However,
+ // since Android 4.1 used the NFC Forum default server to
+ // implement connection handover, we will support this
+ // until we can deprecate it.
+ NdefMessage response = mHandoverDataParser.getIncomingHandoverData(msg).handoverSelect;
+ if (response != null) {
+ onReceiveHandover();
+ return SnepMessage.getSuccessResponse(response);
+ } else {
+ return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED);
+ }
+ }
+ };
+
+ void onReceiveHandover() {
+ mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget();
+ }
+
+ void onReceiveComplete(NdefMessage msg) {
+ // Make callbacks on UI thread
+ mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget();
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_ECHOSERVER:
+ synchronized (this) {
+ mEchoServer.start();
+ break;
+ }
+ case MSG_STOP_ECHOSERVER:
+ synchronized (this) {
+ mEchoServer.stop();
+ break;
+ }
+ case MSG_WAIT_FOR_LINK_TIMEOUT:
+ synchronized (this) {
+ // User wanted to send something but no link
+ // came up. Just cancel the send
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ mEventListener.onP2pTimeoutWaitingForLink();
+ }
+ break;
+ case MSG_DEBOUNCE_TIMEOUT:
+ synchronized (this) {
+ if (mLinkState != LINK_STATE_DEBOUNCE) {
+ break;
+ }
+ if (DBG) Log.d(TAG, "Debounce timeout");
+ mLinkState = LINK_STATE_DOWN;
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ mMessageToSend = null;
+ mUrisToSend = null;
+ if (DBG) Log.d(TAG, "onP2pOutOfRange()");
+ mEventListener.onP2pOutOfRange();
+ }
+ break;
+ case MSG_RECEIVE_HANDOVER:
+ // We're going to do a handover request
+ synchronized (this) {
+ if (mLinkState == LINK_STATE_DOWN) {
+ break;
+ }
+ if (mSendState == SEND_STATE_SENDING) {
+ cancelSendNdefMessage();
+ }
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
+ mEventListener.onP2pReceiveComplete(false);
+ }
+ break;
+ case MSG_RECEIVE_COMPLETE:
+ NdefMessage m = (NdefMessage) msg.obj;
+ synchronized (this) {
+ if (mLinkState == LINK_STATE_DOWN) {
+ break;
+ }
+ if (mSendState == SEND_STATE_SENDING) {
+ cancelSendNdefMessage();
+ }
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ if (DBG) Log.d(TAG, "onP2pReceiveComplete()");
+ mEventListener.onP2pReceiveComplete(true);
+ NfcService.getInstance().sendMockNdefTag(m);
+ }
+ break;
+ case MSG_HANDOVER_NOT_SUPPORTED:
+ synchronized (P2pLinkManager.this) {
+ mSendTask = null;
+
+ if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
+ break;
+ }
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ if (DBG) Log.d(TAG, "onP2pHandoverNotSupported()");
+ mEventListener.onP2pHandoverNotSupported();
+ }
+ break;
+ case MSG_SEND_COMPLETE:
+ synchronized (P2pLinkManager.this) {
+ mSendTask = null;
+
+ if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
+ break;
+ }
+ mSendState = SEND_STATE_COMPLETE;
+ mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT);
+ if (DBG) Log.d(TAG, "onP2pSendComplete()");
+ mEventListener.onP2pSendComplete();
+ if (mCallbackNdef != null) {
+ try {
+ mCallbackNdef.onNdefPushComplete(mPeerLlcpVersion);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed NDEF completed callback: " + e.getMessage());
+ }
+ }
+ }
+ break;
+ case MSG_HANDOVER_BUSY:
+ synchronized (P2pLinkManager.this) {
+ mSendTask = null;
+
+ if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) {
+ break;
+ }
+ mSendState = SEND_STATE_NOTHING_TO_SEND;
+ if (DBG) Log.d(TAG, "onP2pHandoverBusy()");
+ mEventListener.onP2pHandoverBusy();
+ }
+ }
+ return true;
+ }
+
+
+ @Override
+ public void onP2pSendConfirmed() {
+ onP2pSendConfirmed(true);
+ }
+
+ private void onP2pSendConfirmed(boolean requireConfirmation) {
+ if (DBG) Log.d(TAG, "onP2pSendConfirmed()");
+ synchronized (this) {
+ if (mLinkState == LINK_STATE_DOWN || (requireConfirmation
+ && mSendState != SEND_STATE_NEED_CONFIRMATION)) {
+ return;
+ }
+ mSendState = SEND_STATE_SENDING;
+ if (mLinkState == LINK_STATE_UP) {
+ if (mLlcpServicesConnected) {
+ sendNdefMessage();
+ } // else, will send messages when link comes up
+ } else if (mLinkState == LINK_STATE_DEBOUNCE) {
+ // Restart debounce timeout and tell user to tap again
+ scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, LINK_SEND_CONFIRMED_DEBOUNCE_MS);
+ mEventListener.onP2pSendDebounce();
+ }
+ }
+ }
+
+
+ @Override
+ public void onP2pCanceled() {
+ synchronized (this) {
+ mSendState = SEND_STATE_CANCELED;
+ if (mLinkState == LINK_STATE_DOWN) {
+ // If we were waiting for the link to come up, stop doing so
+ mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT);
+ } else if (mLinkState == LINK_STATE_DEBOUNCE) {
+ // We're in debounce state so link is down. Reschedule debounce
+ // timeout to occur sooner, we don't want to wait any longer.
+ scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, LINK_SEND_CANCELED_DEBOUNCE_MS);
+ } else {
+ // Link is up, nothing else to do but wait for link to go down
+ }
+ }
+ }
+
+ void scheduleTimeoutLocked(int what, int timeout) {
+ // Cancel any outstanding debounce timeouts.
+ mHandler.removeMessages(what);
+ mHandler.sendEmptyMessageDelayed(what, timeout);
+ }
+
+ static String sendStateToString(int state) {
+ switch (state) {
+ case SEND_STATE_NOTHING_TO_SEND:
+ return "SEND_STATE_NOTHING_TO_SEND";
+ case SEND_STATE_NEED_CONFIRMATION:
+ return "SEND_STATE_NEED_CONFIRMATION";
+ case SEND_STATE_SENDING:
+ return "SEND_STATE_SENDING";
+ case SEND_STATE_COMPLETE:
+ return "SEND_STATE_COMPLETE";
+ case SEND_STATE_CANCELED:
+ return "SEND_STATE_CANCELED";
+ default:
+ return "";
+ }
+ }
+
+ static String linkStateToString(int state) {
+ switch (state) {
+ case LINK_STATE_DOWN:
+ return "LINK_STATE_DOWN";
+ case LINK_STATE_DEBOUNCE:
+ return "LINK_STATE_DEBOUNCE";
+ case LINK_STATE_UP:
+ return "LINK_STATE_UP";
+ default:
+ return "";
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ synchronized (this) {
+ pw.println("mIsSendEnabled=" + mIsSendEnabled);
+ pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled);
+ pw.println("mLinkState=" + linkStateToString(mLinkState));
+ pw.println("mSendState=" + sendStateToString(mSendState));
+
+ pw.println("mCallbackNdef=" + mCallbackNdef);
+ pw.println("mMessageToSend=" + mMessageToSend);
+ pw.println("mUrisToSend=" + mUrisToSend);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/RegisteredComponentCache.java b/NfcSony/src/com/android/nfc/RegisteredComponentCache.java
new file mode 100644
index 0000000..8d73317
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/RegisteredComponentCache.java
@@ -0,0 +1,225 @@
+/*
+ * 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.nfc;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A cache of intent filters registered to receive the TECH_DISCOVERED dispatch.
+ */
+public class RegisteredComponentCache {
+ private static final String TAG = "RegisteredComponentCache";
+ private static final boolean DEBUG = false;
+
+ final Context mContext;
+ final String mAction;
+ final String mMetaDataName;
+ final AtomicReference mReceiver;
+
+ // synchronized on this
+ private ArrayList mComponents;
+
+ public RegisteredComponentCache(Context context, String action, String metaDataName) {
+ mContext = context;
+ mAction = action;
+ mMetaDataName = metaDataName;
+
+ generateComponentsList();
+
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context1, Intent intent) {
+ generateComponentsList();
+ }
+ };
+ mReceiver = new AtomicReference(receiver);
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(receiver, UserHandle.ALL, intentFilter, null, null);
+ // Register for events related to sdcard installation.
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiverAsUser(receiver, UserHandle.ALL, sdFilter, null, null);
+ // Generate a new list upon switching users as well
+ IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiverAsUser(receiver, UserHandle.ALL, userFilter, null, null);
+ }
+
+ public static class ComponentInfo {
+ public final ResolveInfo resolveInfo;
+ public final String[] techs;
+
+ ComponentInfo(ResolveInfo resolveInfo, String[] techs) {
+ this.resolveInfo = resolveInfo;
+ this.techs = techs;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder("ComponentInfo: ");
+ out.append(resolveInfo);
+ out.append(", techs: ");
+ for (String tech : techs) {
+ out.append(tech);
+ out.append(", ");
+ }
+ return out.toString();
+ }
+ }
+
+ /**
+ * @return a collection of {@link RegisteredComponentCache.ComponentInfo} objects for all
+ * registered authenticators.
+ */
+ public ArrayList getComponents() {
+ synchronized (this) {
+ // It's safe to return a reference here since mComponents is always replaced and
+ // never updated when it changes.
+ return mComponents;
+ }
+ }
+
+ /**
+ * Stops the monitoring of package additions, removals and changes.
+ */
+ public void close() {
+ final BroadcastReceiver receiver = mReceiver.getAndSet(null);
+ if (receiver != null) {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mReceiver.get() != null) {
+ Log.e(TAG, "RegisteredServicesCache finalized without being closed");
+ }
+ close();
+ super.finalize();
+ }
+
+ void dump(ArrayList components) {
+ for (ComponentInfo component : components) {
+ Log.i(TAG, component.toString());
+ }
+ }
+
+ void generateComponentsList() {
+ PackageManager pm;
+ try {
+ UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
+ pm = mContext.createPackageContextAsUser("android", 0,
+ currentUser).getPackageManager();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not create user package context");
+ return;
+ }
+ ArrayList components = new ArrayList();
+ List resolveInfos = pm.queryIntentActivitiesAsUser(new Intent(mAction),
+ PackageManager.GET_META_DATA, ActivityManager.getCurrentUser());
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ try {
+ parseComponentInfo(pm, resolveInfo, components);
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e);
+ }
+ }
+
+ if (DEBUG) {
+ dump(components);
+ }
+
+ synchronized (this) {
+ mComponents = components;
+ }
+ }
+
+ void parseComponentInfo(PackageManager pm, ResolveInfo info,
+ ArrayList components) throws XmlPullParserException, IOException {
+ ActivityInfo ai = info.activityInfo;
+
+ XmlResourceParser parser = null;
+ try {
+ parser = ai.loadXmlMetaData(pm, mMetaDataName);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + mMetaDataName + " meta-data");
+ }
+
+ parseTechLists(pm.getResourcesForApplication(ai.applicationInfo), ai.packageName,
+ parser, info, components);
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to load resources for " + ai.packageName);
+ } finally {
+ if (parser != null) parser.close();
+ }
+ }
+
+ void parseTechLists(Resources res, String packageName, XmlPullParser parser,
+ ResolveInfo resolveInfo, ArrayList components)
+ throws XmlPullParserException, IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG) {
+ eventType = parser.next();
+ }
+
+ ArrayList items = new ArrayList();
+ String tagName;
+ eventType = parser.next();
+ do {
+ tagName = parser.getName();
+ if (eventType == XmlPullParser.START_TAG && "tech".equals(tagName)) {
+ items.add(parser.nextText());
+ } else if (eventType == XmlPullParser.END_TAG && "tech-list".equals(tagName)) {
+ int size = items.size();
+ if (size > 0) {
+ String[] techs = new String[size];
+ techs = items.toArray(techs);
+ items.clear();
+ components.add(new ComponentInfo(resolveInfo, techs));
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/ScreenStateHelper.java b/NfcSony/src/com/android/nfc/ScreenStateHelper.java
new file mode 100644
index 0000000..b044c28
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ScreenStateHelper.java
@@ -0,0 +1,52 @@
+package com.android.nfc;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.PowerManager;
+
+/**
+ * Helper class for determining the current screen state for NFC activities.
+ */
+class ScreenStateHelper {
+
+ static final int SCREEN_STATE_UNKNOWN = 0;
+ static final int SCREEN_STATE_OFF = 1;
+ static final int SCREEN_STATE_ON_LOCKED = 2;
+ static final int SCREEN_STATE_ON_UNLOCKED = 3;
+
+ private final PowerManager mPowerManager;
+ private final KeyguardManager mKeyguardManager;
+
+ ScreenStateHelper(Context context) {
+ mKeyguardManager = (KeyguardManager)
+ context.getSystemService(Context.KEYGUARD_SERVICE);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ }
+
+ int checkScreenState() {
+ //TODO: fix deprecated api
+ if (!mPowerManager.isScreenOn()) {
+ return SCREEN_STATE_OFF;
+ } else if (mKeyguardManager.isKeyguardLocked()) {
+ return SCREEN_STATE_ON_LOCKED;
+ } else {
+ return SCREEN_STATE_ON_UNLOCKED;
+ }
+ }
+
+ /**
+ * For debugging only - no i18n
+ */
+ static String screenStateToString(int screenState) {
+ switch (screenState) {
+ case SCREEN_STATE_OFF:
+ return "OFF";
+ case SCREEN_STATE_ON_LOCKED:
+ return "ON_LOCKED";
+ case SCREEN_STATE_ON_UNLOCKED:
+ return "ON_UNLOCKED";
+ default:
+ return "UNKNOWN";
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/TechListChooserActivity.java b/NfcSony/src/com/android/nfc/TechListChooserActivity.java
new file mode 100644
index 0000000..8c2c34e
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/TechListChooserActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008 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.internal.app.ResolverActivity;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class TechListChooserActivity extends ResolverActivity {
+ public static final String EXTRA_RESOLVE_INFOS = "rlist";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Intent intent = getIntent();
+ Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+ if (!(targetParcelable instanceof Intent)) {
+ Log.w("TechListChooserActivity", "Target is not an intent: " + targetParcelable);
+ finish();
+ return;
+ }
+ Intent target = (Intent)targetParcelable;
+ ArrayList rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS);
+ CharSequence title = getResources().getText(com.android.internal.R.string.chooseActivity);
+ super.onCreate(savedInstanceState, target, title, null, rList, false);
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BeamManager.java b/NfcSony/src/com/android/nfc/beam/BeamManager.java
new file mode 100644
index 0000000..87ea881
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamManager.java
@@ -0,0 +1,142 @@
+/*
+* Copyright (C) 2008 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.beam;
+
+import com.android.nfc.NfcService;
+import com.android.nfc.handover.HandoverDataParser;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Manager for starting and stopping Beam transfers. Prevents more than one transfer from
+ * happening at a time.
+ */
+public class BeamManager implements Handler.Callback {
+ private static final String TAG = "BeamManager";
+ private static final boolean DBG = false;
+
+ private static final String ACTION_WHITELIST_DEVICE =
+ "android.btopp.intent.action.WHITELIST_DEVICE";
+ public static final int MSG_BEAM_COMPLETE = 0;
+
+ private final Object mLock;
+
+ private boolean mBeamInProgress;
+ private final Handler mCallback;
+
+ private NfcService mNfcService;
+
+ private static final class Singleton {
+ public static final BeamManager INSTANCE = new BeamManager();
+ }
+
+ private BeamManager() {
+ mLock = new Object();
+ mBeamInProgress = false;
+ mCallback = new Handler(Looper.getMainLooper(), this);
+ mNfcService = NfcService.getInstance();
+ }
+
+ public static BeamManager getInstance() {
+ return Singleton.INSTANCE;
+ }
+
+ public boolean isBeamInProgress() {
+ synchronized (mLock) {
+ return mBeamInProgress;
+ }
+ }
+
+ public boolean startBeamReceive(Context context,
+ HandoverDataParser.BluetoothHandoverData handoverData) {
+ synchronized (mLock) {
+ if (mBeamInProgress) {
+ return false;
+ } else {
+ mBeamInProgress = true;
+ }
+ }
+
+ BeamTransferRecord transferRecord =
+ BeamTransferRecord.forBluetoothDevice(
+ handoverData.device, handoverData.carrierActivating, null);
+
+ Intent receiveIntent = new Intent(context.getApplicationContext(),
+ BeamReceiveService.class);
+ receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
+ receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK,
+ new Messenger(mCallback));
+ whitelistOppDevice(context, handoverData.device);
+ context.startServiceAsUser(receiveIntent, UserHandle.CURRENT);
+ return true;
+ }
+
+ public boolean startBeamSend(Context context,
+ HandoverDataParser.BluetoothHandoverData outgoingHandoverData,
+ Uri[] uris, UserHandle userHandle) {
+ synchronized (mLock) {
+ if (mBeamInProgress) {
+ return false;
+ } else {
+ mBeamInProgress = true;
+ }
+ }
+
+ BeamTransferRecord transferRecord = BeamTransferRecord.forBluetoothDevice(
+ outgoingHandoverData.device, outgoingHandoverData.carrierActivating,
+ uris);
+ Intent sendIntent = new Intent(context.getApplicationContext(),
+ BeamSendService.class);
+ sendIntent.putExtra(BeamSendService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord);
+ sendIntent.putExtra(BeamSendService.EXTRA_BEAM_COMPLETE_CALLBACK,
+ new Messenger(mCallback));
+ context.startServiceAsUser(sendIntent, userHandle);
+ return true;
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_BEAM_COMPLETE) {
+ synchronized (mLock) {
+ mBeamInProgress = false;
+ }
+
+ boolean success = msg.arg1 == 1;
+ if (success) {
+ mNfcService.playSound(NfcService.SOUND_END);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void whitelistOppDevice(Context context, BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
+ Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ context.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java b/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java
new file mode 100644
index 0000000..e94f8bf
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java
@@ -0,0 +1,163 @@
+package com.android.nfc.beam;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+
+/**
+ * @hide
+ */
+public class BeamReceiveService extends Service implements BeamTransferManager.Callback {
+ private static String TAG = "BeamReceiveService";
+ private static boolean DBG = true;
+
+ public static final String EXTRA_BEAM_TRANSFER_RECORD
+ = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD";
+ public static final String EXTRA_BEAM_COMPLETE_CALLBACK
+ = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK";
+
+ private BeamStatusReceiver mBeamStatusReceiver;
+ private boolean mBluetoothEnabledByNfc;
+ private int mStartId;
+ private BeamTransferManager mTransferManager;
+ private Messenger mCompleteCallback;
+
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ if (state == BluetoothAdapter.STATE_OFF) {
+ mBluetoothEnabledByNfc = false;
+ }
+ }
+ }
+ };
+
+ public BeamReceiveService() {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ mStartId = startId;
+
+ BeamTransferRecord transferRecord;
+ if (intent == null ||
+ (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) {
+ if (DBG) Log.e(TAG, "No transfer record provided. Stopping.");
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+
+ mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK);
+
+ if (prepareToReceive(transferRecord)) {
+ if (DBG) Log.i(TAG, "Ready for incoming Beam transfer");
+ return START_STICKY;
+ } else {
+ invokeCompleteCallback(false);
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+ }
+
+ // TODO: figure out a way to not duplicate this code
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // register BT state receiver
+ IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ registerReceiver(mBluetoothStateReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mBeamStatusReceiver != null) {
+ unregisterReceiver(mBeamStatusReceiver);
+ }
+ unregisterReceiver(mBluetoothStateReceiver);
+ mTransferManager = null;
+ }
+
+ boolean prepareToReceive(BeamTransferRecord transferRecord) {
+ if (mTransferManager != null) {
+ return false;
+ }
+
+ if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
+ // only support BT
+ return false;
+ }
+
+ if (!mBluetoothAdapter.isEnabled()) {
+ if (!mBluetoothAdapter.enableNoAutoConnect()) {
+ Log.e(TAG, "Error enabling Bluetooth.");
+ return false;
+ }
+ mBluetoothEnabledByNfc = true;
+ if (DBG) Log.d(TAG, "Queueing out transfer "
+ + Integer.toString(transferRecord.id));
+ }
+
+ mTransferManager = new BeamTransferManager(this, this, transferRecord, true);
+
+ // register Beam status receiver
+ mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager);
+ registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(),
+ BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler());
+
+ mTransferManager.start();
+ mTransferManager.updateNotification();
+ return true;
+ }
+
+ private void invokeCompleteCallback(boolean success) {
+ if (mCompleteCallback != null) {
+ try {
+ Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE);
+ msg.arg1 = success ? 1 : 0;
+ mCompleteCallback.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to invoke Beam complete callback", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTransferComplete(BeamTransferManager transfer, boolean success) {
+ // Play success sound
+ if (!success) {
+ if (DBG) Log.d(TAG, "Transfer failed, final state: " +
+ Integer.toString(transfer.mState));
+ }
+
+ if (mBluetoothEnabledByNfc) {
+ mBluetoothEnabledByNfc = false;
+ mBluetoothAdapter.disable();
+ }
+
+ invokeCompleteCallback(success);
+ stopSelf(mStartId);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BeamSendService.java b/NfcSony/src/com/android/nfc/beam/BeamSendService.java
new file mode 100644
index 0000000..6c013ea
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamSendService.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 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.beam;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class BeamSendService extends Service implements BeamTransferManager.Callback {
+ private static String TAG = "BeamSendService";
+ private static boolean DBG = true;
+
+ public static String EXTRA_BEAM_TRANSFER_RECORD
+ = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD";
+ public static final String EXTRA_BEAM_COMPLETE_CALLBACK
+ = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK";
+
+ private BeamTransferManager mTransferManager;
+ private BeamStatusReceiver mBeamStatusReceiver;
+ private boolean mBluetoothEnabledByNfc;
+ private Messenger mCompleteCallback;
+ private int mStartId;
+
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ handleBluetoothStateChanged(intent);
+ }
+ }
+ };
+
+ public BeamSendService() {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // register BT state receiver
+ IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ registerReceiver(mBluetoothStateReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (mBeamStatusReceiver != null) {
+ unregisterReceiver(mBeamStatusReceiver);
+ }
+ unregisterReceiver(mBluetoothStateReceiver);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ mStartId = startId;
+
+ BeamTransferRecord transferRecord;
+ if (intent == null ||
+ (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) {
+ if (DBG) Log.e(TAG, "No transfer record provided. Stopping.");
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+
+ mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK);
+
+ if (doTransfer(transferRecord)) {
+ if (DBG) Log.i(TAG, "Starting outgoing Beam transfer");
+ return START_STICKY;
+ } else {
+ invokeCompleteCallback(false);
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+ }
+
+ boolean doTransfer(BeamTransferRecord transferRecord) {
+ if (createBeamTransferManager(transferRecord)) {
+ // register Beam status receiver
+ mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager);
+ registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(),
+ BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler());
+
+ if (transferRecord.dataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
+ if (mBluetoothAdapter.isEnabled()) {
+ // Start the transfer
+ mTransferManager.start();
+ } else {
+ if (!mBluetoothAdapter.enableNoAutoConnect()) {
+ Log.e(TAG, "Error enabling Bluetooth.");
+ mTransferManager = null;
+ return false;
+ }
+ mBluetoothEnabledByNfc = true;
+ if (DBG) Log.d(TAG, "Queueing out transfer "
+ + Integer.toString(transferRecord.id));
+ }
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ boolean createBeamTransferManager(BeamTransferRecord transferRecord) {
+ if (mTransferManager != null) {
+ return false;
+ }
+
+ if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
+ // only support BT
+ return false;
+ }
+
+ mTransferManager = new BeamTransferManager(this, this, transferRecord, false);
+ mTransferManager.updateNotification();
+ return true;
+ }
+
+ private void handleBluetoothStateChanged(Intent intent) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ if (state == BluetoothAdapter.STATE_ON) {
+ if (mTransferManager != null &&
+ mTransferManager.mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
+ mTransferManager.start();
+ }
+ } else if (state == BluetoothAdapter.STATE_OFF) {
+ mBluetoothEnabledByNfc = false;
+ }
+ }
+
+ private void invokeCompleteCallback(boolean success) {
+ if (mCompleteCallback != null) {
+ try {
+ Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE);
+ msg.arg1 = success ? 1 : 0;
+ mCompleteCallback.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to invoke Beam complete callback", e);
+ }
+ }
+ }
+
+ @Override
+ public void onTransferComplete(BeamTransferManager transfer, boolean success) {
+ // Play success sound
+ if (!success) {
+ if (DBG) Log.d(TAG, "Transfer failed, final state: " +
+ Integer.toString(transfer.mState));
+ }
+
+ if (mBluetoothEnabledByNfc) {
+ mBluetoothEnabledByNfc = false;
+ mBluetoothAdapter.disable();
+ }
+
+ invokeCompleteCallback(success);
+ stopSelf(mStartId);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java b/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java
new file mode 100644
index 0000000..67b5b82
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java
@@ -0,0 +1,155 @@
+package com.android.nfc.beam;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * @hide
+ */
+public class BeamStatusReceiver extends BroadcastReceiver {
+ private static final boolean DBG = true;
+ private static final String TAG = "BeamStatusReceiver";
+
+ private static final String ACTION_HANDOVER_STARTED =
+ "android.nfc.handover.intent.action.HANDOVER_STARTED";
+
+ private static final String ACTION_TRANSFER_PROGRESS =
+ "android.nfc.handover.intent.action.TRANSFER_PROGRESS";
+
+ private static final String ACTION_TRANSFER_DONE =
+ "android.nfc.handover.intent.action.TRANSFER_DONE";
+
+ private static final String EXTRA_HANDOVER_DATA_LINK_TYPE =
+ "android.nfc.handover.intent.extra.HANDOVER_DATA_LINK_TYPE";
+
+
+ private static final String EXTRA_TRANSFER_PROGRESS =
+ "android.nfc.handover.intent.extra.TRANSFER_PROGRESS";
+
+ private static final String EXTRA_TRANSFER_URI =
+ "android.nfc.handover.intent.extra.TRANSFER_URI";
+
+ private static final String EXTRA_OBJECT_COUNT =
+ "android.nfc.handover.intent.extra.OBJECT_COUNT";
+
+ private static final String EXTRA_TRANSFER_STATUS =
+ "android.nfc.handover.intent.extra.TRANSFER_STATUS";
+
+ private static final String EXTRA_TRANSFER_MIMETYPE =
+ "android.nfc.handover.intent.extra.TRANSFER_MIME_TYPE";
+
+ private static final String ACTION_STOP_BLUETOOTH_TRANSFER =
+ "android.btopp.intent.action.STOP_HANDOVER_TRANSFER";
+
+ // FIXME: Needs to stay in sync with com.android.bluetooth.opp.Constants
+ private static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+ private static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+
+ // permission needed to be able to receive handover status requests
+ public static final String BEAM_STATUS_PERMISSION =
+ "android.permission.NFC_HANDOVER_STATUS";
+
+ // Needed to build cancel intent in Beam notification
+ public static final String EXTRA_INCOMING =
+ "com.android.nfc.handover.extra.INCOMING";
+
+ public static final String EXTRA_TRANSFER_ID =
+ "android.nfc.handover.intent.extra.TRANSFER_ID";
+
+ public static final String EXTRA_ADDRESS =
+ "android.nfc.handover.intent.extra.ADDRESS";
+
+ public static final String ACTION_CANCEL_HANDOVER_TRANSFER =
+ "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER";
+
+ public static final int DIRECTION_INCOMING = 0;
+ public static final int DIRECTION_OUTGOING = 1;
+
+ private final Context mContext;
+ private final BeamTransferManager mTransferManager;
+
+ BeamStatusReceiver(Context context, BeamTransferManager transferManager) {
+ mContext = context;
+ mTransferManager = transferManager;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ int dataLinkType = intent.getIntExtra(EXTRA_HANDOVER_DATA_LINK_TYPE,
+ BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH);
+
+ if (ACTION_CANCEL_HANDOVER_TRANSFER.equals(action)) {
+ if (mTransferManager != null) {
+ mTransferManager.cancel();
+ }
+ } else if (ACTION_TRANSFER_PROGRESS.equals(action) ||
+ ACTION_TRANSFER_DONE.equals(action) ||
+ ACTION_HANDOVER_STARTED.equals(action)) {
+ handleTransferEvent(intent, dataLinkType);
+ }
+ }
+
+ public IntentFilter getIntentFilter() {
+ IntentFilter filter = new IntentFilter(ACTION_TRANSFER_DONE);
+ filter.addAction(ACTION_TRANSFER_PROGRESS);
+ filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER);
+ filter.addAction(ACTION_HANDOVER_STARTED);
+ return filter;
+ }
+
+ private void handleTransferEvent(Intent intent, int deviceType) {
+ String action = intent.getAction();
+ int id = intent.getIntExtra(EXTRA_TRANSFER_ID, -1);
+
+ String sourceAddress = intent.getStringExtra(EXTRA_ADDRESS);
+
+ if (sourceAddress == null) return;
+
+ if (mTransferManager == null) {
+ // There is no transfer running for this source address; most likely
+ // the transfer was cancelled. We need to tell BT OPP to stop transferring.
+ if (id != -1) {
+ if (deviceType == BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH) {
+ if (DBG) Log.d(TAG, "Didn't find transfer, stopping");
+ Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER);
+ cancelIntent.putExtra(EXTRA_TRANSFER_ID, id);
+ mContext.sendBroadcast(cancelIntent);
+ }
+ }
+ return;
+ }
+
+ mTransferManager.setBluetoothTransferId(id);
+
+ if (action.equals(ACTION_TRANSFER_DONE)) {
+ int handoverStatus = intent.getIntExtra(EXTRA_TRANSFER_STATUS,
+ HANDOVER_TRANSFER_STATUS_FAILURE);
+ if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) {
+ String uriString = intent.getStringExtra(EXTRA_TRANSFER_URI);
+ String mimeType = intent.getStringExtra(EXTRA_TRANSFER_MIMETYPE);
+ Uri uri = Uri.parse(uriString);
+ if (uri != null && uri.getScheme() == null) {
+ uri = Uri.fromFile(new File(uri.getPath()));
+ }
+ mTransferManager.finishTransfer(true, uri, mimeType);
+ } else {
+ mTransferManager.finishTransfer(false, null, null);
+ }
+ } else if (action.equals(ACTION_TRANSFER_PROGRESS)) {
+ float progress = intent.getFloatExtra(EXTRA_TRANSFER_PROGRESS, 0.0f);
+ mTransferManager.updateFileProgress(progress);
+ } else if (action.equals(ACTION_HANDOVER_STARTED)) {
+ int count = intent.getIntExtra(EXTRA_OBJECT_COUNT, 0);
+ if (count > 0) {
+ mTransferManager.setObjectCount(count);
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java b/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java
new file mode 100644
index 0000000..d0914bf
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2012 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.beam;
+
+import com.android.nfc.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Notification.Builder;
+import android.bluetooth.BluetoothDevice;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * A BeamTransferManager object represents a set of files
+ * that were received through NFC connection handover
+ * from the same source address.
+ *
+ * It manages starting, stopping, and processing the transfer, as well
+ * as the user visible notification.
+ *
+ * For Bluetooth, files are received through OPP, and
+ * we have no knowledge how many files will be transferred
+ * as part of a single transaction.
+ * Hence, a transfer has a notion of being "alive": if
+ * the last update to a transfer was within WAIT_FOR_NEXT_TRANSFER_MS
+ * milliseconds, we consider a new file transfer from the
+ * same source address as part of the same transfer.
+ * The corresponding URIs will be grouped in a single folder.
+ *
+ * @hide
+ */
+
+public class BeamTransferManager implements Handler.Callback,
+ MediaScannerConnection.OnScanCompletedListener {
+ interface Callback {
+
+ void onTransferComplete(BeamTransferManager transfer, boolean success);
+ };
+ static final String TAG = "BeamTransferManager";
+
+ static final Boolean DBG = true;
+
+ // In the states below we still accept new file transfer
+ static final int STATE_NEW = 0;
+ static final int STATE_IN_PROGRESS = 1;
+ static final int STATE_W4_NEXT_TRANSFER = 2;
+ // In the states below no new files are accepted.
+ static final int STATE_W4_MEDIA_SCANNER = 3;
+ static final int STATE_FAILED = 4;
+ static final int STATE_SUCCESS = 5;
+ static final int STATE_CANCELLED = 6;
+ static final int STATE_CANCELLING = 7;
+ static final int MSG_NEXT_TRANSFER_TIMER = 0;
+
+ static final int MSG_TRANSFER_TIMEOUT = 1;
+ static final int DATA_LINK_TYPE_BLUETOOTH = 1;
+
+ // We need to receive an update within this time period
+ // to still consider this transfer to be "alive" (ie
+ // a reason to keep the handover transport enabled).
+ static final int ALIVE_CHECK_MS = 20000;
+
+ // The amount of time to wait for a new transfer
+ // once the current one completes.
+ static final int WAIT_FOR_NEXT_TRANSFER_MS = 4000;
+
+ static final String BEAM_DIR = "beam";
+
+ static final String ACTION_WHITELIST_DEVICE =
+ "android.btopp.intent.action.WHITELIST_DEVICE";
+
+ static final String ACTION_STOP_BLUETOOTH_TRANSFER =
+ "android.btopp.intent.action.STOP_HANDOVER_TRANSFER";
+
+ final boolean mIncoming; // whether this is an incoming transfer
+
+ final int mTransferId; // Unique ID of this transfer used for notifications
+ int mBluetoothTransferId; // ID of this transfer in Bluetooth namespace
+
+ final PendingIntent mCancelIntent;
+ final Context mContext;
+ final Handler mHandler;
+ final NotificationManager mNotificationManager;
+ final BluetoothDevice mRemoteDevice;
+ final Callback mCallback;
+ final boolean mRemoteActivating;
+
+ // Variables below are only accessed on the main thread
+ int mState;
+ int mCurrentCount;
+ int mSuccessCount;
+ int mTotalCount;
+ int mDataLinkType;
+ boolean mCalledBack;
+ Long mLastUpdate; // Last time an event occurred for this transfer
+ float mProgress; // Progress in range [0..1]
+ ArrayList mUris; // Received uris from transport
+ ArrayList mTransferMimeTypes; // Mime-types received from transport
+ Uri[] mOutgoingUris; // URIs to send
+ ArrayList mPaths; // Raw paths on the filesystem for Beam-stored files
+ HashMap mMimeTypes; // Mime-types associated with each path
+ HashMap mMediaUris; // URIs found by the media scanner for each path
+ int mUrisScanned;
+ Long mStartTime;
+
+ public BeamTransferManager(Context context, Callback callback,
+ BeamTransferRecord pendingTransfer, boolean incoming) {
+ mContext = context;
+ mCallback = callback;
+ mRemoteDevice = pendingTransfer.remoteDevice;
+ mIncoming = incoming;
+ mTransferId = pendingTransfer.id;
+ mBluetoothTransferId = -1;
+ mDataLinkType = pendingTransfer.dataLinkType;
+ mRemoteActivating = pendingTransfer.remoteActivating;
+ mStartTime = 0L;
+ // For incoming transfers, count can be set later
+ mTotalCount = (pendingTransfer.uris != null) ? pendingTransfer.uris.length : 0;
+ mLastUpdate = SystemClock.elapsedRealtime();
+ mProgress = 0.0f;
+ mState = STATE_NEW;
+ mUris = pendingTransfer.uris == null
+ ? new ArrayList()
+ : new ArrayList(Arrays.asList(pendingTransfer.uris));
+ mTransferMimeTypes = new ArrayList();
+ mMimeTypes = new HashMap();
+ mPaths = new ArrayList();
+ mMediaUris = new HashMap();
+ mCancelIntent = buildCancelIntent();
+ mUrisScanned = 0;
+ mCurrentCount = 0;
+ mSuccessCount = 0;
+ mOutgoingUris = pendingTransfer.uris;
+ mHandler = new Handler(Looper.getMainLooper(), this);
+ mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ }
+
+ void whitelistOppDevice(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP");
+ Intent intent = new Intent(ACTION_WHITELIST_DEVICE);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ }
+
+ public void start() {
+ if (mStartTime > 0) {
+ // already started
+ return;
+ }
+
+ mStartTime = System.currentTimeMillis();
+
+ if (!mIncoming) {
+ if (mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) {
+ new BluetoothOppHandover(mContext, mRemoteDevice, mUris, mRemoteActivating).start();
+ }
+ }
+ }
+
+ public void updateFileProgress(float progress) {
+ if (!isRunning()) return; // Ignore when we're no longer running
+
+ mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
+
+ this.mProgress = progress;
+
+ // We're still receiving data from this device - keep it in
+ // the whitelist for a while longer
+ if (mIncoming && mRemoteDevice != null) whitelistOppDevice(mRemoteDevice);
+
+ updateStateAndNotification(STATE_IN_PROGRESS);
+ }
+
+ public synchronized void setBluetoothTransferId(int id) {
+ if (mBluetoothTransferId == -1 && id != -1) {
+ mBluetoothTransferId = id;
+ if (mState == STATE_CANCELLING) {
+ sendBluetoothCancelIntentAndUpdateState();
+ }
+ }
+ }
+
+ public void finishTransfer(boolean success, Uri uri, String mimeType) {
+ if (!isRunning()) return; // Ignore when we're no longer running
+
+ mCurrentCount++;
+ if (success && uri != null) {
+ mSuccessCount++;
+ if (DBG) Log.d(TAG, "Transfer success, uri " + uri + " mimeType " + mimeType);
+ mProgress = 0.0f;
+ if (mimeType == null) {
+ mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, uri);
+ }
+ if (mimeType != null) {
+ mUris.add(uri);
+ mTransferMimeTypes.add(mimeType);
+ } else {
+ if (DBG) Log.d(TAG, "Could not get mimeType for file.");
+ }
+ } else {
+ Log.e(TAG, "Handover transfer failed");
+ // Do wait to see if there's another file coming.
+ }
+ mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER);
+ if (mCurrentCount == mTotalCount) {
+ if (mIncoming) {
+ processFiles();
+ } else {
+ updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED);
+ }
+ } else {
+ mHandler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS);
+ updateStateAndNotification(STATE_W4_NEXT_TRANSFER);
+ }
+ }
+
+ public boolean isRunning() {
+ if (mState != STATE_NEW && mState != STATE_IN_PROGRESS && mState != STATE_W4_NEXT_TRANSFER) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void setObjectCount(int objectCount) {
+ mTotalCount = objectCount;
+ }
+
+ void cancel() {
+ if (!isRunning()) return;
+
+ // Delete all files received so far
+ for (Uri uri : mUris) {
+ File file = new File(uri.getPath());
+ if (file.exists()) file.delete();
+ }
+
+ if (mBluetoothTransferId != -1) {
+ // we know the ID, we can cancel immediately
+ sendBluetoothCancelIntentAndUpdateState();
+ } else {
+ updateStateAndNotification(STATE_CANCELLING);
+ }
+
+ }
+
+ private void sendBluetoothCancelIntentAndUpdateState() {
+ Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER);
+ cancelIntent.putExtra(BeamStatusReceiver.EXTRA_TRANSFER_ID, mBluetoothTransferId);
+ mContext.sendBroadcast(cancelIntent);
+ updateStateAndNotification(STATE_CANCELLED);
+ }
+
+ void updateNotification() {
+ Builder notBuilder = new Notification.Builder(mContext);
+ notBuilder.setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ notBuilder.setWhen(mStartTime);
+ notBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
+ String beamString;
+ if (mIncoming) {
+ beamString = mContext.getString(R.string.beam_progress);
+ } else {
+ beamString = mContext.getString(R.string.beam_outgoing);
+ }
+ if (mState == STATE_NEW || mState == STATE_IN_PROGRESS ||
+ mState == STATE_W4_NEXT_TRANSFER || mState == STATE_W4_MEDIA_SCANNER) {
+ notBuilder.setAutoCancel(false);
+ notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download :
+ android.R.drawable.stat_sys_upload);
+ notBuilder.setTicker(beamString);
+ notBuilder.setContentTitle(beamString);
+ notBuilder.addAction(R.drawable.ic_menu_cancel_holo_dark,
+ mContext.getString(R.string.cancel), mCancelIntent);
+ float progress = 0;
+ if (mTotalCount > 0) {
+ float progressUnit = 1.0f / mTotalCount;
+ progress = (float) mCurrentCount * progressUnit + mProgress * progressUnit;
+ }
+ if (mTotalCount > 0 && progress > 0) {
+ notBuilder.setProgress(100, (int) (100 * progress), false);
+ } else {
+ notBuilder.setProgress(100, 0, true);
+ }
+ } else if (mState == STATE_SUCCESS) {
+ notBuilder.setAutoCancel(true);
+ notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done :
+ android.R.drawable.stat_sys_upload_done);
+ notBuilder.setTicker(mContext.getString(R.string.beam_complete));
+ notBuilder.setContentTitle(mContext.getString(R.string.beam_complete));
+
+ if (mIncoming) {
+ notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view));
+ Intent viewIntent = buildViewIntent();
+ PendingIntent contentIntent = PendingIntent.getActivity(
+ mContext, mTransferId, viewIntent, 0, null);
+
+ notBuilder.setContentIntent(contentIntent);
+ }
+ } else if (mState == STATE_FAILED) {
+ notBuilder.setAutoCancel(false);
+ notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done :
+ android.R.drawable.stat_sys_upload_done);
+ notBuilder.setTicker(mContext.getString(R.string.beam_failed));
+ notBuilder.setContentTitle(mContext.getString(R.string.beam_failed));
+ } else if (mState == STATE_CANCELLED || mState == STATE_CANCELLING) {
+ notBuilder.setAutoCancel(false);
+ notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done :
+ android.R.drawable.stat_sys_upload_done);
+ notBuilder.setTicker(mContext.getString(R.string.beam_canceled));
+ notBuilder.setContentTitle(mContext.getString(R.string.beam_canceled));
+ } else {
+ return;
+ }
+
+ mNotificationManager.notify(null, mTransferId, notBuilder.build());
+ }
+
+ void updateStateAndNotification(int newState) {
+ this.mState = newState;
+ this.mLastUpdate = SystemClock.elapsedRealtime();
+
+ mHandler.removeMessages(MSG_TRANSFER_TIMEOUT);
+ if (isRunning()) {
+ // Update timeout timer if we're still running
+ mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS);
+ }
+
+ updateNotification();
+
+ if ((mState == STATE_SUCCESS || mState == STATE_FAILED || mState == STATE_CANCELLED)
+ && !mCalledBack) {
+ mCalledBack = true;
+ // Notify that we're done with this transfer
+ mCallback.onTransferComplete(this, mState == STATE_SUCCESS);
+ }
+ }
+
+ void processFiles() {
+ // Check the amount of files we received in this transfer;
+ // If more than one, create a separate directory for it.
+ String extRoot = Environment.getExternalStorageDirectory().getPath();
+ File beamPath = new File(extRoot + "/" + BEAM_DIR);
+
+ if (!checkMediaStorage(beamPath) || mUris.size() == 0) {
+ Log.e(TAG, "Media storage not valid or no uris received.");
+ updateStateAndNotification(STATE_FAILED);
+ return;
+ }
+
+ if (mUris.size() > 1) {
+ beamPath = generateMultiplePath(extRoot + "/" + BEAM_DIR + "/");
+ if (!beamPath.isDirectory() && !beamPath.mkdir()) {
+ Log.e(TAG, "Failed to create multiple path " + beamPath.toString());
+ updateStateAndNotification(STATE_FAILED);
+ return;
+ }
+ }
+
+ for (int i = 0; i < mUris.size(); i++) {
+ Uri uri = mUris.get(i);
+ String mimeType = mTransferMimeTypes.get(i);
+
+ File srcFile = new File(uri.getPath());
+
+ File dstFile = generateUniqueDestination(beamPath.getAbsolutePath(),
+ uri.getLastPathSegment());
+ Log.d(TAG, "Renaming from " + srcFile);
+ if (!srcFile.renameTo(dstFile)) {
+ if (DBG) Log.d(TAG, "Failed to rename from " + srcFile + " to " + dstFile);
+ srcFile.delete();
+ return;
+ } else {
+ mPaths.add(dstFile.getAbsolutePath());
+ mMimeTypes.put(dstFile.getAbsolutePath(), mimeType);
+ if (DBG) Log.d(TAG, "Did successful rename from " + srcFile + " to " + dstFile);
+ }
+ }
+
+ // We can either add files to the media provider, or provide an ACTION_VIEW
+ // intent to the file directly. We base this decision on the mime type
+ // of the first file; if it's media the platform can deal with,
+ // use the media provider, if it's something else, just launch an ACTION_VIEW
+ // on the file.
+ String mimeType = mMimeTypes.get(mPaths.get(0));
+ if (mimeType.startsWith("image/") || mimeType.startsWith("video/") ||
+ mimeType.startsWith("audio/")) {
+ String[] arrayPaths = new String[mPaths.size()];
+ MediaScannerConnection.scanFile(mContext, mPaths.toArray(arrayPaths), null, this);
+ updateStateAndNotification(STATE_W4_MEDIA_SCANNER);
+ } else {
+ // We're done.
+ updateStateAndNotification(STATE_SUCCESS);
+ }
+
+ }
+
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_NEXT_TRANSFER_TIMER) {
+ // We didn't receive a new transfer in time, finalize this one
+ if (mIncoming) {
+ processFiles();
+ } else {
+ updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED);
+ }
+ return true;
+ } else if (msg.what == MSG_TRANSFER_TIMEOUT) {
+ // No update on this transfer for a while, fail it.
+ if (DBG) Log.d(TAG, "Transfer timed out for id: " + Integer.toString(mTransferId));
+ updateStateAndNotification(STATE_FAILED);
+ }
+ return false;
+ }
+
+ public synchronized void onScanCompleted(String path, Uri uri) {
+ if (DBG) Log.d(TAG, "Scan completed, path " + path + " uri " + uri);
+ if (uri != null) {
+ mMediaUris.put(path, uri);
+ }
+ mUrisScanned++;
+ if (mUrisScanned == mPaths.size()) {
+ // We're done
+ updateStateAndNotification(STATE_SUCCESS);
+ }
+ }
+
+
+ Intent buildViewIntent() {
+ if (mPaths.size() == 0) return null;
+
+ Intent viewIntent = new Intent(Intent.ACTION_VIEW);
+
+ String filePath = mPaths.get(0);
+ Uri mediaUri = mMediaUris.get(filePath);
+ Uri uri = mediaUri != null ? mediaUri :
+ Uri.parse(ContentResolver.SCHEME_FILE + "://" + filePath);
+ viewIntent.setDataAndTypeAndNormalize(uri, mMimeTypes.get(filePath));
+ viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return viewIntent;
+ }
+
+ PendingIntent buildCancelIntent() {
+ Intent intent = new Intent(BeamStatusReceiver.ACTION_CANCEL_HANDOVER_TRANSFER);
+ intent.putExtra(BeamStatusReceiver.EXTRA_ADDRESS, mRemoteDevice.getAddress());
+ intent.putExtra(BeamStatusReceiver.EXTRA_INCOMING, mIncoming ?
+ BeamStatusReceiver.DIRECTION_INCOMING : BeamStatusReceiver.DIRECTION_OUTGOING);
+ PendingIntent pi = PendingIntent.getBroadcast(mContext, mTransferId, intent,
+ PendingIntent.FLAG_ONE_SHOT);
+
+ return pi;
+ }
+
+ static boolean checkMediaStorage(File path) {
+ if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ if (!path.isDirectory() && !path.mkdir()) {
+ Log.e(TAG, "Not dir or not mkdir " + path.getAbsolutePath());
+ return false;
+ }
+ return true;
+ } else {
+ Log.e(TAG, "External storage not mounted, can't store file.");
+ return false;
+ }
+ }
+
+ static File generateUniqueDestination(String path, String fileName) {
+ int dotIndex = fileName.lastIndexOf(".");
+ String extension = null;
+ String fileNameWithoutExtension = null;
+ if (dotIndex < 0) {
+ extension = "";
+ fileNameWithoutExtension = fileName;
+ } else {
+ extension = fileName.substring(dotIndex);
+ fileNameWithoutExtension = fileName.substring(0, dotIndex);
+ }
+ File dstFile = new File(path + File.separator + fileName);
+ int count = 0;
+ while (dstFile.exists()) {
+ dstFile = new File(path + File.separator + fileNameWithoutExtension + "-" +
+ Integer.toString(count) + extension);
+ count++;
+ }
+ return dstFile;
+ }
+
+ static File generateMultiplePath(String beamRoot) {
+ // Generate a unique directory with the date
+ String format = "yyyy-MM-dd";
+ SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US);
+ String newPath = beamRoot + "beam-" + sdf.format(new Date());
+ File newFile = new File(newPath);
+ int count = 0;
+ while (newFile.exists()) {
+ newPath = beamRoot + "beam-" + sdf.format(new Date()) + "-" +
+ Integer.toString(count);
+ newFile = new File(newPath);
+ count++;
+ }
+ return newFile;
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl
new file mode 100644
index 0000000..93af205
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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.beam;
+
+parcelable BeamTransferRecord;
diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java
new file mode 100644
index 0000000..d8f8668
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2012 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.beam;
+
+import android.bluetooth.BluetoothDevice;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class BeamTransferRecord implements Parcelable {
+
+ public static int DATA_LINK_TYPE_BLUETOOTH = 0;
+
+ public int id;
+ public boolean remoteActivating;
+ public Uri[] uris;
+ public int dataLinkType;
+
+ // Data link specific fields
+ public BluetoothDevice remoteDevice;
+
+
+ private BeamTransferRecord(BluetoothDevice remoteDevice,
+ boolean remoteActivating, Uri[] uris) {
+ this.id = 0;
+ this.remoteDevice = remoteDevice;
+ this.remoteActivating = remoteActivating;
+ this.uris = uris;
+
+ this.dataLinkType = DATA_LINK_TYPE_BLUETOOTH;
+ }
+
+ public static final BeamTransferRecord forBluetoothDevice(
+ BluetoothDevice remoteDevice, boolean remoteActivating,
+ Uri[] uris) {
+ return new BeamTransferRecord(remoteDevice, remoteActivating, uris);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public BeamTransferRecord createFromParcel(Parcel in) {
+ int deviceType = in.readInt();
+
+ if (deviceType != DATA_LINK_TYPE_BLUETOOTH) {
+ // only support BLUETOOTH
+ return null;
+ }
+
+ BluetoothDevice remoteDevice = in.readParcelable(getClass().getClassLoader());
+ boolean remoteActivating = (in.readInt() == 1);
+ int numUris = in.readInt();
+ Uri[] uris = null;
+ if (numUris > 0) {
+ uris = new Uri[numUris];
+ in.readTypedArray(uris, Uri.CREATOR);
+ }
+
+ return new BeamTransferRecord(remoteDevice,
+ remoteActivating, uris);
+
+ }
+
+ @Override
+ public BeamTransferRecord[] newArray(int size) {
+ return new BeamTransferRecord[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(dataLinkType);
+ dest.writeParcelable(remoteDevice, 0);
+ dest.writeInt(remoteActivating ? 1 : 0);
+ dest.writeInt(uris != null ? uris.length : 0);
+ if (uris != null && uris.length > 0) {
+ dest.writeTypedArray(uris, 0);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java b/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java
new file mode 100644
index 0000000..8e04093
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 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.beam;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class BluetoothOppHandover implements Handler.Callback {
+ static final String TAG = "BluetoothOppHandover";
+ static final boolean DBG = true;
+
+ static final int STATE_INIT = 0;
+ static final int STATE_TURNING_ON = 1;
+ static final int STATE_WAITING = 2; // Need to wait for remote side turning on BT
+ static final int STATE_COMPLETE = 3;
+
+ static final int MSG_START_SEND = 0;
+
+ static final int REMOTE_BT_ENABLE_DELAY_MS = 5000;
+
+ static final String ACTION_HANDOVER_SEND =
+ "android.nfc.handover.intent.action.HANDOVER_SEND";
+
+ static final String ACTION_HANDOVER_SEND_MULTIPLE =
+ "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE";
+
+ final Context mContext;
+ final BluetoothDevice mDevice;
+
+ final ArrayList mUris;
+ final boolean mRemoteActivating;
+ final Handler mHandler;
+ final Long mCreateTime;
+
+ int mState;
+
+ public BluetoothOppHandover(Context context, BluetoothDevice device, ArrayList uris,
+ boolean remoteActivating) {
+ mContext = context;
+ mDevice = device;
+ mUris = uris;
+ mRemoteActivating = remoteActivating;
+ mCreateTime = SystemClock.elapsedRealtime();
+
+ mHandler = new Handler(context.getMainLooper(), this);
+ mState = STATE_INIT;
+ }
+
+ /**
+ * Main entry point. This method is usually called after construction,
+ * to begin the BT sequence. Must be called on Main thread.
+ */
+ public void start() {
+ if (mRemoteActivating) {
+ Long timeElapsed = SystemClock.elapsedRealtime() - mCreateTime;
+ if (timeElapsed < REMOTE_BT_ENABLE_DELAY_MS) {
+ mHandler.sendEmptyMessageDelayed(MSG_START_SEND,
+ REMOTE_BT_ENABLE_DELAY_MS - timeElapsed);
+ } else {
+ // Already waited long enough for BT to come up
+ // - start send.
+ sendIntent();
+ }
+ } else {
+ // Remote BT enabled already, start send immediately
+ sendIntent();
+ }
+ }
+
+ void complete() {
+ mState = STATE_COMPLETE;
+ }
+
+ void sendIntent() {
+ Intent intent = new Intent();
+ intent.setPackage("com.android.bluetooth");
+ String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris.get(0));
+ intent.setType(mimeType);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ for (Uri uri : mUris) {
+ // TODO we need to transfer our permission grant from NFC
+ // to the Bluetooth process. This works, but we don't have
+ // a good framework API for revoking permission yet.
+ try {
+ mContext.grantUriPermission("com.android.bluetooth", uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (SecurityException e) {
+ Log.e(TAG, "Failed to transfer permission to Bluetooth process.");
+ }
+ }
+ if (mUris.size() == 1) {
+ intent.setAction(ACTION_HANDOVER_SEND);
+ intent.putExtra(Intent.EXTRA_STREAM, mUris.get(0));
+ } else {
+ intent.setAction(ACTION_HANDOVER_SEND_MULTIPLE);
+ intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mUris);
+ }
+ if (DBG) Log.d(TAG, "Handing off outging transfer to BT");
+ intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.sendBroadcast(intent);
+
+ complete();
+ }
+
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_START_SEND) {
+ sendIntent();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java b/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java
new file mode 100644
index 0000000..d87a5d9
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java
@@ -0,0 +1,425 @@
+/*
+ * 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.beam;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+public class FireflyRenderer {
+ private static final String LOG_TAG = "NfcFireflyThread";
+
+ static final int NUM_FIREFLIES = 200;
+
+ static final float NEAR_CLIPPING_PLANE = 50f;
+ static final float FAR_CLIPPING_PLANE = 100f;
+
+ // All final variables below only need to be allocated once
+ // and can be reused between subsequent Beams
+ static final int[] sEglConfig = {
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 0,
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL10.EGL_NONE
+ };
+
+ // Vertices for drawing a 32x32 rect
+ static final float mVertices[] = {
+ 0.0f, 0.0f, 0.0f, // 0, Top Left
+ 0.0f, 32.0f, 0.0f, // 1, Bottom Left
+ 32.0f, 32.0f, 0.0f, // 2, Bottom Right
+ 32.0f, 0.0f, 0.0f, // 3, Top Right
+ };
+
+ // Mapping coordinates for the texture
+ static final float mTextCoords[] = {
+ 0.0f, 0.0f,
+ 1.0f, 0.0f,
+ 1.0f, 1.0f,
+ 0.0f, 1.0f
+ };
+
+ // Connecting order (draws a square)
+ static final short[] mIndices = { 0, 1, 2, 0, 2, 3 };
+
+ final Context mContext;
+
+ // Buffer holding the vertices
+ final FloatBuffer mVertexBuffer;
+
+ // Buffer holding the indices
+ final ShortBuffer mIndexBuffer;
+
+ // Buffer holding the texture mapping coordinates
+ final FloatBuffer mTextureBuffer;
+
+ final Firefly[] mFireflies;
+
+ FireflyRenderThread mFireflyRenderThread;
+
+ // The surface to render the flies on, including width and height
+ SurfaceTexture mSurface;
+ int mDisplayWidth;
+ int mDisplayHeight;
+
+ public FireflyRenderer(Context context) {
+ mContext = context;
+
+ // First, build the vertex, texture and index buffers
+ ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes
+ vbb.order(ByteOrder.nativeOrder());
+ mVertexBuffer = vbb.asFloatBuffer();
+ mVertexBuffer.put(mVertices);
+ mVertexBuffer.position(0);
+
+ ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes
+ ibb.order(ByteOrder.nativeOrder());
+ mIndexBuffer = ibb.asShortBuffer();
+ mIndexBuffer.put(mIndices);
+ mIndexBuffer.position(0);
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4);
+ tbb.order(ByteOrder.nativeOrder());
+ mTextureBuffer = tbb.asFloatBuffer();
+ mTextureBuffer.put(mTextCoords);
+ mTextureBuffer.position(0);
+
+ mFireflies = new Firefly[NUM_FIREFLIES];
+ for (int i = 0; i < NUM_FIREFLIES; i++) {
+ mFireflies[i] = new Firefly();
+ }
+ }
+
+ /**
+ * Starts rendering fireflies on the given surface.
+ * Must be called from the UI-thread.
+ */
+ public void start(SurfaceTexture surface, int width, int height) {
+ mSurface = surface;
+ mDisplayWidth = width;
+ mDisplayHeight = height;
+
+ mFireflyRenderThread = new FireflyRenderThread();
+ mFireflyRenderThread.start();
+ }
+
+ /**
+ * Stops rendering fireflies.
+ * Must be called from the UI-thread.
+ */
+ public void stop() {
+ if (mFireflyRenderThread != null) {
+ mFireflyRenderThread.finish();
+ try {
+ mFireflyRenderThread.join();
+ } catch (InterruptedException e) {
+ Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread.");
+ }
+ mFireflyRenderThread = null;
+ }
+ }
+
+ private class FireflyRenderThread extends Thread {
+ EGL10 mEgl;
+ EGLDisplay mEglDisplay;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+ EGLSurface mEglSurface;
+ GL10 mGL;
+
+ // Holding the handle to the texture
+ int mTextureId;
+
+ // Read/written by multiple threads
+ volatile boolean mFinished;
+
+ @Override
+ public void run() {
+ if (!initGL()) {
+ Log.e(LOG_TAG, "Failed to initialize OpenGL.");
+ return;
+ }
+ loadStarTexture();
+
+ mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+
+ mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+
+ // make adjustments for screen ratio
+ mGL.glMatrixMode(GL10.GL_PROJECTION);
+ mGL.glLoadIdentity();
+ mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE);
+
+ // Switch back to modelview
+ mGL.glMatrixMode(GL10.GL_MODELVIEW);
+ mGL.glLoadIdentity();
+
+ mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
+ mGL.glDepthMask(true);
+
+
+ for (Firefly firefly : mFireflies) {
+ firefly.reset();
+ }
+
+ for (int i = 0; i < 3; i++) {
+ // Call eglSwapBuffers 3 times - this will allocate the necessary
+ // buffers, and make sure the animation looks smooth from the start.
+ mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ Log.e(LOG_TAG, "Could not swap buffers");
+ mFinished = true;
+ }
+ }
+
+ long startTime = System.currentTimeMillis();
+
+ while (!mFinished) {
+ long timeElapsedMs = System.currentTimeMillis() - startTime;
+ startTime = System.currentTimeMillis();
+
+ checkCurrent();
+
+ mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+ mGL.glLoadIdentity();
+
+ mGL.glEnable(GL10.GL_TEXTURE_2D);
+ mGL.glEnable(GL10.GL_BLEND);
+ mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
+
+ for (Firefly firefly : mFireflies) {
+ firefly.updatePositionAndScale(timeElapsedMs);
+ firefly.draw(mGL);
+ }
+
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ Log.e(LOG_TAG, "Could not swap buffers");
+ mFinished = true;
+ }
+
+ long elapsed = System.currentTimeMillis() - startTime;
+ try {
+ Thread.sleep(Math.max(30 - elapsed, 0));
+ } catch (InterruptedException e) {
+
+ }
+ }
+ finishGL();
+ }
+
+ public void finish() {
+ mFinished = true;
+ }
+
+ void loadStarTexture() {
+ int[] textureIds = new int[1];
+ mGL.glGenTextures(1, textureIds, 0);
+ mTextureId = textureIds[0];
+
+ InputStream in = null;
+ try {
+ // Remember that both texture dimensions must be a power of 2!
+ in = mContext.getAssets().open("star.png");
+
+ Bitmap bitmap = BitmapFactory.decodeStream(in);
+ mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
+
+ mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
+ mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
+
+ GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
+
+ bitmap.recycle();
+
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException opening assets.");
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) { }
+ }
+ }
+ }
+
+ private void checkCurrent() {
+ if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
+ !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new RuntimeException("eglMakeCurrent failed "
+ + GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ }
+ }
+ }
+
+ boolean initGL() {
+ // Initialize openGL engine
+ mEgl = (EGL10) EGLContext.getEGL();
+
+ mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+ if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+ Log.e(LOG_TAG, "eglGetDisplay failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ int[] version = new int[2];
+ if (!mEgl.eglInitialize(mEglDisplay, version)) {
+ Log.e(LOG_TAG, "eglInitialize failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ mEglConfig = chooseEglConfig();
+ if (mEglConfig == null) {
+ Log.e(LOG_TAG, "eglConfig not initialized.");
+ return false;
+ }
+
+ mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null);
+
+ mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
+
+ if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+ int error = mEgl.eglGetError();
+ Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error));
+ return false;
+ }
+
+ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ Log.e(LOG_TAG, "eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ return false;
+ }
+
+ mGL = (GL10) mEglContext.getGL();
+
+ return true;
+ }
+
+ private void finishGL() {
+ if (mEgl == null || mEglDisplay == null) {
+ // Nothing to free
+ return;
+ }
+ // Unbind the current surface and context from the display
+ mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+ EGL10.EGL_NO_CONTEXT);
+
+ if (mEglSurface != null) {
+ mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+
+ if (mEglContext != null) {
+ mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ }
+
+ private EGLConfig chooseEglConfig() {
+ int[] configsCount = new int[1];
+ EGLConfig[] configs = new EGLConfig[1];
+ if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) {
+ throw new IllegalArgumentException("eglChooseConfig failed " +
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
+ } else if (configsCount[0] > 0) {
+ return configs[0];
+ }
+ return null;
+ }
+ }
+
+ private class Firefly {
+ static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size
+ static final float SPEED = .5f;
+
+ float mX; // between -mDisplayHeight and mDisplayHeight
+ float mY; // between -mDisplayWidth and mDisplayWidth
+ float mZ; // between 0.0 (near) and 1.0 (far)
+ float mZ0;
+ float mT;
+ float mScale;
+ float mAlpha;
+
+ public Firefly() {
+ }
+
+ void reset() {
+ mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth;
+ mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight;
+ mZ0 = mZ = (float) (Math.random()) * 2 - 1;
+ mT = 0f;
+ mScale = 1.5f;
+ mAlpha = 0f;
+ }
+
+ public void updatePositionAndScale(long timeElapsedMs) {
+ mT += timeElapsedMs;
+ mZ = mZ0 + mT/1000f * SPEED;
+ mAlpha = 1f-mZ;
+ if(mZ > 1.0) reset();
+ }
+
+ public void draw(GL10 gl) {
+ gl.glLoadIdentity();
+
+ // Counter clockwise winding
+ gl.glFrontFace(GL10.GL_CCW);
+
+ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+ gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+
+ gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
+ gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
+
+ gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE));
+ gl.glColor4f(1, 1, 1, mAlpha);
+
+ // scale around center
+ gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0);
+ gl.glScalef(mScale, mScale, 0);
+ gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0);
+
+ gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT,
+ mIndexBuffer);
+
+ gl.glColor4f(1, 1, 1, 1);
+ gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+ gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java b/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java
new file mode 100644
index 0000000..73d7fd6
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 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.beam;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+public final class MimeTypeUtil {
+
+ private static final String TAG = "MimeTypeUtil";
+
+ private MimeTypeUtil() {}
+
+ public static String getMimeTypeForUri(Context context, Uri uri) {
+ if (uri.getScheme() == null) return null;
+
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = context.getContentResolver();
+ return cr.getType(uri);
+ } else if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+ String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath()).toLowerCase();
+ if (extension != null) {
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ } else {
+ return null;
+ }
+ } else {
+ Log.d(TAG, "Could not determine mime type for Uri " + uri);
+ return null;
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/beam/SendUi.java b/NfcSony/src/com/android/nfc/beam/SendUi.java
new file mode 100644
index 0000000..7eaea08
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/beam/SendUi.java
@@ -0,0 +1,891 @@
+/*
+ * 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.beam;
+
+import com.android.nfc.R;
+import com.android.nfc.beam.FireflyRenderer;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeAnimator;
+import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import com.android.internal.policy.PhoneWindow;
+import android.view.SearchEvent;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * This class is responsible for handling the UI animation
+ * around Android Beam. The animation consists of the following
+ * animators:
+ *
+ * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE
+ * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation)
+ * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success)
+ * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes)
+ * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving)
+ * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint
+ *
+ * Possible sequences are:
+ *
+ * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success)
+ * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure)
+ * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received)
+ *
+ * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they
+ * are an atomic animation that cannot be interrupted.
+ *
+ * All methods of this class must be called on the UI thread
+ */
+public class SendUi implements Animator.AnimatorListener, View.OnTouchListener,
+ TimeAnimator.TimeListener, TextureView.SurfaceTextureListener, android.view.Window.Callback {
+ static final String TAG = "SendUi";
+
+ static final float INTERMEDIATE_SCALE = 0.6f;
+
+ static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE};
+ static final int PRE_DURATION_MS = 350;
+
+ static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f};
+ static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s
+ static final int FAST_SEND_DURATION_MS = 350;
+
+ static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f};
+ static final int SCALE_UP_DURATION_MS = 300;
+
+ static final int FADE_IN_DURATION_MS = 250;
+ static final int FADE_IN_START_DELAY_MS = 350;
+
+ static final int SLIDE_OUT_DURATION_MS = 300;
+
+ static final float[] BLACK_LAYER_ALPHA_DOWN_RANGE = {0.9f, 0.0f};
+ static final float[] BLACK_LAYER_ALPHA_UP_RANGE = {0.0f, 0.9f};
+
+ static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f};
+ static final int TEXT_HINT_ALPHA_DURATION_MS = 500;
+ static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300;
+
+ public static final int FINISH_SCALE_UP = 0;
+ public static final int FINISH_SEND_SUCCESS = 1;
+
+ static final int STATE_IDLE = 0;
+ static final int STATE_W4_SCREENSHOT = 1;
+ static final int STATE_W4_SCREENSHOT_PRESEND_REQUESTED = 2;
+ static final int STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED = 3;
+ static final int STATE_W4_SCREENSHOT_THEN_STOP = 4;
+ static final int STATE_W4_PRESEND = 5;
+ static final int STATE_W4_TOUCH = 6;
+ static final int STATE_W4_NFC_TAP = 7;
+ static final int STATE_SENDING = 8;
+ static final int STATE_COMPLETE = 9;
+
+ // 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 StatusBarManager mStatusBarManager;
+ final View mScreenshotLayout;
+ final ImageView mScreenshotView;
+ final ImageView mBlackLayer;
+ final TextureView mTextureView;
+ final TextView mTextHint;
+ final TextView mTextRetry;
+ final Callback mCallback;
+
+ // The mFrameCounter animation is purely used to count down a certain
+ // number of (vsync'd) frames. This is needed because the first 3
+ // times the animation internally calls eglSwapBuffers(), large buffers
+ // are allocated by the graphics drivers. This causes the animation
+ // to look janky. So on platforms where we can use hardware acceleration,
+ // the animation order is:
+ // Wait for hw surface => start frame counter => start pre-animation after 3 frames
+ // For platforms where no hw acceleration can be used, the pre-animation
+ // is started immediately.
+ final TimeAnimator mFrameCounterAnimator;
+
+ final ObjectAnimator mPreAnimator;
+ final ObjectAnimator mSlowSendAnimator;
+ final ObjectAnimator mFastSendAnimator;
+ final ObjectAnimator mFadeInAnimator;
+ final ObjectAnimator mHintAnimator;
+ final ObjectAnimator mScaleUpAnimator;
+ final ObjectAnimator mAlphaDownAnimator;
+ final ObjectAnimator mAlphaUpAnimator;
+ final AnimatorSet mSuccessAnimatorSet;
+
+ // Besides animating the screenshot, the Beam UI also renders
+ // fireflies on platforms where we can do hardware-acceleration.
+ // Firefly rendering is only started once the initial
+ // "pre-animation" has scaled down the screenshot, to avoid
+ // that animation becoming janky. Likewise, the fireflies are
+ // stopped in their tracks as soon as we finish the animation,
+ // to make the finishing animation smooth.
+ final boolean mHardwareAccelerated;
+ final FireflyRenderer mFireflyRenderer;
+
+ String mToastString;
+ Bitmap mScreenshotBitmap;
+
+ int mState;
+ int mRenderedFrames;
+
+ View mDecor;
+
+ // Used for holding the surface
+ SurfaceTexture mSurface;
+ int mSurfaceWidth;
+ int mSurfaceHeight;
+
+ public interface Callback {
+ public void onSendConfirmed();
+ public void onCanceled();
+ }
+
+ public SendUi(Context context, Callback callback) {
+ mContext = context;
+ mCallback = callback;
+
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplayMatrix = new Matrix();
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_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);
+
+ mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction);
+ mTextRetry = (TextView) mScreenshotLayout.findViewById(R.id.retrytext);
+ mBlackLayer = (ImageView) mScreenshotLayout.findViewById(R.id.blacklayer);
+ mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies);
+ mTextureView.setSurfaceTextureListener(this);
+
+ // We're only allowed to use hardware acceleration if
+ // isHighEndGfx() returns true - otherwise, we're too limited
+ // on resources to do it.
+ mHardwareAccelerated = ActivityManager.isHighEndGfx();
+ int hwAccelerationFlags = mHardwareAccelerated ?
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0;
+
+ mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | hwAccelerationFlags
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.OPAQUE);
+ mWindowLayoutParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ mWindowLayoutParams.token = new Binder();
+
+ mFrameCounterAnimator = new TimeAnimator();
+ mFrameCounterAnimator.setTimeListener(this);
+
+ PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE);
+ PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE);
+ mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY);
+ mPreAnimator.setInterpolator(new DecelerateInterpolator());
+ mPreAnimator.setDuration(PRE_DURATION_MS);
+ mPreAnimator.addListener(this);
+
+ PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE);
+ PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE);
+ PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
+ new float[]{1.0f, 0.0f});
+
+ mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY);
+ mSlowSendAnimator.setInterpolator(new DecelerateInterpolator());
+ mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS);
+
+ mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX,
+ postY, alphaDown);
+ mFastSendAnimator.setInterpolator(new DecelerateInterpolator());
+ mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS);
+ mFastSendAnimator.addListener(this);
+
+ PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE);
+ PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE);
+
+ mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY);
+ mScaleUpAnimator.setInterpolator(new DecelerateInterpolator());
+ mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS);
+ mScaleUpAnimator.addListener(this);
+
+ PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f);
+ mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn);
+ mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ mFadeInAnimator.setDuration(FADE_IN_DURATION_MS);
+ mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS);
+ mFadeInAnimator.addListener(this);
+
+ PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE);
+ mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp);
+ mHintAnimator.setInterpolator(null);
+ mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS);
+ mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS);
+
+ alphaDown = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_DOWN_RANGE);
+ mAlphaDownAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaDown);
+ mAlphaDownAnimator.setInterpolator(new DecelerateInterpolator());
+ mAlphaDownAnimator.setDuration(400);
+
+ alphaUp = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_UP_RANGE);
+ mAlphaUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaUp);
+ mAlphaUpAnimator.setInterpolator(new DecelerateInterpolator());
+ mAlphaUpAnimator.setDuration(200);
+
+ mSuccessAnimatorSet = new AnimatorSet();
+ mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator);
+
+ // Create a Window with a Decor view; creating a window allows us to get callbacks
+ // on key events (which require a decor view to be dispatched).
+ mContext.setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen);
+ Window window = new PhoneWindow(mContext);
+ window.setCallback(this);
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+ mDecor = window.getDecorView();
+ window.setContentView(mScreenshotLayout, mWindowLayoutParams);
+
+ if (mHardwareAccelerated) {
+ mFireflyRenderer = new FireflyRenderer(context);
+ } else {
+ mFireflyRenderer = null;
+ }
+ mState = STATE_IDLE;
+ }
+
+ public void takeScreenshot() {
+ // There's no point in taking the screenshot if
+ // we're still finishing the previous animation.
+ if (mState >= STATE_W4_TOUCH) {
+ return;
+ }
+ mState = STATE_W4_SCREENSHOT;
+ new ScreenshotTask().execute();
+ }
+
+ /** Show pre-send animation */
+ public void showPreSend(boolean promptToNfcTap) {
+ switch (mState) {
+ case STATE_IDLE:
+ Log.e(TAG, "Unexpected showPreSend() in STATE_IDLE");
+ return;
+ case STATE_W4_SCREENSHOT:
+ // Still waiting for screenshot, store request in state
+ // and wait for screenshot completion.
+ if (promptToNfcTap) {
+ mState = STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED;
+ } else {
+ mState = STATE_W4_SCREENSHOT_PRESEND_REQUESTED;
+ }
+ return;
+ case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
+ case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED:
+ Log.e(TAG, "Unexpected showPreSend() in STATE_W4_SCREENSHOT_PRESEND_REQUESTED");
+ return;
+ case STATE_W4_PRESEND:
+ // Expected path, continue below
+ break;
+ default:
+ Log.e(TAG, "Unexpected showPreSend() in state " + Integer.toString(mState));
+ return;
+ }
+ // Update display metrics
+ mDisplay.getRealMetrics(mDisplayMetrics);
+
+ final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ mBlackLayer.setVisibility(View.GONE);
+ mBlackLayer.setAlpha(0f);
+ mScreenshotLayout.setOnTouchListener(this);
+ mScreenshotView.setImageBitmap(mScreenshotBitmap);
+ mScreenshotView.setTranslationX(0f);
+ mScreenshotView.setAlpha(1.0f);
+ mScreenshotView.setPadding(0, statusBarHeight, 0, 0);
+
+ mScreenshotLayout.requestFocus();
+
+ if (promptToNfcTap) {
+ mTextHint.setText(mContext.getResources().getString(R.string.ask_nfc_tap));
+ } else {
+ mTextHint.setText(mContext.getResources().getString(R.string.touch));
+ }
+ mTextHint.setAlpha(0.0f);
+ mTextHint.setVisibility(View.VISIBLE);
+ mHintAnimator.start();
+
+ // Lock the orientation.
+ // The orientation from the configuration does not specify whether
+ // the orientation is reverse or not (ie landscape or reverse landscape).
+ // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure
+ // we lock in portrait / landscape and have the sensor determine
+ // which way is up.
+ int orientation = mContext.getResources().getConfiguration().orientation;
+
+ switch (orientation) {
+ case Configuration.ORIENTATION_LANDSCAPE:
+ mWindowLayoutParams.screenOrientation =
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+ break;
+ case Configuration.ORIENTATION_PORTRAIT:
+ mWindowLayoutParams.screenOrientation =
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ break;
+ default:
+ mWindowLayoutParams.screenOrientation =
+ ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+ break;
+ }
+
+ mWindowManager.addView(mDecor, mWindowLayoutParams);
+ // Disable statusbar pull-down
+ mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
+
+ mToastString = null;
+
+ if (!mHardwareAccelerated) {
+ mPreAnimator.start();
+ } // else, we will start the animation once we get the hardware surface
+ mState = promptToNfcTap ? STATE_W4_NFC_TAP : STATE_W4_TOUCH;
+ }
+
+ /** Show starting send animation */
+ public void showStartSend() {
+ if (mState < STATE_SENDING) return;
+
+ mTextRetry.setVisibility(View.GONE);
+ // Update the starting scale - touchscreen-mashers may trigger
+ // this before the pre-animation completes.
+ float currentScale = mScreenshotView.getScaleX();
+ PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
+ new float[] {currentScale, 0.0f});
+ PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
+ new float[] {currentScale, 0.0f});
+
+ mSlowSendAnimator.setValues(postX, postY);
+
+ float currentAlpha = mBlackLayer.getAlpha();
+ if (mBlackLayer.isShown() && currentAlpha > 0.0f) {
+ PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha",
+ new float[] {currentAlpha, 0.0f});
+ mAlphaDownAnimator.setValues(alphaDown);
+ mAlphaDownAnimator.start();
+ }
+ mSlowSendAnimator.start();
+ }
+
+ public void finishAndToast(int finishMode, String toast) {
+ mToastString = toast;
+
+ finish(finishMode);
+ }
+
+ /** Return to initial state */
+ public void finish(int finishMode) {
+ switch (mState) {
+ case STATE_IDLE:
+ return;
+ case STATE_W4_SCREENSHOT:
+ case STATE_W4_SCREENSHOT_PRESEND_REQUESTED:
+ case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED:
+ // Screenshot is still being captured on a separate thread.
+ // Update state, and stop everything when the capture is done.
+ mState = STATE_W4_SCREENSHOT_THEN_STOP;
+ return;
+ case STATE_W4_SCREENSHOT_THEN_STOP:
+ Log.e(TAG, "Unexpected call to finish() in STATE_W4_SCREENSHOT_THEN_STOP");
+ return;
+ case STATE_W4_PRESEND:
+ // We didn't build up any animation state yet, but
+ // did store the bitmap. Clear out the bitmap, reset
+ // state and bail.
+ mScreenshotBitmap = null;
+ mState = STATE_IDLE;
+ return;
+ default:
+ // We've started animations and attached a view; tear stuff down below.
+ break;
+ }
+
+ // Stop rendering the fireflies
+ if (mFireflyRenderer != null) {
+ mFireflyRenderer.stop();
+ }
+
+ mTextHint.setVisibility(View.GONE);
+ mTextRetry.setVisibility(View.GONE);
+
+ float currentScale = mScreenshotView.getScaleX();
+ float currentAlpha = mScreenshotView.getAlpha();
+
+ if (finishMode == FINISH_SCALE_UP) {
+ mBlackLayer.setVisibility(View.GONE);
+ PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX",
+ new float[] {currentScale, 1.0f});
+ PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY",
+ new float[] {currentScale, 1.0f});
+ PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha",
+ new float[] {currentAlpha, 1.0f});
+ mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha);
+
+ mScaleUpAnimator.start();
+ } else if (finishMode == FINISH_SEND_SUCCESS){
+ // Modify the fast send parameters to match the current scale
+ PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX",
+ new float[] {currentScale, 0.0f});
+ PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY",
+ new float[] {currentScale, 0.0f});
+ PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha",
+ new float[] {currentAlpha, 0.0f});
+ mFastSendAnimator.setValues(postX, postY, alpha);
+
+ // Reset the fadeIn parameters to start from alpha 1
+ PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha",
+ new float[] {0.0f, 1.0f});
+ mFadeInAnimator.setValues(fadeIn);
+
+ mSlowSendAnimator.cancel();
+ mSuccessAnimatorSet.start();
+ }
+ mState = STATE_COMPLETE;
+ }
+
+ void dismiss() {
+ if (mState < STATE_W4_TOUCH) return;
+ // Immediately set to IDLE, to prevent .cancel() calls
+ // below from immediately calling into dismiss() again.
+ // (They can do so on the same thread).
+ mState = STATE_IDLE;
+ mSurface = null;
+ mFrameCounterAnimator.cancel();
+ mPreAnimator.cancel();
+ mSlowSendAnimator.cancel();
+ mFastSendAnimator.cancel();
+ mSuccessAnimatorSet.cancel();
+ mScaleUpAnimator.cancel();
+ mAlphaUpAnimator.cancel();
+ mAlphaDownAnimator.cancel();
+ mWindowManager.removeView(mDecor);
+ mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
+ mScreenshotBitmap = null;
+ if (mToastString != null) {
+ Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG).show();
+ }
+ mToastString = 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;
+ }
+
+ final class ScreenshotTask extends AsyncTask {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ return createScreenshot();
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap result) {
+ if (mState == STATE_W4_SCREENSHOT) {
+ // Screenshot done, wait for request to start preSend anim
+ mScreenshotBitmap = result;
+ mState = STATE_W4_PRESEND;
+ } else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) {
+ // We were asked to finish, move to idle state and exit
+ mState = STATE_IDLE;
+ } else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED ||
+ mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED) {
+ if (result != null) {
+ mScreenshotBitmap = result;
+ boolean requestTap = (mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED);
+ mState = STATE_W4_PRESEND;
+ showPreSend(requestTap);
+ } else {
+ // Failed to take screenshot; reset state to idle
+ // and don't do anything
+ Log.e(TAG, "Failed to create screenshot");
+ mState = STATE_IDLE;
+ }
+ } else {
+ Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState));
+ }
+ }
+ };
+
+ /**
+ * Returns a screenshot of the current display contents.
+ */
+ 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);
+ boolean hasNavBar = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_showNavigationBar);
+
+ float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
+ float degrees = getDegreesForRotation(mDisplay.getRotation());
+ final int statusBarHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+
+ // Navbar has different sizes, depending on orientation
+ final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height) : 0;
+ final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_height_landscape) : 0;
+
+ final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_width) : 0;
+
+ 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 = SurfaceControl.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;
+ }
+
+ // TODO this is somewhat device-specific; need generic solution.
+ // Crop off the status bar and the nav bar
+ // Portrait: 0, statusBarHeight, width, height - status - nav
+ // Landscape: 0, statusBarHeight, width - navBar, height - status
+ int newLeft = 0;
+ int newTop = statusBarHeight;
+ int newWidth = bitmap.getWidth();
+ int newHeight = bitmap.getHeight();
+ float smallestWidth = (float)Math.min(newWidth, newHeight);
+ float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f);
+ if (bitmap.getWidth() < bitmap.getHeight()) {
+ // Portrait mode: status bar is at the top, navbar bottom, width unchanged
+ newHeight = bitmap.getHeight() - statusBarHeight - navBarHeight;
+ } else {
+ // Landscape mode: status bar is at the top
+ // Navbar: bottom on >599dp width devices, otherwise to the side
+ if (smallestWidthDp > 599) {
+ newHeight = bitmap.getHeight() - statusBarHeight - navBarHeightLandscape;
+ } else {
+ newHeight = bitmap.getHeight() - statusBarHeight;
+ newWidth = bitmap.getWidth() - navBarWidth;
+ }
+ }
+ bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight);
+
+ return bitmap;
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet ||
+ animation == mFadeInAnimator) {
+ // These all indicate the end of the animation
+ dismiss();
+ } else if (animation == mFastSendAnimator) {
+ // After sending is done and we've faded out, reset the scale to 1
+ // so we can fade it back in.
+ mScreenshotView.setScaleX(1.0f);
+ mScreenshotView.setScaleY(1.0f);
+ } else if (animation == mPreAnimator) {
+ if (mHardwareAccelerated && (mState == STATE_W4_TOUCH || mState == STATE_W4_NFC_TAP)) {
+ mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight);
+ }
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) { }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+
+ @Override
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ // This gets called on animation vsync
+ if (++mRenderedFrames < 4) {
+ // For the first 3 frames, call invalidate(); this calls eglSwapBuffers
+ // on the surface, which will allocate large buffers the first three calls
+ // as Android uses triple buffering.
+ mScreenshotLayout.invalidate();
+ } else {
+ // Buffers should be allocated, start the real animation
+ mFrameCounterAnimator.cancel();
+ mPreAnimator.start();
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mState != STATE_W4_TOUCH) {
+ return false;
+ }
+ mState = STATE_SENDING;
+ // Ignore future touches
+ mScreenshotView.setOnTouchListener(null);
+
+ // Cancel any ongoing animations
+ mFrameCounterAnimator.cancel();
+ mPreAnimator.cancel();
+
+ mCallback.onSendConfirmed();
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ if (mHardwareAccelerated && mState < STATE_COMPLETE) {
+ mRenderedFrames = 0;
+
+ mFrameCounterAnimator.start();
+ mSurface = surface;
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ // Since we've disabled orientation changes, we can safely ignore this
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ mSurface = null;
+
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) { }
+
+ public void showSendHint() {
+ if (mAlphaDownAnimator.isRunning()) {
+ mAlphaDownAnimator.cancel();
+ }
+ if (mSlowSendAnimator.isRunning()) {
+ mSlowSendAnimator.cancel();
+ }
+ mBlackLayer.setScaleX(mScreenshotView.getScaleX());
+ mBlackLayer.setScaleY(mScreenshotView.getScaleY());
+ mBlackLayer.setVisibility(View.VISIBLE);
+ mTextHint.setVisibility(View.GONE);
+
+ mTextRetry.setText(mContext.getResources().getString(R.string.beam_try_again));
+ mTextRetry.setVisibility(View.VISIBLE);
+
+ PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha",
+ new float[] {mBlackLayer.getAlpha(), 0.9f});
+ mAlphaUpAnimator.setValues(alphaUp);
+ mAlphaUpAnimator.start();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ mCallback.onCanceled();
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
+ keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ // Treat as if it's a touch event
+ return onTouch(mScreenshotView, null);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return mScreenshotLayout.dispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams attrs) {
+ }
+
+ @Override
+ public void onContentChanged() {
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+
+ }
+
+ @Override
+ public boolean onSearchRequested(SearchEvent searchEvent) {
+ return onSearchRequested();
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(
+ android.view.ActionMode.Callback callback) {
+ return null;
+ }
+
+ public ActionMode onWindowStartingActionMode(
+ android.view.ActionMode.Callback callback, int type) {
+ return null;
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java b/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java
new file mode 100644
index 0000000..96225b7
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.nfc.NfcService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AidRoutingManager {
+ static final String TAG = "AidRoutingManager";
+
+ static final boolean DBG = false;
+
+ static final int ROUTE_HOST = 0x00;
+
+ // Every routing table entry is matched exact
+ static final int AID_MATCHING_EXACT_ONLY = 0x00;
+ // Every routing table entry can be matched either exact or prefix
+ static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01;
+ // Every routing table entry is matched as a prefix
+ static final int AID_MATCHING_PREFIX_ONLY = 0x02;
+
+ // This is the default IsoDep protocol route; it means
+ // that for any AID that needs to be routed to this
+ // destination, we won't need to add a rule to the routing
+ // table, because this destination is already the default route.
+ //
+ // For Nexus devices, the default route is always 0x00.
+ final int mDefaultRoute;
+
+ // For Nexus devices, just a static route to the eSE
+ // OEMs/Carriers could manually map off-host AIDs
+ // to the correct eSE/UICC based on state they keep.
+ final int mDefaultOffHostRoute;
+
+ // How the NFC controller can match AIDs in the routing table;
+ // see AID_MATCHING constants
+ final int mAidMatchingSupport;
+
+ final Object mLock = new Object();
+
+ // mAidRoutingTable contains the current routing table. The index is the route ID.
+ // The route can include routes to a eSE/UICC.
+ SparseArray> mAidRoutingTable =
+ new SparseArray>();
+
+ // Easy look-up what the route is for a certain AID
+ HashMap mRouteForAid = new HashMap();
+
+ private native int doGetDefaultRouteDestination();
+ private native int doGetDefaultOffHostRouteDestination();
+ private native int doGetAidMatchingMode();
+
+ public AidRoutingManager() {
+ mDefaultRoute = doGetDefaultRouteDestination();
+ if (DBG) Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
+ mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination();
+ if (DBG) Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
+ mAidMatchingSupport = doGetAidMatchingMode();
+ if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
+ }
+
+ public boolean supportsAidPrefixRouting() {
+ return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
+ mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY;
+ }
+
+ void clearNfcRoutingTableLocked() {
+ for (Map.Entry aidEntry : mRouteForAid.entrySet()) {
+ String aid = aidEntry.getKey();
+ if (aid.endsWith("*")) {
+ if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
+ Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid
+ + "] is registered");
+ } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
+ if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
+ // Cut off '*' since controller anyway treats all AIDs as a prefix
+ aid = aid.substring(0, aid.length() - 1);
+ } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
+ if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Unrouting exact AID " + aid);
+ }
+
+ NfcService.getInstance().unrouteAids(aid);
+ }
+ }
+
+ public boolean configureRouting(HashMap aidMap) {
+ SparseArray> aidRoutingTable = new SparseArray>(aidMap.size());
+ HashMap routeForAid = new HashMap(aidMap.size());
+ // Then, populate internal data structures first
+ for (Map.Entry aidEntry : aidMap.entrySet()) {
+ int route = aidEntry.getValue() ? ROUTE_HOST : mDefaultOffHostRoute;
+ String aid = aidEntry.getKey();
+ Set entries = aidRoutingTable.get(route, new HashSet());
+ entries.add(aid);
+ aidRoutingTable.put(route, entries);
+ routeForAid.put(aid, route);
+ }
+
+ synchronized (mLock) {
+ if (routeForAid.equals(mRouteForAid)) {
+ if (DBG) Log.d(TAG, "Routing table unchanged, not updating");
+ return false;
+ }
+
+ // Otherwise, update internal structures and commit new routing
+ clearNfcRoutingTableLocked();
+ mRouteForAid = routeForAid;
+ mAidRoutingTable = aidRoutingTable;
+ if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
+ /* If a non-default route registers an exact AID which is shorter
+ * than this exact AID, this will create a problem with controllers
+ * that treat every AID in the routing table as a prefix.
+ * For example, if App A registers F0000000041010 as an exact AID,
+ * and App B registers F000000004 as an exact AID, and App B is not
+ * the default route, the following would be added to the routing table:
+ * F000000004 -> non-default destination
+ * However, because in this mode, the controller treats every routing table
+ * entry as a prefix, it means F0000000041010 would suddenly go to the non-default
+ * destination too, whereas it should have gone to the default.
+ *
+ * The only way to prevent this is to add the longer AIDs of the
+ * default route at the top of the table, so they will be matched first.
+ */
+ Set defaultRouteAids = mAidRoutingTable.get(mDefaultRoute);
+ if (defaultRouteAids != null) {
+ for (String defaultRouteAid : defaultRouteAids) {
+ // Check whether there are any shorted AIDs routed to non-default
+ // TODO this is O(N^2) run-time complexity...
+ for (Map.Entry aidEntry : mRouteForAid.entrySet()) {
+ String aid = aidEntry.getKey();
+ int route = aidEntry.getValue();
+ if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) {
+ if (DBG)
+ Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " +
+ "route, because a conflicting shorter AID will be " +
+ "added to the routing table");
+ NfcService.getInstance().routeAids(defaultRouteAid, mDefaultRoute);
+ }
+ }
+ }
+ }
+ }
+
+ // Add AID entries for all non-default routes
+ for (int i = 0; i < mAidRoutingTable.size(); i++) {
+ int route = mAidRoutingTable.keyAt(i);
+ if (route != mDefaultRoute) {
+ Set aidsForRoute = mAidRoutingTable.get(route);
+ for (String aid : aidsForRoute) {
+ if (aid.endsWith("*")) {
+ if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
+ Log.e(TAG, "This device does not support prefix AIDs.");
+ } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
+ if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
+ + Integer.toString(route));
+ // Cut off '*' since controller anyway treats all AIDs as a prefix
+ NfcService.getInstance().routeAids(aid.substring(0,
+ aid.length() - 1), route);
+ } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
+ if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
+ + Integer.toString(route));
+ NfcService.getInstance().routeAids(aid, route);
+ }
+ } else {
+ if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route "
+ + Integer.toString(route));
+ NfcService.getInstance().routeAids(aid, route);
+ }
+ }
+ }
+ }
+ }
+
+ // And finally commit the routing
+ NfcService.getInstance().commitRouting();
+
+ return true;
+ }
+
+ /**
+ * This notifies that the AID routing table in the controller
+ * has been cleared (usually due to NFC being turned off).
+ */
+ public void onNfccRoutingTableCleared() {
+ // The routing table in the controller was cleared
+ // To stay in sync, clear our own tables.
+ synchronized (mLock) {
+ mAidRoutingTable.clear();
+ mRouteForAid.clear();
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Routing table:");
+ pw.println(" Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element"));
+ synchronized (mLock) {
+ for (int i = 0; i < mAidRoutingTable.size(); i++) {
+ Set aids = mAidRoutingTable.valueAt(i);
+ pw.println(" Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":");
+ for (String aid : aids) {
+ pw.println(" \"" + aid + "\"");
+ }
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java b/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java
new file mode 100644
index 0000000..5ed96c0
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+public class AppChooserActivity extends AlertActivity
+ implements AdapterView.OnItemClickListener {
+
+ static final String TAG = "AppChooserActivity";
+
+ public static final String EXTRA_APDU_SERVICES = "services";
+ public static final String EXTRA_CATEGORY = "category";
+ public static final String EXTRA_FAILED_COMPONENT = "failed_component";
+
+ private int mIconSize;
+ private ListView mListView;
+ private ListAdapter mListAdapter;
+ private CardEmulation mCardEmuManager;
+ private String mCategory;
+
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ finish();
+ }
+ };
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ }
+
+ protected void onCreate(Bundle savedInstanceState, String category,
+ ArrayList options, ComponentName failedComponent) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(mReceiver, filter);
+
+ if ((options == null || options.size() == 0) && failedComponent == null) {
+ Log.e(TAG, "No components passed in.");
+ finish();
+ return;
+ }
+
+ mCategory = category;
+ boolean isPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
+
+ final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
+ mCardEmuManager = CardEmulation.getInstance(adapter);
+ AlertController.AlertParams ap = mAlertParams;
+
+ final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+ mIconSize = am.getLauncherLargeIconSize();
+
+ // Three cases:
+ // 1. Failed component and no alternatives: just an OK box
+ // 2. Failed component and alternatives: pick alternative
+ // 3. No failed component and alternatives: pick alternative
+ PackageManager pm = getPackageManager();
+
+ CharSequence applicationLabel = "unknown";
+ if (failedComponent != null) {
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0);
+ applicationLabel = info.loadLabel(pm);
+ } catch (NameNotFoundException e) {
+ }
+
+ }
+ if (options.size() == 0 && failedComponent != null) {
+ String formatString = getString(com.android.nfc.R.string.transaction_failure);
+ ap.mTitle = "";
+ ap.mMessage = String.format(formatString, applicationLabel);
+ ap.mPositiveButtonText = getString(R.string.ok);
+ setupAlert();
+ } else {
+ mListAdapter = new ListAdapter(this, options);
+ if (failedComponent != null) {
+ String formatString = getString(com.android.nfc.R.string.could_not_use_app);
+ ap.mTitle = String.format(formatString, applicationLabel);
+ ap.mNegativeButtonText = getString(R.string.cancel);
+ } else {
+ if (CardEmulation.CATEGORY_PAYMENT.equals(category)) {
+ ap.mTitle = getString(com.android.nfc.R.string.pay_with);
+ } else {
+ ap.mTitle = getString(com.android.nfc.R.string.complete_with);
+ }
+ }
+ ap.mView = getLayoutInflater().inflate(com.android.nfc.R.layout.cardemu_resolver, null);
+
+ mListView = (ListView) ap.mView.findViewById(com.android.nfc.R.id.resolver_list);
+ if (isPayment) {
+ mListView.setDivider(getResources().getDrawable(android.R.color.transparent));
+ int height = (int) (getResources().getDisplayMetrics().density * 16);
+ mListView.setDividerHeight(height);
+ } else {
+ mListView.setPadding(0, 0, 0, 0);
+ }
+ mListView.setAdapter(mListAdapter);
+ mListView.setOnItemClickListener(this);
+
+ setupAlert();
+ }
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Intent intent = getIntent();
+ ArrayList services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES);
+ String category = intent.getStringExtra(EXTRA_CATEGORY);
+ ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT);
+ onCreate(savedInstanceState, category, services, failedComponent);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position);
+ mCardEmuManager.setDefaultForNextTap(info.serviceInfo.getComponent());
+ Intent dialogIntent = new Intent(this, TapAgainDialog.class);
+ dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory);
+ dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo);
+ startActivity(dialogIntent);
+ finish();
+ }
+
+ final class DisplayAppInfo {
+ ApduServiceInfo serviceInfo;
+ CharSequence displayLabel;
+ Drawable displayIcon;
+ Drawable displayBanner;
+
+ public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon,
+ Drawable banner) {
+ this.serviceInfo = serviceInfo;
+ displayIcon = icon;
+ displayLabel = label;
+ displayBanner = banner;
+ }
+ }
+
+ final class ListAdapter extends BaseAdapter {
+ private final LayoutInflater mInflater;
+ private final boolean mIsPayment;
+ private List mList;
+
+ public ListAdapter(Context context, ArrayList services) {
+ mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ // For each component, get the corresponding app name and icon
+ PackageManager pm = getPackageManager();
+ mList = new ArrayList();
+ mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
+ for (ApduServiceInfo service : services) {
+ CharSequence label = service.getDescription();
+ if (label == null) label = service.loadLabel(pm);
+ Drawable icon = service.loadIcon(pm);
+ Drawable banner = null;
+ if (mIsPayment) {
+ banner = service.loadBanner(pm);
+ if (banner == null) {
+ Log.e(TAG, "Not showing " + label + " because no banner specified.");
+ continue;
+ }
+ }
+ DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner);
+ mList.add(info);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mList.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mList.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ if (convertView == null) {
+ if (mIsPayment) {
+ view = mInflater.inflate(
+ com.android.nfc.R.layout.cardemu_payment_item, parent, false);
+ } else {
+ view = mInflater.inflate(
+ com.android.nfc.R.layout.cardemu_item, parent, false);
+ }
+ final ViewHolder holder = new ViewHolder(view);
+ view.setTag(holder);
+
+ } else {
+ view = convertView;
+ }
+
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ DisplayAppInfo appInfo = mList.get(position);
+ if (mIsPayment) {
+ holder.banner.setImageDrawable(appInfo.displayBanner);
+ } else {
+ ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
+ lp.width = lp.height = mIconSize;
+ holder.icon.setImageDrawable(appInfo.displayIcon);
+ holder.text.setText(appInfo.displayLabel);
+ }
+ return view;
+ }
+ }
+
+ static class ViewHolder {
+ public TextView text;
+ public ImageView icon;
+ public ImageView banner;
+ public ViewHolder(View view) {
+ text = (TextView) view.findViewById(com.android.nfc.R.id.applabel);
+ icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon);
+ banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java b/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java
new file mode 100644
index 0000000..f34522a
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2014 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.cardemulation;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.INfcCardEmulation;
+import android.nfc.cardemulation.AidGroup;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.nfc.NfcPermissions;
+import com.android.nfc.cardemulation.RegisteredServicesCache;
+
+/**
+ * CardEmulationManager is the central entity
+ * responsible for delegating to individual components
+ * implementing card emulation:
+ * - RegisteredServicesCache keeping track of HCE and SE services on the device
+ * - RegisteredAidCache keeping track of AIDs registered by those services and manages
+ * the routing table in the NFCC.
+ * - HostEmulationManager handles incoming APDUs for the host and forwards to HCE
+ * services as necessary.
+ */
+public class CardEmulationManager implements RegisteredServicesCache.Callback,
+ PreferredServices.Callback {
+ static final String TAG = "CardEmulationManager";
+ static final boolean DBG = false;
+
+ final RegisteredAidCache mAidCache;
+ final RegisteredServicesCache mServiceCache;
+ final HostEmulationManager mHostEmulationManager;
+ final PreferredServices mPreferredServices;
+ final Context mContext;
+ final CardEmulationInterface mCardEmulationInterface;
+
+ public CardEmulationManager(Context context) {
+ mContext = context;
+ mCardEmulationInterface = new CardEmulationInterface();
+ mAidCache = new RegisteredAidCache(context);
+ mHostEmulationManager = new HostEmulationManager(context, mAidCache);
+ mServiceCache = new RegisteredServicesCache(context, this);
+ mPreferredServices = new PreferredServices(context, mServiceCache, mAidCache, this);
+
+ mServiceCache.initialize();
+ }
+
+ public INfcCardEmulation getNfcCardEmulationInterface() {
+ return mCardEmulationInterface;
+ }
+
+ public void onHostCardEmulationActivated() {
+ mHostEmulationManager.onHostEmulationActivated();
+ mPreferredServices.onHostEmulationActivated();
+ }
+
+ public void onHostCardEmulationData(byte[] data) {
+ mHostEmulationManager.onHostEmulationData(data);
+ }
+
+ public void onHostCardEmulationDeactivated() {
+ mHostEmulationManager.onHostEmulationDeactivated();
+ mPreferredServices.onHostEmulationDeactivated();
+ }
+
+ public void onOffHostAidSelected() {
+ mHostEmulationManager.onOffHostAidSelected();
+ }
+
+ public void onUserSwitched(int userId) {
+ mServiceCache.invalidateCache(userId);
+ mPreferredServices.onUserSwitched(userId);
+ }
+
+ public void onNfcEnabled() {
+ mAidCache.onNfcEnabled();
+ }
+
+ public void onNfcDisabled() {
+ mAidCache.onNfcDisabled();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mServiceCache.dump(fd, pw, args);
+ mPreferredServices.dump(fd, pw, args);
+ mAidCache.dump(fd, pw, args);
+ mHostEmulationManager.dump(fd, pw, args);
+ }
+
+ @Override
+ public void onServicesUpdated(int userId, List services) {
+ // Verify defaults are still sane
+ verifyDefaults(userId, services);
+ // Update the AID cache
+ mAidCache.onServicesUpdated(userId, services);
+ // Update the preferred services list
+ mPreferredServices.onServicesUpdated();
+ }
+
+ void verifyDefaults(int userId, List services) {
+ ComponentName defaultPaymentService =
+ getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false);
+ if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService);
+ if (defaultPaymentService != null) {
+ // Validate the default is still installed and handling payment
+ ApduServiceInfo serviceInfo = mServiceCache.getService(userId, defaultPaymentService);
+ if (serviceInfo == null || !serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
+ if (serviceInfo == null) {
+ Log.e(TAG, "Default payment service unexpectedly removed.");
+ } else if (!serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
+ if (DBG) Log.d(TAG, "Default payment service had payment category removed");
+ }
+ int numPaymentServices = 0;
+ ComponentName lastFoundPaymentService = null;
+ for (ApduServiceInfo service : services) {
+ if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
+ numPaymentServices++;
+ lastFoundPaymentService = service.getComponent();
+ }
+ }
+ if (DBG) Log.d(TAG, "Number of payment services is " +
+ Integer.toString(numPaymentServices));
+ if (numPaymentServices == 0) {
+ if (DBG) Log.d(TAG, "Default removed, no services left.");
+ // No payment services left, unset default and don't ask the user
+ setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT);
+ } else if (numPaymentServices == 1) {
+ // Only one left, automatically make it the default
+ if (DBG) Log.d(TAG, "Default removed, making remaining service default.");
+ setDefaultServiceForCategoryChecked(userId, lastFoundPaymentService,
+ CardEmulation.CATEGORY_PAYMENT);
+ } else if (numPaymentServices > 1) {
+ // More than one left, unset default and ask the user if he wants
+ // to set a new one
+ if (DBG) Log.d(TAG, "Default removed, asking user to pick.");
+ setDefaultServiceForCategoryChecked(userId, null,
+ CardEmulation.CATEGORY_PAYMENT);
+ Intent intent = new Intent(mContext, DefaultRemovedActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ } else {
+ // Default still exists and handles the category, nothing do
+ if (DBG) Log.d(TAG, "Default payment service still ok.");
+ }
+ } else {
+ // A payment service may have been removed, leaving only one;
+ // in that case, automatically set that app as default.
+ int numPaymentServices = 0;
+ ComponentName lastFoundPaymentService = null;
+ for (ApduServiceInfo service : services) {
+ if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
+ numPaymentServices++;
+ lastFoundPaymentService = service.getComponent();
+ }
+ }
+ if (numPaymentServices > 1) {
+ // More than one service left, leave default unset
+ if (DBG) Log.d(TAG, "No default set, more than one service left.");
+ } else if (numPaymentServices == 1) {
+ // Make single found payment service the default
+ if (DBG) Log.d(TAG, "No default set, making single service default.");
+ setDefaultServiceForCategoryChecked(userId, lastFoundPaymentService,
+ CardEmulation.CATEGORY_PAYMENT);
+ } else {
+ // No payment services left, leave default at null
+ if (DBG) Log.d(TAG, "No default set, last payment service removed.");
+ }
+ }
+ }
+
+ ComponentName getDefaultServiceForCategory(int userId, String category,
+ boolean validateInstalled) {
+ if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
+ Log.e(TAG, "Not allowing defaults for category " + category);
+ return null;
+ }
+ // Load current payment default from settings
+ String name = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+ userId);
+ if (name != null) {
+ ComponentName service = ComponentName.unflattenFromString(name);
+ if (!validateInstalled || service == null) {
+ return service;
+ } else {
+ return mServiceCache.hasService(userId, service) ? service : null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ boolean setDefaultServiceForCategoryChecked(int userId, ComponentName service,
+ String category) {
+ if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
+ Log.e(TAG, "Not allowing defaults for category " + category);
+ return false;
+ }
+ // TODO Not really nice to be writing to Settings.Secure here...
+ // ideally we overlay our local changes over whatever is in
+ // Settings.Secure
+ if (service == null || mServiceCache.hasService(userId, service)) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+ service != null ? service.flattenToString() : null, userId);
+ } else {
+ Log.e(TAG, "Could not find default service to make default: " + service);
+ }
+ return true;
+ }
+
+ boolean isServiceRegistered(int userId, ComponentName service) {
+ boolean serviceFound = mServiceCache.hasService(userId, service);
+ if (!serviceFound) {
+ // If we don't know about this service yet, it may have just been enabled
+ // using PackageManager.setComponentEnabledSetting(). The PackageManager
+ // broadcasts are delayed by 10 seconds in that scenario, which causes
+ // calls to our APIs referencing that service to fail.
+ // Hence, update the cache in case we don't know about the service.
+ if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache.");
+ mServiceCache.invalidateCache(userId);
+ }
+ return mServiceCache.hasService(userId, service);
+ }
+
+ /**
+ * Returns whether a service in this package is preferred,
+ * either because it's the default payment app or it's running
+ * in the foreground.
+ */
+ public boolean packageHasPreferredService(String packageName) {
+ return mPreferredServices.packageHasPreferredService(packageName);
+ }
+
+ /**
+ * This class implements the application-facing APIs
+ * and are called from binder. All calls must be
+ * permission-checked.
+ *
+ */
+ final class CardEmulationInterface extends INfcCardEmulation.Stub {
+ @Override
+ public boolean isDefaultServiceForCategory(int userId, ComponentName service,
+ String category) {
+ NfcPermissions.enforceUserPermissions(mContext);
+ NfcPermissions.validateUserId(userId);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ ComponentName defaultService =
+ getDefaultServiceForCategory(userId, category, true);
+ return (defaultService != null && defaultService.equals(service));
+ }
+
+ @Override
+ public boolean isDefaultServiceForAid(int userId,
+ ComponentName service, String aid) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ return mAidCache.isDefaultServiceForAid(userId, service, aid);
+ }
+
+ @Override
+ public boolean setDefaultServiceForCategory(int userId,
+ ComponentName service, String category) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceAdminPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ return setDefaultServiceForCategoryChecked(userId, service, category);
+ }
+
+ @Override
+ public boolean setDefaultForNextTap(int userId, ComponentName service)
+ throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceAdminPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ return mPreferredServices.setDefaultForNextTap(service);
+ }
+
+ @Override
+ public boolean registerAidGroupForService(int userId,
+ ComponentName service, AidGroup aidGroup) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ return mServiceCache.registerAidGroupForService(userId, Binder.getCallingUid(), service,
+ aidGroup);
+ }
+
+ @Override
+ public AidGroup getAidGroupForService(int userId,
+ ComponentName service, String category) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return null;
+ }
+ return mServiceCache.getAidGroupForService(userId, Binder.getCallingUid(), service,
+ category);
+ }
+
+ @Override
+ public boolean removeAidGroupForService(int userId,
+ ComponentName service, String category) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ return false;
+ }
+ return mServiceCache.removeAidGroupForService(userId, Binder.getCallingUid(), service,
+ category);
+ }
+
+ @Override
+ public List getServices(int userId, String category)
+ throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceAdminPermissions(mContext);
+ return mServiceCache.getServicesForCategory(userId, category);
+ }
+
+ @Override
+ public boolean setPreferredService(ComponentName service)
+ throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(UserHandle.getCallingUserId(), service)) {
+ Log.e(TAG, "setPreferredService: unknown component.");
+ return false;
+ }
+ return mPreferredServices.registerPreferredForegroundService(service,
+ Binder.getCallingUid());
+ }
+
+ @Override
+ public boolean unsetPreferredService() throws RemoteException {
+ NfcPermissions.enforceUserPermissions(mContext);
+ return mPreferredServices.unregisteredPreferredForegroundService(
+ Binder.getCallingUid());
+
+ }
+
+ @Override
+ public boolean supportsAidPrefixRegistration() throws RemoteException {
+ return mAidCache.supportsAidPrefixRegistration();
+ }
+ }
+
+ @Override
+ public void onPreferredPaymentServiceChanged(ComponentName service) {
+ mAidCache.onPreferredPaymentServiceChanged(service);
+ mHostEmulationManager.onPreferredPaymentServiceChanged(service);
+ }
+
+ @Override
+ public void onPreferredForegroundServiceChanged(ComponentName service) {
+ mAidCache.onPreferredForegroundServiceChanged(service);
+ mHostEmulationManager.onPreferredForegroundServiceChanged(service);
+ };
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java b/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java
new file mode 100644
index 0000000..d9fa7e9
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 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.cardemulation;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+import com.android.internal.R;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+public class DefaultRemovedActivity extends AlertActivity implements
+ DialogInterface.OnClickListener {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
+ super.onCreate(savedInstanceState);
+
+ AlertController.AlertParams ap = mAlertParams;
+
+ ap.mMessage = getString(com.android.nfc.R.string.default_pay_app_removed);
+ ap.mNegativeButtonText = getString(R.string.no);
+ ap.mPositiveButtonText = getString(R.string.yes);
+ ap.mPositiveButtonListener = this;
+ setupAlert();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Launch into Settings
+ Intent intent = new Intent(Settings.ACTION_NFC_PAYMENT_SETTINGS);
+ startActivity(intent);
+ }
+}
\ No newline at end of file
diff --git a/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java b/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java
new file mode 100644
index 0000000..b481130
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.HostApduService;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.nfc.NfcService;
+import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class HostEmulationManager {
+ static final String TAG = "HostEmulationManager";
+ static final boolean DBG = false;
+
+ static final int STATE_IDLE = 0;
+ static final int STATE_W4_SELECT = 1;
+ static final int STATE_W4_SERVICE = 2;
+ static final int STATE_W4_DEACTIVATE = 3;
+ static final int STATE_XFER = 4;
+
+ /** Minimum AID lenth as per ISO7816 */
+ static final int MINIMUM_AID_LENGTH = 5;
+
+ /** Length of Select APDU header including length byte */
+ static final int SELECT_APDU_HDR_LENGTH = 5;
+
+ static final byte INSTR_SELECT = (byte)0xA4;
+
+ static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345";
+ static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00};
+
+ static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82};
+ static final byte[] UNKNOWN_ERROR = {0x6F, 0x00};
+
+ final Context mContext;
+ final RegisteredAidCache mAidCache;
+ final Messenger mMessenger = new Messenger (new MessageHandler());
+ final KeyguardManager mKeyguard;
+ final Object mLock;
+
+ // All variables below protected by mLock
+
+ // Variables below are for a non-payment service,
+ // that is typically only bound in the STATE_XFER state.
+ Messenger mService;
+ boolean mServiceBound;
+ ComponentName mServiceName;
+
+ // Variables below are for a payment service,
+ // which is typically bound persistently to improve on
+ // latency.
+ Messenger mPaymentService;
+ boolean mPaymentServiceBound;
+ ComponentName mPaymentServiceName;
+
+ // mActiveService denotes the service interface
+ // that is the current active one, until a new SELECT AID
+ // comes in that may be resolved to a different service.
+ // On deactivation, mActiveService stops being valid.
+ Messenger mActiveService;
+ ComponentName mActiveServiceName;
+
+ String mLastSelectedAid;
+ int mState;
+ byte[] mSelectApdu;
+
+ public HostEmulationManager(Context context, RegisteredAidCache aidCache) {
+ mContext = context;
+ mLock = new Object();
+ mAidCache = aidCache;
+ mState = STATE_IDLE;
+ mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+ }
+
+ public void onPreferredPaymentServiceChanged(ComponentName service) {
+ synchronized (mLock) {
+ if (service != null) {
+ bindPaymentServiceLocked(ActivityManager.getCurrentUser(), service);
+ } else {
+ unbindPaymentServiceLocked();
+ }
+ }
+ }
+
+ public void onPreferredForegroundServiceChanged(ComponentName service) {
+ synchronized (mLock) {
+ if (service != null) {
+ bindServiceIfNeededLocked(service);
+ } else {
+ unbindServiceIfNeededLocked();
+ }
+ }
+ }
+
+ public void onHostEmulationActivated() {
+ Log.d(TAG, "notifyHostEmulationActivated");
+ synchronized (mLock) {
+ // Regardless of what happens, if we're having a tap again
+ // activity up, close it
+ Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
+ intent.setPackage("com.android.nfc");
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ if (mState != STATE_IDLE) {
+ Log.e(TAG, "Got activation event in non-idle state");
+ }
+ mState = STATE_W4_SELECT;
+ }
+ }
+
+ public void onHostEmulationData(byte[] data) {
+ Log.d(TAG, "notifyHostEmulationData");
+ String selectAid = findSelectAid(data);
+ ComponentName resolvedService = null;
+ synchronized (mLock) {
+ if (mState == STATE_IDLE) {
+ Log.e(TAG, "Got data in idle state.");
+ return;
+ } else if (mState == STATE_W4_DEACTIVATE) {
+ Log.e(TAG, "Dropping APDU in STATE_W4_DECTIVATE");
+ return;
+ }
+ if (selectAid != null) {
+ if (selectAid.equals(ANDROID_HCE_AID)) {
+ NfcService.getInstance().sendData(ANDROID_HCE_RESPONSE);
+ return;
+ }
+ AidResolveInfo resolveInfo = mAidCache.resolveAid(selectAid);
+ if (resolveInfo == null || resolveInfo.services.size() == 0) {
+ // Tell the remote we don't handle this AID
+ NfcService.getInstance().sendData(AID_NOT_FOUND);
+ return;
+ }
+ mLastSelectedAid = selectAid;
+ if (resolveInfo.defaultService != null) {
+ // Resolve to default
+ // Check if resolvedService requires unlock
+ ApduServiceInfo defaultServiceInfo = resolveInfo.defaultService;
+ if (defaultServiceInfo.requiresUnlock() &&
+ mKeyguard.isKeyguardLocked() && mKeyguard.isKeyguardSecure()) {
+ // Just ignore all future APDUs until next tap
+ mState = STATE_W4_DEACTIVATE;
+ launchTapAgain(resolveInfo.defaultService, resolveInfo.category);
+ return;
+ }
+ // In no circumstance should this be an OffHostService -
+ // we should never get this AID on the host in the first place
+ if (!defaultServiceInfo.isOnHost()) {
+ Log.e(TAG, "AID that was meant to go off-host was routed to host." +
+ " Check routing table configuration.");
+ NfcService.getInstance().sendData(AID_NOT_FOUND);
+ return;
+ }
+ resolvedService = defaultServiceInfo.getComponent();
+ } else if (mActiveServiceName != null) {
+ for (ApduServiceInfo serviceInfo : resolveInfo.services) {
+ if (mActiveServiceName.equals(serviceInfo.getComponent())) {
+ resolvedService = mActiveServiceName;
+ break;
+ }
+ }
+ }
+ if (resolvedService == null) {
+ // We have no default, and either one or more services.
+ // Ask the user to confirm.
+ // Just ignore all future APDUs until we resolve to only one
+ mState = STATE_W4_DEACTIVATE;
+ launchResolver((ArrayList)resolveInfo.services, null,
+ resolveInfo.category);
+ return;
+ }
+ }
+ switch (mState) {
+ case STATE_W4_SELECT:
+ if (selectAid != null) {
+ Messenger existingService = bindServiceIfNeededLocked(resolvedService);
+ if (existingService != null) {
+ Log.d(TAG, "Binding to existing service");
+ mState = STATE_XFER;
+ sendDataToServiceLocked(existingService, data);
+ } else {
+ // Waiting for service to be bound
+ Log.d(TAG, "Waiting for new service.");
+ // Queue SELECT APDU to be used
+ mSelectApdu = data;
+ mState = STATE_W4_SERVICE;
+ }
+ } else {
+ Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT");
+ NfcService.getInstance().sendData(UNKNOWN_ERROR);
+ }
+ break;
+ case STATE_W4_SERVICE:
+ Log.d(TAG, "Unexpected APDU in STATE_W4_SERVICE");
+ break;
+ case STATE_XFER:
+ if (selectAid != null) {
+ Messenger existingService = bindServiceIfNeededLocked(resolvedService);
+ if (existingService != null) {
+ sendDataToServiceLocked(existingService, data);
+ mState = STATE_XFER;
+ } else {
+ // Waiting for service to be bound
+ mSelectApdu = data;
+ mState = STATE_W4_SERVICE;
+ }
+ } else if (mActiveService != null) {
+ // Regular APDU data
+ sendDataToServiceLocked(mActiveService, data);
+ } else {
+ // No SELECT AID and no active service.
+ Log.d(TAG, "Service no longer bound, dropping APDU");
+ }
+ break;
+ }
+ }
+ }
+
+ public void onHostEmulationDeactivated() {
+ Log.d(TAG, "notifyHostEmulationDeactivated");
+ synchronized (mLock) {
+ if (mState == STATE_IDLE) {
+ Log.e(TAG, "Got deactivation event while in idle state");
+ }
+ sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_LINK_LOSS);
+ mActiveService = null;
+ mActiveServiceName = null;
+ unbindServiceIfNeededLocked();
+ mState = STATE_IDLE;
+ }
+ }
+
+ public void onOffHostAidSelected() {
+ Log.d(TAG, "notifyOffHostAidSelected");
+ synchronized (mLock) {
+ if (mState != STATE_XFER || mActiveService == null) {
+ // Don't bother telling, we're not bound to any service yet
+ } else {
+ sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
+ }
+ mActiveService = null;
+ mActiveServiceName = null;
+ unbindServiceIfNeededLocked();
+ mState = STATE_W4_SELECT;
+
+ //close the TapAgainDialog
+ Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
+ intent.setPackage("com.android.nfc");
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+ }
+
+ Messenger bindServiceIfNeededLocked(ComponentName service) {
+ if (mPaymentServiceBound && mPaymentServiceName.equals(service)) {
+ Log.d(TAG, "Service already bound as payment service.");
+ return mPaymentService;
+ } else if (mServiceBound && mServiceName.equals(service)) {
+ Log.d(TAG, "Service already bound as regular service.");
+ return mService;
+ } else {
+ Log.d(TAG, "Binding to service " + service);
+ unbindServiceIfNeededLocked();
+ Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE);
+ aidIntent.setComponent(service);
+ if (mContext.bindServiceAsUser(aidIntent, mConnection,
+ Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+ } else {
+ Log.e(TAG, "Could not bind service.");
+ }
+ return null;
+ }
+ }
+
+ void sendDataToServiceLocked(Messenger service, byte[] data) {
+ if (service != mActiveService) {
+ sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
+ mActiveService = service;
+ if (service.equals(mPaymentService)) {
+ mActiveServiceName = mPaymentServiceName;
+ } else {
+ mActiveServiceName = mServiceName;
+ }
+ }
+ Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU);
+ Bundle dataBundle = new Bundle();
+ dataBundle.putByteArray("data", data);
+ msg.setData(dataBundle);
+ msg.replyTo = mMessenger;
+ try {
+ mActiveService.send(msg);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote service has died, dropping APDU");
+ }
+ }
+
+ void sendDeactivateToActiveServiceLocked(int reason) {
+ if (mActiveService == null) return;
+ Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED);
+ msg.arg1 = reason;
+ try {
+ mActiveService.send(msg);
+ } catch (RemoteException e) {
+ // Don't care
+ }
+ }
+
+ void unbindPaymentServiceLocked() {
+ if (mPaymentServiceBound) {
+ mContext.unbindService(mPaymentConnection);
+ mPaymentServiceBound = false;
+ mPaymentService = null;
+ mPaymentServiceName = null;
+ }
+ }
+
+ void bindPaymentServiceLocked(int userId, ComponentName service) {
+ unbindPaymentServiceLocked();
+
+ Intent intent = new Intent(HostApduService.SERVICE_INTERFACE);
+ intent.setComponent(service);
+ if (!mContext.bindServiceAsUser(intent, mPaymentConnection,
+ Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
+ Log.e(TAG, "Could not bind (persistent) payment service.");
+ }
+ }
+
+ void unbindServiceIfNeededLocked() {
+ if (mServiceBound) {
+ Log.d(TAG, "Unbinding from service " + mServiceName);
+ mContext.unbindService(mConnection);
+ mServiceBound = false;
+ mService = null;
+ mServiceName = null;
+ }
+ }
+
+ void launchTapAgain(ApduServiceInfo service, String category) {
+ Intent dialogIntent = new Intent(mContext, TapAgainDialog.class);
+ dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, category);
+ dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service);
+ dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(dialogIntent, UserHandle.CURRENT);
+ }
+
+ void launchResolver(ArrayList services, ComponentName failedComponent,
+ String category) {
+ Intent intent = new Intent(mContext, AppChooserActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES, services);
+ intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category);
+ if (failedComponent != null) {
+ intent.putExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT, failedComponent);
+ }
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
+ String findSelectAid(byte[] data) {
+ if (data == null || data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH) {
+ if (DBG) Log.d(TAG, "Data size too small for SELECT APDU");
+ return null;
+ }
+ // To accept a SELECT AID for dispatch, we require the following:
+ // Class byte must be 0x00: logical channel set to zero, no secure messaging, no chaining
+ // Instruction byte must be 0xA4: SELECT instruction
+ // P1: must be 0x04: select by application identifier
+ // P2: File control information is only relevant for higher-level application,
+ // and we only support "first or only occurrence".
+ if (data[0] == 0x00 && data[1] == INSTR_SELECT && data[2] == 0x04) {
+ if (data[3] != 0x00) {
+ Log.d(TAG, "Selecting next, last or previous AID occurrence is not supported");
+ }
+ int aidLength = data[4];
+ if (data.length < SELECT_APDU_HDR_LENGTH + aidLength) {
+ return null;
+ }
+ return bytesToString(data, SELECT_APDU_HDR_LENGTH, aidLength);
+ }
+ return null;
+ }
+
+ private ServiceConnection mPaymentConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mPaymentServiceName = name;
+ mPaymentService = new Messenger(service);
+ mPaymentServiceBound = true;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ mPaymentService = null;
+ mPaymentServiceBound = false;
+ mPaymentServiceName = null;
+ }
+ }
+ };
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mService = new Messenger(service);
+ mServiceBound = true;
+ mServiceName = name;
+ Log.d(TAG, "Service bound");
+ mState = STATE_XFER;
+ // Send pending select APDU
+ if (mSelectApdu != null) {
+ sendDataToServiceLocked(mService, mSelectApdu);
+ mSelectApdu = null;
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ Log.d(TAG, "Service unbound");
+ mService = null;
+ mServiceBound = false;
+ }
+ }
+ };
+
+ class MessageHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized(mLock) {
+ if (mActiveService == null) {
+ Log.d(TAG, "Dropping service response message; service no longer active.");
+ return;
+ } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) {
+ Log.d(TAG, "Dropping service response message; service no longer bound.");
+ return;
+ }
+ }
+ if (msg.what == HostApduService.MSG_RESPONSE_APDU) {
+ Bundle dataBundle = msg.getData();
+ if (dataBundle == null) {
+ return;
+ }
+ byte[] data = dataBundle.getByteArray("data");
+ if (data == null || data.length == 0) {
+ Log.e(TAG, "Dropping empty R-APDU");
+ return;
+ }
+ int state;
+ synchronized(mLock) {
+ state = mState;
+ }
+ if (state == STATE_XFER) {
+ Log.d(TAG, "Sending data");
+ NfcService.getInstance().sendData(data);
+ } else {
+ Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state));
+ }
+ } else if (msg.what == HostApduService.MSG_UNHANDLED) {
+ synchronized (mLock) {
+ AidResolveInfo resolveInfo = mAidCache.resolveAid(mLastSelectedAid);
+ boolean isPayment = false;
+ if (resolveInfo.services.size() > 0) {
+ launchResolver((ArrayList)resolveInfo.services,
+ mActiveServiceName, resolveInfo.category);
+ }
+ }
+ }
+ }
+ }
+
+ static String bytesToString(byte[] bytes, int offset, int length) {
+ final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+ char[] chars = new char[length * 2];
+ int byteValue;
+ for (int j = 0; j < length; j++) {
+ byteValue = bytes[offset + j] & 0xFF;
+ chars[j * 2] = hexChars[byteValue >>> 4];
+ chars[j * 2 + 1] = hexChars[byteValue & 0x0F];
+ }
+ return new String(chars);
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Bound services: ");
+ if (mPaymentServiceBound) {
+ pw.println(" payment: " + mPaymentServiceName);
+ }
+ if (mServiceBound) {
+ pw.println(" other: " + mServiceName);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java b/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java
new file mode 100644
index 0000000..9118aa5
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2014 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.cardemulation;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.nfc.ForegroundUtils;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+/**
+ * This class keeps track of what HCE/SE-based services are
+ * preferred by the user. It currently has 3 inputs:
+ * 1) The default set in tap&pay menu for payment category
+ * 2) An app in the foreground asking for a specific
+ * service for a specific category
+ * 3) If we had to disambiguate a previous tap (because no
+ * preferred service was there), we need to temporarily
+ * store the user's choice for the next tap.
+ *
+ * This class keeps track of all 3 inputs, and computes a new
+ * preferred services as needed. It then passes this service
+ * (if it changed) through a callback, which allows other components
+ * to adapt as necessary (ie the AID cache can update its AID
+ * mappings and the routing table).
+ */
+public class PreferredServices implements com.android.nfc.ForegroundUtils.Callback {
+ static final String TAG = "PreferredCardEmulationServices";
+ static final Uri paymentDefaultUri = Settings.Secure.getUriFor(
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+ static final Uri paymentForegroundUri = Settings.Secure.getUriFor(
+ Settings.Secure.NFC_PAYMENT_FOREGROUND);
+
+ final SettingsObserver mSettingsObserver;
+ final Context mContext;
+ final RegisteredServicesCache mServiceCache;
+ final RegisteredAidCache mAidCache;
+ final Callback mCallback;
+ final ForegroundUtils mForegroundUtils = ForegroundUtils.getInstance();
+ final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ final class PaymentDefaults {
+ boolean preferForeground; // The current selection mode for this category
+ ComponentName settingsDefault; // The component preferred in settings (eg Tap&Pay)
+ ComponentName currentPreferred; // The computed preferred component
+ }
+
+ final Object mLock = new Object();
+ // Variables below synchronized on mLock
+ PaymentDefaults mPaymentDefaults = new PaymentDefaults();
+
+ ComponentName mForegroundRequested; // The component preferred by fg app
+ int mForegroundUid; // The UID of the fg app, or -1 if fg app didn't request
+
+ ComponentName mNextTapDefault; // The component preferred by active disambig dialog
+ boolean mClearNextTapDefault = false; // Set when the next tap default must be cleared
+
+ ComponentName mForegroundCurrent; // The currently computed foreground component
+
+ public interface Callback {
+ void onPreferredPaymentServiceChanged(ComponentName service);
+ void onPreferredForegroundServiceChanged(ComponentName service);
+ }
+
+ public PreferredServices(Context context, RegisteredServicesCache serviceCache,
+ RegisteredAidCache aidCache, Callback callback) {
+ mContext = context;
+ mServiceCache = serviceCache;
+ mAidCache = aidCache;
+ mCallback = callback;
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mContext.getContentResolver().registerContentObserver(
+ paymentDefaultUri,
+ true, mSettingsObserver, UserHandle.USER_ALL);
+
+ mContext.getContentResolver().registerContentObserver(
+ paymentForegroundUri,
+ true, mSettingsObserver, UserHandle.USER_ALL);
+
+ // Load current settings defaults for payments
+ loadDefaultsFromSettings(ActivityManager.getCurrentUser());
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ // Do it just for the current user. If it was in fact
+ // a change made for another user, we'll sync it down
+ // on user switch.
+ int currentUser = ActivityManager.getCurrentUser();
+ loadDefaultsFromSettings(currentUser);
+ }
+ };
+
+ void loadDefaultsFromSettings(int userId) {
+ boolean paymentDefaultChanged = false;
+ boolean paymentPreferForegroundChanged = false;
+ // Load current payment default from settings
+ String name = Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+ userId);
+ ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null;
+ boolean preferForeground = false;
+ try {
+ preferForeground = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+ } catch (SettingNotFoundException e) {
+ }
+ synchronized (mLock) {
+ paymentPreferForegroundChanged = (preferForeground != mPaymentDefaults.preferForeground);
+ mPaymentDefaults.preferForeground = preferForeground;
+
+ mPaymentDefaults.settingsDefault = newDefault;
+ if (newDefault != null && !newDefault.equals(mPaymentDefaults.currentPreferred)) {
+ paymentDefaultChanged = true;
+ mPaymentDefaults.currentPreferred = newDefault;
+ } else if (newDefault == null && mPaymentDefaults.currentPreferred != null) {
+ paymentDefaultChanged = true;
+ mPaymentDefaults.currentPreferred = newDefault;
+ } else {
+ // Same default as before
+ }
+ }
+ // Notify if anything changed
+ if (paymentDefaultChanged) {
+ mCallback.onPreferredPaymentServiceChanged(newDefault);
+ }
+ if (paymentPreferForegroundChanged) {
+ computePreferredForegroundService();
+ }
+ }
+
+ void computePreferredForegroundService() {
+ ComponentName preferredService = null;
+ boolean changed = false;
+ synchronized (mLock) {
+ // Prio 1: next tap default
+ preferredService = mNextTapDefault;
+ if (preferredService == null) {
+ // Prio 2: foreground requested by app
+ preferredService = mForegroundRequested;
+ }
+ if (preferredService != null && !preferredService.equals(mForegroundCurrent)) {
+ mForegroundCurrent = preferredService;
+ changed = true;
+ } else if (preferredService == null && mForegroundCurrent != null){
+ mForegroundCurrent = preferredService;
+ changed = true;
+ }
+ }
+ // Notify if anything changed
+ if (changed) {
+ mCallback.onPreferredForegroundServiceChanged(preferredService);
+ }
+ }
+
+ public boolean setDefaultForNextTap(ComponentName service) {
+ // This is a trusted API, so update without checking
+ synchronized (mLock) {
+ mNextTapDefault = service;
+ }
+ computePreferredForegroundService();
+ return true;
+ }
+
+ public void onServicesUpdated() {
+ // If this service is the current foreground service, verify
+ // there are no conflicts
+ boolean changed = false;
+ synchronized (mLock) {
+ // Check if the current foreground service is still allowed to override;
+ // it could have registered new AIDs that make it conflict with user
+ // preferences.
+ if (mForegroundCurrent != null) {
+ if (!isForegroundAllowedLocked(mForegroundCurrent)) {
+ Log.d(TAG, "Removing foreground preferred service because of conflict.");
+ mForegroundRequested = null;
+ mForegroundUid = -1;
+ changed = true;
+ }
+ } else {
+ // Don't care about this service
+ }
+ }
+ if (changed) {
+ computePreferredForegroundService();
+ }
+ }
+
+ // Verifies whether a service is allowed to register as preferred
+ boolean isForegroundAllowedLocked(ComponentName service) {
+ if (service.equals(mPaymentDefaults.currentPreferred)) {
+ // If the requester is already the payment default, allow it to request foreground
+ // override as well (it could use this to make sure it handles AIDs of category OTHER)
+ return true;
+ }
+ ApduServiceInfo serviceInfo = mServiceCache.getService(ActivityManager.getCurrentUser(),
+ service);
+ // Do some sanity checking
+ if (!mPaymentDefaults.preferForeground) {
+ // Foreground apps are not allowed to override payment default
+ // Check if this app registers payment AIDs, in which case we'll fail anyway
+ if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
+ Log.d(TAG, "User doesn't allow payment services to be overridden.");
+ return false;
+ }
+ // If no payment AIDs, get AIDs of category other, and see if there's any
+ // conflict with payment AIDs of current default payment app. That means
+ // the current default payment app said this was a payment AID, and the
+ // foreground app says it was not. In this case we'll still prefer the payment
+ // app, since that is the one that the user has explicitly selected (and said
+ // it's not allowed to be overridden).
+ final List otherAids = serviceInfo.getAids();
+ ApduServiceInfo paymentServiceInfo = mServiceCache.getService(
+ ActivityManager.getCurrentUser(), mPaymentDefaults.currentPreferred);
+ if (paymentServiceInfo != null && otherAids != null && otherAids.size() > 0) {
+ for (String aid : otherAids) {
+ RegisteredAidCache.AidResolveInfo resolveInfo = mAidCache.resolveAid(aid);
+ if (CardEmulation.CATEGORY_PAYMENT.equals(resolveInfo.category) &&
+ paymentServiceInfo.equals(resolveInfo.defaultService)) {
+ Log.d(TAG, "AID " + aid + " is handled by the default payment app, " +
+ "and the user has not allowed payments to be overridden.");
+ return false;
+ }
+ }
+ return true;
+ } else {
+ // Could not find payment service or fg app doesn't register other AIDs;
+ // okay to proceed.
+ return true;
+ }
+ } else {
+ // Payment allows override, so allow anything.
+ return true;
+ }
+ }
+
+ public boolean registerPreferredForegroundService(ComponentName service, int callingUid) {
+ boolean success = false;
+ synchronized (mLock) {
+ if (isForegroundAllowedLocked(service)) {
+ if (mForegroundUtils.registerUidToBackgroundCallback(this, callingUid)) {
+ mForegroundRequested = service;
+ mForegroundUid = callingUid;
+ success = true;
+ } else {
+ Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
+ success = false;
+ }
+ } else {
+ Log.e(TAG, "Requested foreground service conflicts with default payment app.");
+ }
+ }
+ if (success) {
+ computePreferredForegroundService();
+ }
+ return success;
+ }
+
+ boolean unregisterForegroundService(int uid) {
+ boolean success = false;
+ synchronized (mLock) {
+ if (mForegroundUid == uid) {
+ mForegroundRequested = null;
+ mForegroundUid = -1;
+ success = true;
+ } // else, other UID in foreground
+ }
+ if (success) {
+ computePreferredForegroundService();
+ }
+ return success;
+ }
+
+ public boolean unregisteredPreferredForegroundService(int callingUid) {
+ // Verify the calling UID is in the foreground
+ if (mForegroundUtils.isInForeground(callingUid)) {
+ return unregisterForegroundService(callingUid);
+ } else {
+ Log.e(TAG, "Calling UID is not in the foreground, ignorning!");
+ return false;
+ }
+ }
+
+ @Override
+ public void onUidToBackground(int uid) {
+ unregisterForegroundService(uid);
+ }
+
+ public void onHostEmulationActivated() {
+ synchronized (mLock) {
+ mClearNextTapDefault = (mNextTapDefault != null);
+ }
+ }
+
+ public void onHostEmulationDeactivated() {
+ // If we had any next tap defaults set, clear them out
+ boolean changed = false;
+ synchronized (mLock) {
+ if (mClearNextTapDefault) {
+ // The reason we need to check this boolean is because the next tap
+ // default may have been set while the user held the phone
+ // on the reader; when the user then removes his phone from
+ // the reader (causing the "onHostEmulationDeactivated" event),
+ // the next tap default would immediately be cleared
+ // again. Instead, clear out defaults only if a next tap default
+ // had already been set at time of activation, which is captured
+ // by mClearNextTapDefault.
+ if (mNextTapDefault != null) {
+ mNextTapDefault = null;
+ changed = true;
+ }
+ mClearNextTapDefault = false;
+ }
+ }
+ if (changed) {
+ computePreferredForegroundService();
+ }
+ }
+
+ public void onUserSwitched(int userId) {
+ loadDefaultsFromSettings(userId);
+ }
+
+ public boolean packageHasPreferredService(String packageName) {
+ if (packageName == null) return false;
+
+ if (mPaymentDefaults.currentPreferred != null &&
+ packageName.equals(mPaymentDefaults.currentPreferred.getPackageName())) {
+ return true;
+ } else if (mForegroundCurrent != null &&
+ packageName.equals(mForegroundCurrent.getPackageName())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Preferred services (in order of importance): ");
+ pw.println(" *** Current preferred foreground service: " + mForegroundCurrent);
+ pw.println(" *** Current preferred payment service: " + mPaymentDefaults.currentPreferred);
+ pw.println(" Next tap default: " + mNextTapDefault);
+ pw.println(" Default for foreground app (UID: " + mForegroundUid +
+ "): " + mForegroundRequested);
+ pw.println(" Default in payment settings: " + mPaymentDefaults.settingsDefault);
+ pw.println(" Payment settings allows override: " + mPaymentDefaults.preferForeground);
+ pw.println("");
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java b/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java
new file mode 100644
index 0000000..0ac04b7
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C) 2014 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.cardemulation;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.util.Log;
+
+import com.google.android.collect.Maps;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.PriorityQueue;
+import java.util.TreeMap;
+
+public class RegisteredAidCache {
+ static final String TAG = "RegisteredAidCache";
+
+ static final boolean DBG = false;
+
+ // mAidServices maps AIDs to services that have registered them.
+ // It's a TreeMap in order to be able to quickly select subsets
+ // of AIDs that conflict with each other.
+ final TreeMap> mAidServices =
+ new TreeMap>();
+
+ // mAidCache is a lookup table for quickly mapping an exact or prefix AID to one or
+ // more handling services. It differs from mAidServices in the sense that it
+ // has already accounted for defaults, and hence its return value
+ // is authoritative for the current set of services and defaults.
+ // It is only valid for the current user.
+ final TreeMap mAidCache = new TreeMap();
+
+ // Represents a single AID registration of a service
+ final class ServiceAidInfo {
+ ApduServiceInfo service;
+ String aid;
+ String category;
+
+ @Override
+ public String toString() {
+ return "ServiceAidInfo{" +
+ "service=" + service.getComponent() +
+ ", aid='" + aid + '\'' +
+ ", category='" + category + '\'' +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ServiceAidInfo that = (ServiceAidInfo) o;
+
+ if (!aid.equals(that.aid)) return false;
+ if (!category.equals(that.category)) return false;
+ if (!service.equals(that.service)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = service.hashCode();
+ result = 31 * result + aid.hashCode();
+ result = 31 * result + category.hashCode();
+ return result;
+ }
+ }
+
+ // Represents a list of services, an optional default and a category that
+ // an AID was resolved to.
+ final class AidResolveInfo {
+ List services = new ArrayList();
+ ApduServiceInfo defaultService = null;
+ String category = null;
+ boolean mustRoute = true; // Whether this AID should be routed at all
+
+ @Override
+ public String toString() {
+ return "AidResolveInfo{" +
+ "services=" + services +
+ ", defaultService=" + defaultService +
+ ", category='" + category + '\'' +
+ ", mustRoute=" + mustRoute +
+ '}';
+ }
+ }
+
+ final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo();
+
+ final Context mContext;
+ final AidRoutingManager mRoutingManager;
+
+ final Object mLock = new Object();
+
+ ComponentName mPreferredPaymentService;
+ ComponentName mPreferredForegroundService;
+
+ boolean mNfcEnabled = false;
+ boolean mSupportsPrefixes = false;
+
+ public RegisteredAidCache(Context context) {
+ mContext = context;
+ mRoutingManager = new AidRoutingManager();
+ mPreferredPaymentService = null;
+ mPreferredForegroundService = null;
+ mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting();
+ if (mSupportsPrefixes) {
+ if (DBG) Log.d(TAG, "Controller supports AID prefix routing");
+ }
+ }
+
+ public AidResolveInfo resolveAid(String aid) {
+ synchronized (mLock) {
+ if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid);
+ if (aid.length() < 10) {
+ Log.e(TAG, "AID selected with fewer than 5 bytes.");
+ return EMPTY_RESOLVE_INFO;
+ }
+ AidResolveInfo resolveInfo = new AidResolveInfo();
+ if (mSupportsPrefixes) {
+ // Our AID cache may contain prefixes which also match this AID,
+ // so we must find all potential prefixes and merge the ResolveInfo
+ // of those prefixes plus any exact match in a single result.
+ String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes
+ String longestAidMatch = aid + "*"; // Longest potential matching AID
+
+ if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch +
+ " - " + longestAidMatch + "]");
+ NavigableMap matchingAids =
+ mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true);
+
+ resolveInfo.category = CardEmulation.CATEGORY_OTHER;
+ for (Map.Entry entry : matchingAids.entrySet()) {
+ boolean isPrefix = isPrefix(entry.getKey());
+ String entryAid = isPrefix ? entry.getKey().substring(0,
+ entry.getKey().length() - 1) : entry.getKey(); // Cut off '*' if prefix
+ if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))) {
+ if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches.");
+ AidResolveInfo entryResolveInfo = entry.getValue();
+ if (entryResolveInfo.defaultService != null) {
+ if (resolveInfo.defaultService != null) {
+ // This shouldn't happen; for every prefix we have only one
+ // default service.
+ Log.e(TAG, "Different defaults for conflicting AIDs!");
+ }
+ resolveInfo.defaultService = entryResolveInfo.defaultService;
+ resolveInfo.category = entryResolveInfo.category;
+ }
+ for (ApduServiceInfo serviceInfo : entryResolveInfo.services) {
+ if (!resolveInfo.services.contains(serviceInfo)) {
+ resolveInfo.services.add(serviceInfo);
+ }
+ }
+ }
+ }
+ } else {
+ resolveInfo = mAidCache.get(aid);
+ }
+ if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo);
+ return resolveInfo;
+ }
+ }
+
+ public boolean supportsAidPrefixRegistration() {
+ return mSupportsPrefixes;
+ }
+
+ public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
+ AidResolveInfo resolveInfo = resolveAid(aid);
+ if (resolveInfo == null || resolveInfo.services == null ||
+ resolveInfo.services.size() == 0) {
+ return false;
+ }
+
+ if (resolveInfo.defaultService != null) {
+ return service.equals(resolveInfo.defaultService.getComponent());
+ } else if (resolveInfo.services.size() == 1) {
+ return service.equals(resolveInfo.services.get(0).getComponent());
+ } else {
+ // More than one service, not the default
+ return false;
+ }
+ }
+
+ /**
+ * Resolves a conflict between multiple services handling the same
+ * AIDs. Note that the AID itself is not an input to the decision
+ * process - the algorithm just looks at the competing services
+ * and what preferences the user has indicated. In short, it works like
+ * this:
+ *
+ * 1) If there is a preferred foreground service, that service wins
+ * 2) Else, if there is a preferred payment service, that service wins
+ * 3) Else, if there is no winner, and all conflicting services will be
+ * in the list of resolved services.
+ */
+ AidResolveInfo resolveAidConflictLocked(Collection conflictingServices,
+ boolean makeSingleServiceDefault) {
+ if (conflictingServices == null || conflictingServices.size() == 0) {
+ Log.e(TAG, "resolveAidConflict: No services passed in.");
+ return null;
+ }
+ AidResolveInfo resolveInfo = new AidResolveInfo();
+ resolveInfo.category = CardEmulation.CATEGORY_OTHER;
+
+ ApduServiceInfo matchedForeground = null;
+ ApduServiceInfo matchedPayment = null;
+ for (ServiceAidInfo serviceAidInfo : conflictingServices) {
+ boolean serviceClaimsPaymentAid =
+ CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
+ if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
+ resolveInfo.services.add(serviceAidInfo.service);
+ if (serviceClaimsPaymentAid) {
+ resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ }
+ matchedForeground = serviceAidInfo.service;
+ } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
+ serviceClaimsPaymentAid) {
+ resolveInfo.services.add(serviceAidInfo.service);
+ resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ matchedPayment = serviceAidInfo.service;
+ } else {
+ if (serviceClaimsPaymentAid) {
+ // If this service claims it's a payment AID, don't route it,
+ // because it's not the default. Otherwise, add it to the list
+ // but not as default.
+ if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
+ serviceAidInfo.service.getComponent() +
+ " because it's not the payment default.)");
+ } else {
+ resolveInfo.services.add(serviceAidInfo.service);
+ }
+ }
+ }
+ if (matchedForeground != null) {
+ // 1st priority: if the foreground app prefers a service,
+ // and that service asks for the AID, that service gets it
+ if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " +
+ matchedForeground);
+ resolveInfo.defaultService = matchedForeground;
+ } else if (matchedPayment != null) {
+ // 2nd priority: if there is a preferred payment service,
+ // and that service claims this as a payment AID, that service gets it
+ if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " +
+ "default " + matchedPayment);
+ resolveInfo.defaultService = matchedPayment;
+ } else {
+ if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
+ if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " +
+ resolveInfo.services.get(0).getComponent() + " default.");
+ resolveInfo.defaultService = resolveInfo.services.get(0);
+ } else {
+ // Nothing to do, all services already in list
+ if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
+ }
+ }
+ return resolveInfo;
+ }
+
+ class DefaultServiceInfo {
+ ServiceAidInfo paymentDefault;
+ ServiceAidInfo foregroundDefault;
+ }
+
+ DefaultServiceInfo findDefaultServices(ArrayList serviceAidInfos) {
+ DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo();
+
+ for (ServiceAidInfo serviceAidInfo : serviceAidInfos) {
+ boolean serviceClaimsPaymentAid =
+ CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
+ if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
+ defaultServiceInfo.foregroundDefault = serviceAidInfo;
+ } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
+ serviceClaimsPaymentAid) {
+ defaultServiceInfo.paymentDefault = serviceAidInfo;
+ }
+ }
+ return defaultServiceInfo;
+ }
+
+ AidResolveInfo resolvePrefixAidConflictLocked(ArrayList prefixServices,
+ ArrayList conflictingServices) {
+ // Find defaults among the prefix services themselves
+ DefaultServiceInfo prefixDefaultInfo = findDefaultServices(prefixServices);
+
+ // Find any defaults among the children
+ DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices);
+
+ // Three conditions under which the prefix root AID gets to be the default
+ // 1. A service registering the prefix root AID is the current foreground preferred
+ // 2. A service registering the prefix root AID is the current tap & pay default AND
+ // no child is the current foreground preferred
+ // 3. There is only one service for the prefix root AID, and there are no children
+ if (prefixDefaultInfo.foregroundDefault != null) {
+ if (DBG) Log.d(TAG, "Prefix AID service " +
+ prefixDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +
+ " preference, ignoring conflicting AIDs.");
+ // Foreground default trumps any conflicting services, treat as normal AID conflict
+ // and ignore children
+ return resolveAidConflictLocked(prefixServices, true);
+ } else if (prefixDefaultInfo.paymentDefault != null) {
+ // Check if any of the conflicting services is foreground default
+ if (conflictingDefaultInfo.foregroundDefault != null) {
+ // Conflicting AID registration is in foreground, trumps prefix tap&pay default
+ if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " +
+ "preferred, ignoring prefix.");
+ return EMPTY_RESOLVE_INFO;
+ } else {
+ // Prefix service is tap&pay default, treat as normal AID conflict for just prefix
+ if (DBG) Log.d(TAG, "Prefix AID service " +
+ prefixDefaultInfo.paymentDefault.service.getComponent() + " is payment" +
+ " default, ignoring conflicting AIDs.");
+ return resolveAidConflictLocked(prefixServices, true);
+ }
+ } else {
+ if (conflictingDefaultInfo.foregroundDefault != null ||
+ conflictingDefaultInfo.paymentDefault != null) {
+ if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " +
+ "default or foreground preferred, ignoring prefix.");
+ return EMPTY_RESOLVE_INFO;
+ } else {
+ // No children that are preferred; add all services of the root
+ // make single service default if no children are present
+ if (DBG) Log.d(TAG, "No service has preference, adding all.");
+ return resolveAidConflictLocked(prefixServices, conflictingServices.isEmpty());
+ }
+ }
+ }
+
+ void generateServiceMapLocked(List services) {
+ // Easiest is to just build the entire tree again
+ mAidServices.clear();
+ for (ApduServiceInfo service : services) {
+ if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
+ List prefixAids = service.getPrefixAids();
+ for (String aid : service.getAids()) {
+ if (!CardEmulation.isValidAid(aid)) {
+ Log.e(TAG, "Aid " + aid + " is not valid.");
+ continue;
+ }
+ if (aid.endsWith("*") && !supportsAidPrefixRegistration()) {
+ Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it.");
+ continue;
+ } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && !isPrefix(aid)) {
+ // Check if we already have an overlapping prefix registered for this AID
+ boolean foundPrefix = false;
+ for (String prefixAid : prefixAids) {
+ String prefix = prefixAid.substring(0, prefixAid.length() - 1);
+ if (aid.startsWith(prefix)) {
+ Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid +
+ " is already registered");
+ foundPrefix = true;
+ break;
+ }
+ }
+ if (foundPrefix) {
+ continue;
+ }
+ }
+ ServiceAidInfo serviceAidInfo = new ServiceAidInfo();
+ serviceAidInfo.aid = aid.toUpperCase();
+ serviceAidInfo.service = service;
+ serviceAidInfo.category = service.getCategoryForAid(aid);
+
+ if (mAidServices.containsKey(serviceAidInfo.aid)) {
+ final ArrayList serviceAidInfos =
+ mAidServices.get(serviceAidInfo.aid);
+ serviceAidInfos.add(serviceAidInfo);
+ } else {
+ final ArrayList serviceAidInfos =
+ new ArrayList();
+ serviceAidInfos.add(serviceAidInfo);
+ mAidServices.put(serviceAidInfo.aid, serviceAidInfos);
+ }
+ }
+ }
+ }
+
+ static boolean isPrefix(String aid) {
+ return aid.endsWith("*");
+ }
+
+ final class PrefixConflicts {
+ NavigableMap> conflictMap;
+ final ArrayList services = new ArrayList();
+ final HashSet aids = new HashSet();
+ }
+
+ PrefixConflicts findConflictsForPrefixLocked(String prefixAid) {
+ PrefixConflicts prefixConflicts = new PrefixConflicts();
+ String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*"
+ String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F');
+ if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " +
+ lastAidWithPrefix + "]");
+ prefixConflicts.conflictMap =
+ mAidServices.subMap(plainAid, true, lastAidWithPrefix, true);
+ for (Map.Entry> entry :
+ prefixConflicts.conflictMap.entrySet()) {
+ if (!entry.getKey().equalsIgnoreCase(prefixAid)) {
+ if (DBG)
+ Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " +
+ " adding handling services for conflict resolution.");
+ prefixConflicts.services.addAll(entry.getValue());
+ prefixConflicts.aids.add(entry.getKey());
+ }
+ }
+ return prefixConflicts;
+ }
+
+ void generateAidCacheLocked() {
+ mAidCache.clear();
+ // Get all exact and prefix AIDs in an ordered list
+ PriorityQueue aidsToResolve = new PriorityQueue(mAidServices.keySet());
+
+ while (!aidsToResolve.isEmpty()) {
+ final ArrayList resolvedAids = new ArrayList();
+
+ String aidToResolve = aidsToResolve.peek();
+ // Because of the lexicographical ordering, all following AIDs either start with the
+ // same bytes and are longer, or start with different bytes.
+
+ // A special case is if another service registered the same AID as a prefix, in
+ // which case we want to start with that AID, since it conflicts with this one
+ if (aidsToResolve.contains(aidToResolve + "*")) {
+ aidToResolve = aidToResolve + "*";
+ }
+ if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve);
+
+ if (isPrefix(aidToResolve)) {
+ // This AID itself is a prefix; let's consider this prefix as the "root",
+ // and all conflicting AIDs as its children.
+ // For example, if "A000000003*" is the prefix root,
+ // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs
+ final ArrayList prefixServices = new ArrayList(
+ mAidServices.get(aidToResolve));
+
+ // Find all conflicting children services
+ PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve);
+
+ // Resolve conflicts
+ AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices,
+ prefixConflicts.services);
+ mAidCache.put(aidToResolve, resolveInfo);
+ resolvedAids.add(aidToResolve);
+ if (resolveInfo.defaultService != null) {
+ // This prefix is the default; therefore, AIDs of all conflicting children
+ // will no longer be evaluated.
+ resolvedAids.addAll(prefixConflicts.aids);
+ } else if (resolveInfo.services.size() > 0) {
+ // This means we don't have a default for this prefix and all its
+ // conflicting children. So, for all conflicting AIDs, just add
+ // all handling services without setting a default
+ boolean foundChildService = false;
+ for (Map.Entry> entry :
+ prefixConflicts.conflictMap.entrySet()) {
+ if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
+ if (DBG)
+ Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " +
+ " adding all handling services.");
+ AidResolveInfo childResolveInfo = resolveAidConflictLocked(
+ entry.getValue(), false);
+ // Special case: in this case all children AIDs must be routed to the
+ // host, so we can ask the user which service is preferred.
+ // Since these are all "children" of the prefix, they don't need
+ // to be routed, since the prefix will already get routed to the host
+ childResolveInfo.mustRoute = false;
+ mAidCache.put(entry.getKey(),childResolveInfo);
+ resolvedAids.add(entry.getKey());
+ foundChildService |= !childResolveInfo.services.isEmpty();
+ }
+ }
+ // Special case: if in the end we didn't add any children services,
+ // and the prefix has only one service, make that default
+ if (!foundChildService && resolveInfo.services.size() == 1) {
+ resolveInfo.defaultService = resolveInfo.services.get(0);
+ }
+ } else {
+ // This prefix is not handled at all; we will evaluate
+ // the children separately in next passes.
+ }
+ } else {
+ // Exact AID and no other conflicting AID registrations present
+ // This is true because aidsToResolve is lexicographically ordered, and
+ // so by necessity all other AIDs are different than this AID or longer.
+ if (DBG) Log.d(TAG, "Exact AID, resolving.");
+ final ArrayList conflictingServiceInfos =
+ new ArrayList(mAidServices.get(aidToResolve));
+ mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true));
+ resolvedAids.add(aidToResolve);
+ }
+
+ // Remove the AIDs we resolved from the list of AIDs to resolve
+ if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
+ aidsToResolve.removeAll(resolvedAids);
+ resolvedAids.clear();
+ }
+
+ updateRoutingLocked();
+ }
+
+ void updateRoutingLocked() {
+ if (!mNfcEnabled) {
+ if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
+ return;
+ }
+ final HashMap routingEntries = Maps.newHashMap();
+ // For each AID, find interested services
+ for (Map.Entry aidEntry:
+ mAidCache.entrySet()) {
+ String aid = aidEntry.getKey();
+ AidResolveInfo resolveInfo = aidEntry.getValue();
+ if (!resolveInfo.mustRoute) {
+ if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request.");
+ continue;
+ }
+ if (resolveInfo.services.size() == 0) {
+ // No interested services
+ } else if (resolveInfo.defaultService != null) {
+ // There is a default service set, route to where that service resides -
+ // either on the host (HCE) or on an SE.
+ routingEntries.put(aid, resolveInfo.defaultService.isOnHost());
+ } else if (resolveInfo.services.size() == 1) {
+ // Only one service, but not the default, must route to host
+ // to ask the user to choose one.
+ routingEntries.put(aid, true);
+ } else if (resolveInfo.services.size() > 1) {
+ // Multiple services, need to route to host to ask
+ routingEntries.put(aid, true);
+ }
+ }
+ mRoutingManager.configureRouting(routingEntries);
+ }
+
+ public void onServicesUpdated(int userId, List services) {
+ if (DBG) Log.d(TAG, "onServicesUpdated");
+ synchronized (mLock) {
+ if (ActivityManager.getCurrentUser() == userId) {
+ // Rebuild our internal data-structures
+ generateServiceMapLocked(services);
+ generateAidCacheLocked();
+ } else {
+ if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
+ }
+ }
+ }
+
+ public void onPreferredPaymentServiceChanged(ComponentName service) {
+ if (DBG) Log.d(TAG, "Preferred payment service changed.");
+ synchronized (mLock) {
+ mPreferredPaymentService = service;
+ generateAidCacheLocked();
+ }
+ }
+
+ public void onPreferredForegroundServiceChanged(ComponentName service) {
+ if (DBG) Log.d(TAG, "Preferred foreground service changed.");
+ synchronized (mLock) {
+ mPreferredForegroundService = service;
+ generateAidCacheLocked();
+ }
+ }
+
+ public void onNfcDisabled() {
+ synchronized (mLock) {
+ mNfcEnabled = false;
+ }
+ mRoutingManager.onNfccRoutingTableCleared();
+ }
+
+ public void onNfcEnabled() {
+ synchronized (mLock) {
+ mNfcEnabled = true;
+ updateRoutingLocked();
+ }
+ }
+
+ String dumpEntry(Map.Entry entry) {
+ StringBuilder sb = new StringBuilder();
+ String category = entry.getValue().category;
+ ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService;
+ sb.append(" \"" + entry.getKey() + "\" (category: " + category + ")\n");
+ ComponentName defaultComponent = defaultServiceInfo != null ?
+ defaultServiceInfo.getComponent() : null;
+
+ for (ApduServiceInfo serviceInfo : entry.getValue().services) {
+ sb.append(" ");
+ if (serviceInfo.getComponent().equals(defaultComponent)) {
+ sb.append("*DEFAULT* ");
+ }
+ sb.append(serviceInfo.getComponent() +
+ " (Description: " + serviceInfo.getDescription() + ")\n");
+ }
+ return sb.toString();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" AID cache entries: ");
+ for (Map.Entry entry : mAidCache.entrySet()) {
+ pw.println(dumpEntry(entry));
+ }
+ pw.println(" Service preferred by foreground app: " + mPreferredForegroundService);
+ pw.println(" Preferred payment service: " + mPreferredPaymentService);
+ pw.println("");
+ mRoutingManager.dump(fd, pw, args);
+ pw.println("");
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java b/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java
new file mode 100644
index 0000000..be84e53
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.nfc.cardemulation.AidGroup;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.OffHostApduService;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.google.android.collect.Maps;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This class is inspired by android.content.pm.RegisteredServicesCache
+ * That class was not re-used because it doesn't support dynamically
+ * registering additional properties, but generates everything from
+ * the manifest. Since we have some properties that are not in the manifest,
+ * it's less suited.
+ */
+public class RegisteredServicesCache {
+ static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
+ static final String TAG = "RegisteredServicesCache";
+ static final boolean DEBUG = false;
+
+ final Context mContext;
+ final AtomicReference mReceiver;
+
+ final Object mLock = new Object();
+ // All variables below synchronized on mLock
+
+ // mUserServices holds the card emulation services that are running for each user
+ final SparseArray mUserServices = new SparseArray();
+ final Callback mCallback;
+ final AtomicFile mDynamicAidsFile;
+
+ public interface Callback {
+ void onServicesUpdated(int userId, final List services);
+ };
+
+ static class DynamicAids {
+ public final int uid;
+ public final HashMap aidGroups = Maps.newHashMap();
+
+ DynamicAids(int uid) {
+ this.uid = uid;
+ }
+ };
+
+ private static class UserServices {
+ /**
+ * All services that have registered
+ */
+ final HashMap services =
+ Maps.newHashMap(); // Re-built at run-time
+ final HashMap dynamicAids =
+ Maps.newHashMap(); // In memory cache of dynamic AID store
+ };
+
+ private UserServices findOrCreateUserLocked(int userId) {
+ UserServices services = mUserServices.get(userId);
+ if (services == null) {
+ services = new UserServices();
+ mUserServices.put(userId, services);
+ }
+ return services;
+ }
+
+ public RegisteredServicesCache(Context context, Callback callback) {
+ mContext = context;
+ mCallback = callback;
+
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ String action = intent.getAction();
+ if (DEBUG) Log.d(TAG, "Intent action: " + action);
+ if (uid != -1) {
+ boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
+ (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
+ Intent.ACTION_PACKAGE_REMOVED.equals(action));
+ if (!replaced) {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (currentUser == UserHandle.getUserId(uid)) {
+ invalidateCache(UserHandle.getUserId(uid));
+ } else {
+ // Cache will automatically be updated on user switch
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced.");
+ }
+ }
+ }
+ };
+ mReceiver = new AtomicReference(receiver);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null);
+
+ // Register for events related to sdcard operations
+ IntentFilter sdFilter = new IntentFilter();
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
+ sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+ mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null);
+
+ File dataDir = mContext.getFilesDir();
+ mDynamicAidsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml"));
+ }
+
+ void initialize() {
+ synchronized (mLock) {
+ readDynamicAidsLocked();
+ }
+ invalidateCache(ActivityManager.getCurrentUser());
+ }
+
+ void dump(ArrayList services) {
+ for (ApduServiceInfo service : services) {
+ if (DEBUG) Log.d(TAG, service.toString());
+ }
+ }
+
+ boolean containsServiceLocked(ArrayList services, ComponentName serviceName) {
+ for (ApduServiceInfo service : services) {
+ if (service.getComponent().equals(serviceName)) return true;
+ }
+ return false;
+ }
+
+ public boolean hasService(int userId, ComponentName service) {
+ return getService(userId, service) != null;
+ }
+
+ public ApduServiceInfo getService(int userId, ComponentName service) {
+ synchronized (mLock) {
+ UserServices userServices = findOrCreateUserLocked(userId);
+ return userServices.services.get(service);
+ }
+ }
+
+ public List getServices(int userId) {
+ final ArrayList services = new ArrayList();
+ synchronized (mLock) {
+ UserServices userServices = findOrCreateUserLocked(userId);
+ services.addAll(userServices.services.values());
+ }
+ return services;
+ }
+
+ public List getServicesForCategory(int userId, String category) {
+ final ArrayList services = new ArrayList();
+ synchronized (mLock) {
+ UserServices userServices = findOrCreateUserLocked(userId);
+ for (ApduServiceInfo service : userServices.services.values()) {
+ if (service.hasCategory(category)) services.add(service);
+ }
+ }
+ return services;
+ }
+
+ ArrayList getInstalledServices(int userId) {
+ PackageManager pm;
+ try {
+ pm = mContext.createPackageContextAsUser("android", 0,
+ new UserHandle(userId)).getPackageManager();
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not create user package context");
+ return null;
+ }
+
+ ArrayList validServices = new ArrayList();
+
+ List resolvedServices = pm.queryIntentServicesAsUser(
+ new Intent(HostApduService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA, userId);
+
+ List resolvedOffHostServices = pm.queryIntentServicesAsUser(
+ new Intent(OffHostApduService.SERVICE_INTERFACE),
+ PackageManager.GET_META_DATA, userId);
+ resolvedServices.addAll(resolvedOffHostServices);
+
+ for (ResolveInfo resolvedService : resolvedServices) {
+ try {
+ boolean onHost = !resolvedOffHostServices.contains(resolvedService);
+ ServiceInfo si = resolvedService.serviceInfo;
+ ComponentName componentName = new ComponentName(si.packageName, si.name);
+ // Check if the package holds the NFC permission
+ if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) !=
+ PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Skipping application component " + componentName +
+ ": it must request the permission " +
+ android.Manifest.permission.NFC);
+ continue;
+ }
+ if (!android.Manifest.permission.BIND_NFC_SERVICE.equals(
+ si.permission)) {
+ Log.e(TAG, "Skipping APDU service " + componentName +
+ ": it does not require the permission " +
+ android.Manifest.permission.BIND_NFC_SERVICE);
+ continue;
+ }
+ ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
+ if (service != null) {
+ validServices.add(service);
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e);
+ }
+ }
+
+ return validServices;
+ }
+
+ public void invalidateCache(int userId) {
+ final ArrayList validServices = getInstalledServices(userId);
+ if (validServices == null) {
+ return;
+ }
+ synchronized (mLock) {
+ UserServices userServices = findOrCreateUserLocked(userId);
+
+ // Find removed services
+ Iterator> it =
+ userServices.services.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry =
+ (Map.Entry) it.next();
+ if (!containsServiceLocked(validServices, entry.getKey())) {
+ Log.d(TAG, "Service removed: " + entry.getKey());
+ it.remove();
+ }
+ }
+ for (ApduServiceInfo service : validServices) {
+ if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
+ " AIDs: " + service.getAids());
+ userServices.services.put(service.getComponent(), service);
+ }
+
+ // Apply dynamic AID mappings
+ ArrayList toBeRemoved = new ArrayList();
+ for (Map.Entry entry :
+ userServices.dynamicAids.entrySet()) {
+ // Verify component / uid match
+ ComponentName component = entry.getKey();
+ DynamicAids dynamicAids = entry.getValue();
+ ApduServiceInfo serviceInfo = userServices.services.get(component);
+ if (serviceInfo == null || (serviceInfo.getUid() != dynamicAids.uid)) {
+ toBeRemoved.add(component);
+ continue;
+ } else {
+ for (AidGroup group : dynamicAids.aidGroups.values()) {
+ serviceInfo.setOrReplaceDynamicAidGroup(group);
+ }
+ }
+ }
+
+ if (toBeRemoved.size() > 0) {
+ for (ComponentName component : toBeRemoved) {
+ Log.d(TAG, "Removing dynamic AIDs registered by " + component);
+ userServices.dynamicAids.remove(component);
+ }
+ // Persist to filesystem
+ writeDynamicAidsLocked();
+ }
+ }
+
+ mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices));
+ dump(validServices);
+ }
+
+ private void readDynamicAidsLocked() {
+ FileInputStream fis = null;
+ try {
+ if (!mDynamicAidsFile.getBaseFile().exists()) {
+ Log.d(TAG, "Dynamic AIDs file does not exist.");
+ return;
+ }
+ fis = mDynamicAidsFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+ String tagName = parser.getName();
+ if ("services".equals(tagName)) {
+ boolean inService = false;
+ ComponentName currentComponent = null;
+ int currentUid = -1;
+ ArrayList currentGroups = new ArrayList();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ tagName = parser.getName();
+ if (eventType == XmlPullParser.START_TAG) {
+ if ("service".equals(tagName) && parser.getDepth() == 2) {
+ String compString = parser.getAttributeValue(null, "component");
+ String uidString = parser.getAttributeValue(null, "uid");
+ if (compString == null || uidString == null) {
+ Log.e(TAG, "Invalid service attributes");
+ } else {
+ try {
+ currentUid = Integer.parseInt(uidString);
+ currentComponent = ComponentName.unflattenFromString(compString);
+ inService = true;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Could not parse service uid");
+ }
+ }
+ }
+ if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) {
+ AidGroup group = AidGroup.createFromXml(parser);
+ if (group != null) {
+ currentGroups.add(group);
+ } else {
+ Log.e(TAG, "Could not parse AID group.");
+ }
+ }
+ } else if (eventType == XmlPullParser.END_TAG) {
+ if ("service".equals(tagName)) {
+ // See if we have a valid service
+ if (currentComponent != null && currentUid >= 0 &&
+ currentGroups.size() > 0) {
+ final int userId = UserHandle.getUserId(currentUid);
+ DynamicAids dynAids = new DynamicAids(currentUid);
+ for (AidGroup group : currentGroups) {
+ dynAids.aidGroups.put(group.getCategory(), group);
+ }
+ UserServices services = findOrCreateUserLocked(userId);
+ services.dynamicAids.put(currentComponent, dynAids);
+ }
+ currentUid = -1;
+ currentComponent = null;
+ currentGroups.clear();
+ inService = false;
+ }
+ }
+ eventType = parser.next();
+ };
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Could not parse dynamic AIDs file, trashing.");
+ mDynamicAidsFile.delete();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private boolean writeDynamicAidsLocked() {
+ FileOutputStream fos = null;
+ try {
+ fos = mDynamicAidsFile.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.setFeature(XML_INDENT_OUTPUT_FEATURE, true);
+ out.startTag(null, "services");
+ for (int i = 0; i < mUserServices.size(); i++) {
+ final UserServices user = mUserServices.valueAt(i);
+ for (Map.Entry service : user.dynamicAids.entrySet()) {
+ out.startTag(null, "service");
+ out.attribute(null, "component", service.getKey().flattenToString());
+ out.attribute(null, "uid", Integer.toString(service.getValue().uid));
+ for (AidGroup group : service.getValue().aidGroups.values()) {
+ group.writeAsXml(out);
+ }
+ out.endTag(null, "service");
+ }
+ }
+ out.endTag(null, "services");
+ out.endDocument();
+ mDynamicAidsFile.finishWrite(fos);
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Error writing dynamic AIDs", e);
+ if (fos != null) {
+ mDynamicAidsFile.failWrite(fos);
+ }
+ return false;
+ }
+ }
+
+ public boolean registerAidGroupForService(int userId, int uid,
+ ComponentName componentName, AidGroup aidGroup) {
+ ArrayList newServices = null;
+ boolean success;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ // Check if we can find this service
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo == null) {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ return false;
+ }
+ if (serviceInfo.getUid() != uid) {
+ // This is probably a good indication something is wrong here.
+ // Either newer service installed with different uid (but then
+ // we should have known about it), or somebody calling us from
+ // a different uid.
+ Log.e(TAG, "UID mismatch.");
+ return false;
+ }
+ // Do another AID validation, since a caller could have thrown in a modified
+ // AidGroup object with invalid AIDs over Binder.
+ List aids = aidGroup.getAids();
+ for (String aid : aids) {
+ if (!CardEmulation.isValidAid(aid)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID");
+ return false;
+ }
+ }
+ serviceInfo.setOrReplaceDynamicAidGroup(aidGroup);
+ DynamicAids dynAids = services.dynamicAids.get(componentName);
+ if (dynAids == null) {
+ dynAids = new DynamicAids(uid);
+ services.dynamicAids.put(componentName, dynAids);
+ }
+ dynAids.aidGroups.put(aidGroup.getCategory(), aidGroup);
+ success = writeDynamicAidsLocked();
+ if (success) {
+ newServices = new ArrayList(services.services.values());
+ } else {
+ Log.e(TAG, "Failed to persist AID group.");
+ // Undo registration
+ dynAids.aidGroups.remove(aidGroup.getCategory());
+ }
+ }
+ if (success) {
+ // Make callback without the lock held
+ mCallback.onServicesUpdated(userId, newServices);
+ }
+ return success;
+ }
+
+ public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName,
+ String category) {
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo != null) {
+ if (serviceInfo.getUid() != uid) {
+ Log.e(TAG, "UID mismatch");
+ return null;
+ }
+ return serviceInfo.getDynamicAidGroupForCategory(category);
+ } else {
+ Log.e(TAG, "Could not find service " + componentName);
+ return null;
+ }
+ }
+
+ public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName,
+ String category) {
+ boolean success = false;
+ ArrayList newServices = null;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo != null) {
+ if (serviceInfo.getUid() != uid) {
+ // Calling from different uid
+ Log.e(TAG, "UID mismatch");
+ return false;
+ }
+ if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {
+ Log.e(TAG," Could not find dynamic AIDs for category " + category);
+ return false;
+ }
+ // Remove from local cache
+ DynamicAids dynAids = services.dynamicAids.get(componentName);
+ if (dynAids != null) {
+ AidGroup deletedGroup = dynAids.aidGroups.remove(category);
+ success = writeDynamicAidsLocked();
+ if (success) {
+ newServices = new ArrayList(services.services.values());
+ } else {
+ Log.e(TAG, "Could not persist deleted AID group.");
+ dynAids.aidGroups.put(category, deletedGroup);
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Could not find aid group in local cache.");
+ }
+ } else {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ }
+ }
+ if (success) {
+ mCallback.onServicesUpdated(userId, newServices);
+ }
+ return success;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Registered HCE services for current user: ");
+ UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser());
+ for (ApduServiceInfo service : userServices.services.values()) {
+ service.dump(fd, pw, args);
+ pw.println("");
+ }
+ pw.println("");
+ }
+
+}
diff --git a/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java b/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java
new file mode 100644
index 0000000..b1880b4
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2013 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.cardemulation;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+
+public class TapAgainDialog extends AlertActivity implements DialogInterface.OnClickListener {
+ public static final String ACTION_CLOSE = "com.android.nfc.cardmeulation.close_tap_dialog";
+ public static final String EXTRA_APDU_SERVICE = "apdu_service";
+
+ public static final String EXTRA_CATEGORY = "category";
+
+ // Variables below only accessed on the main thread
+ private CardEmulation mCardEmuManager;
+ private boolean mClosedOnRequest = false;
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mClosedOnRequest = true;
+ finish();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
+
+ final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
+ mCardEmuManager = CardEmulation.getInstance(adapter);
+ Intent intent = getIntent();
+ String category = intent.getStringExtra(EXTRA_CATEGORY);
+ ApduServiceInfo serviceInfo = intent.getParcelableExtra(EXTRA_APDU_SERVICE);
+ IntentFilter filter = new IntentFilter(ACTION_CLOSE);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(mReceiver, filter);
+ AlertController.AlertParams ap = mAlertParams;
+
+ ap.mTitle = "";
+ ap.mView = getLayoutInflater().inflate(com.android.nfc.R.layout.tapagain, null);
+
+ PackageManager pm = getPackageManager();
+ TextView tv = (TextView) ap.mView.findViewById(com.android.nfc.R.id.textview);
+ String description = serviceInfo.getDescription();
+ if (description == null) {
+ CharSequence label = serviceInfo.loadLabel(pm);
+ if (label == null) {
+ finish();
+ } else {
+ description = label.toString();
+ }
+ }
+ if (CardEmulation.CATEGORY_PAYMENT.equals(category)) {
+ String formatString = getString(com.android.nfc.R.string.tap_again_to_pay);
+ tv.setText(String.format(formatString, description));
+ } else {
+ String formatString = getString(com.android.nfc.R.string.tap_again_to_complete);
+ tv.setText(String.format(formatString, description));
+ }
+ ap.mNegativeButtonText = getString(R.string.cancel);
+ setupAlert();
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (!mClosedOnRequest) {
+ mCardEmuManager.setDefaultForNextTap(null);
+ }
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/NfcSony/src/com/android/nfc/echoserver/EchoServer.java b/NfcSony/src/com/android/nfc/echoserver/EchoServer.java
new file mode 100644
index 0000000..71d0bf8
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/echoserver/EchoServer.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2012 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.echoserver;
+
+import com.android.nfc.DeviceHost.LlcpConnectionlessSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpPacket;
+import com.android.nfc.NfcService;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * EchoServer is an implementation of the echo server that is used in the
+ * nfcpy LLCP test suite. Enabling the EchoServer allows to test Android
+ * NFC devices against nfcpy.
+ * It has two main features (which run simultaneously):
+ * 1) A connection-based server, which has a receive buffer of two
+ * packets. Once a packet is received, a 2-second sleep is initiated.
+ * After these 2 seconds, all packets that are in the receive buffer
+ * are echoed back on the same connection. The connection-based server
+ * does not drop packets, but simply blocks if the queue is full.
+ * 2) A connection-less mode, which has a receive buffer of two packets.
+ * On LLCP link activation, we try to receive data on a pre-determined
+ * connection-less SAP. Like the connection-based server, all data in
+ * the buffer is echoed back to the SAP from which the data originated
+ * after a sleep of two seconds.
+ * The main difference is that the connection-less SAP is supposed
+ * to drop packets when the buffer is full.
+ *
+ * To use with nfcpy:
+ * - Adapt default_miu (see ECHO_MIU below)
+ * - llcp-test-client.py --mode=target --co-echo=17 --cl-echo=18 -t 1
+ *
+ * Modify -t to execute the different tests.
+ *
+ */
+public class EchoServer {
+ static boolean DBG = true;
+
+ static final int DEFAULT_CO_SAP = 0x11;
+ static final int DEFAULT_CL_SAP = 0x12;
+
+ // Link MIU
+ static final int MIU = 128;
+
+ static final String TAG = "EchoServer";
+ static final String CONNECTION_SERVICE_NAME = "urn:nfc:sn:co-echo";
+ static final String CONNECTIONLESS_SERVICE_NAME = "urn:nfc:sn:cl-echo";
+
+ ServerThread mServerThread;
+ ConnectionlessServerThread mConnectionlessServerThread;
+ NfcService mService;
+
+ public interface WriteCallback {
+ public void write(byte[] data);
+ }
+
+ public EchoServer() {
+ mService = NfcService.getInstance();
+ }
+
+ static class EchoMachine implements Handler.Callback {
+ static final int QUEUE_SIZE = 2;
+ static final int ECHO_DELAY_IN_MS = 2000;
+
+ /**
+ * ECHO_MIU must be set equal to default_miu in nfcpy.
+ * The nfcpy echo server is expected to maintain the
+ * packet boundaries and sizes of the requests - that is,
+ * if the nfcpy client sends a service data unit of 48 bytes
+ * in a packet, the echo packet should have a payload of
+ * 48 bytes as well. The "problem" is that the current
+ * Android LLCP implementation simply pushes all received data
+ * in a single large buffer, causing us to loose the packet
+ * boundaries, not knowing how much data to put in a single
+ * response packet. The ECHO_MIU parameter determines exactly that.
+ * We use ECHO_MIU=48 because of a bug in PN544, which does not respect
+ * the target length reduction parameter of the p2p protocol.
+ */
+ static final int ECHO_MIU = 128;
+
+ final BlockingQueue dataQueue;
+ final Handler handler;
+ final boolean dumpWhenFull;
+ final WriteCallback callback;
+
+ // shutdown can be modified from multiple threads, protected by this
+ boolean shutdown = false;
+
+ EchoMachine(WriteCallback callback, boolean dumpWhenFull) {
+ this.callback = callback;
+ this.dumpWhenFull = dumpWhenFull;
+ dataQueue = new LinkedBlockingQueue(QUEUE_SIZE);
+ handler = new Handler(this);
+ }
+
+ public void pushUnit(byte[] unit, int size) {
+ if (dumpWhenFull && dataQueue.remainingCapacity() == 0) {
+ if (DBG) Log.d(TAG, "Dumping data unit");
+ } else {
+ try {
+ // Split up the packet in ECHO_MIU size packets
+ int sizeLeft = size;
+ int offset = 0;
+ if (dataQueue.isEmpty()) {
+ // First message: start echo'ing in 2 seconds
+ handler.sendMessageDelayed(handler.obtainMessage(), ECHO_DELAY_IN_MS);
+ }
+
+ if (sizeLeft == 0) {
+ // Special case: also send a zero-sized data unit
+ dataQueue.put(new byte[] {});
+ }
+ while (sizeLeft > 0) {
+ int minSize = Math.min(size, ECHO_MIU);
+ byte[] data = new byte[minSize];
+ System.arraycopy(unit, offset, data, 0, minSize);
+ dataQueue.put(data);
+ sizeLeft -= minSize;
+ offset += minSize;
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ /** Shuts down the EchoMachine. May block until callbacks
+ * in progress are completed.
+ */
+ public synchronized void shutdown() {
+ dataQueue.clear();
+ shutdown = true;
+ }
+
+ @Override
+ public synchronized boolean handleMessage(Message msg) {
+ if (shutdown) return true;
+ while (!dataQueue.isEmpty()) {
+ callback.write(dataQueue.remove());
+ }
+ return true;
+ }
+ }
+
+ public class ServerThread extends Thread implements WriteCallback {
+ final EchoMachine echoMachine;
+
+ boolean running = true;
+ LlcpServerSocket serverSocket;
+ LlcpSocket clientSocket;
+
+ public ServerThread() {
+ super();
+ echoMachine = new EchoMachine(this, false);
+ }
+
+ private void handleClient(LlcpSocket socket) {
+ boolean connectionBroken = false;
+ byte[] dataUnit = new byte[1024];
+
+ // Get raw data from remote server
+ while (!connectionBroken) {
+ try {
+ int size = socket.receive(dataUnit);
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
+ if (size < 0) {
+ connectionBroken = true;
+ break;
+ } else {
+ echoMachine.pushUnit(dataUnit, size);
+ }
+ } catch (IOException e) {
+ // Connection broken
+ connectionBroken = true;
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "about create LLCP service socket");
+ try {
+ serverSocket = mService.createLlcpServerSocket(DEFAULT_CO_SAP,
+ CONNECTION_SERVICE_NAME, MIU, 1, 1024);
+ } catch (LlcpException e) {
+ return;
+ }
+ if (serverSocket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
+ return;
+ }
+ if (DBG) Log.d(TAG, "created LLCP service socket");
+
+ while (running) {
+
+ try {
+ if (DBG) Log.d(TAG, "about to accept");
+ clientSocket = serverSocket.accept();
+ if (DBG) Log.d(TAG, "accept returned " + clientSocket);
+ handleClient(clientSocket);
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ running = false;
+ } catch (IOException e) {
+ Log.e(TAG, "IO error", e);
+ running = false;
+ }
+ }
+
+ echoMachine.shutdown();
+
+ try {
+ clientSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ clientSocket = null;
+
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ serverSocket = null;
+ }
+
+ @Override
+ public void write(byte[] data) {
+ if (clientSocket != null) {
+ try {
+ clientSocket.send(data);
+ Log.e(TAG, "Send success!");
+ } catch (IOException e) {
+ Log.e(TAG, "Send failed.");
+ }
+ }
+ }
+
+ public void shutdown() {
+ running = false;
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ serverSocket = null;
+ }
+ }
+ }
+
+ public class ConnectionlessServerThread extends Thread implements WriteCallback {
+ final EchoMachine echoMachine;
+
+ LlcpConnectionlessSocket socket;
+ int mRemoteSap;
+ boolean mRunning = true;
+
+ public ConnectionlessServerThread() {
+ super();
+ echoMachine = new EchoMachine(this, true);
+ }
+
+ @Override
+ public void run() {
+ boolean connectionBroken = false;
+ LlcpPacket packet;
+ if (DBG) Log.d(TAG, "about create LLCP connectionless socket");
+ try {
+ socket = mService.createLlcpConnectionLessSocket(
+ DEFAULT_CL_SAP, CONNECTIONLESS_SERVICE_NAME);
+ if (socket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP connectionless socket");
+ return;
+ }
+
+ while (mRunning && !connectionBroken) {
+ try {
+ packet = socket.receive();
+ if (packet == null || packet.getDataBuffer() == null) {
+ break;
+ }
+ byte[] dataUnit = packet.getDataBuffer();
+ int size = dataUnit.length;
+
+ if (DBG) Log.d(TAG, "read " + packet.getDataBuffer().length + " bytes");
+ if (size < 0) {
+ connectionBroken = true;
+ break;
+ } else {
+ mRemoteSap = packet.getRemoteSap();
+ echoMachine.pushUnit(dataUnit, size);
+ }
+ } catch (IOException e) {
+ // Connection broken
+ connectionBroken = true;
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
+ }
+ }
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ } finally {
+ echoMachine.shutdown();
+
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ }
+
+ public void shutdown() {
+ mRunning = false;
+ }
+
+ @Override
+ public void write(byte[] data) {
+ try {
+ socket.send(mRemoteSap, data);
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "Error writing data.");
+ }
+ }
+ }
+
+ public void onLlcpActivated() {
+ synchronized (this) {
+ // Connectionless server can only be started once the link is up
+ // - otherwise, all calls to receive() on the connectionless socket
+ // will fail immediately.
+ if (mConnectionlessServerThread == null) {
+ mConnectionlessServerThread = new ConnectionlessServerThread();
+ mConnectionlessServerThread.start();
+ }
+ }
+ }
+
+ public void onLlcpDeactivated() {
+ synchronized (this) {
+ if (mConnectionlessServerThread != null) {
+ mConnectionlessServerThread.shutdown();
+ mConnectionlessServerThread = null;
+ }
+ }
+ }
+
+ /**
+ * Needs to be called on the UI thread
+ */
+ public void start() {
+ synchronized (this) {
+ if (mServerThread == null) {
+ mServerThread = new ServerThread();
+ mServerThread.start();
+ }
+ }
+
+ }
+
+ public void stop() {
+ synchronized (this) {
+ if (mServerThread != null) {
+ mServerThread.shutdown();
+ mServerThread = null;
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java b/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
new file mode 100644
index 0000000..10400be
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.session.MediaSessionLegacyHelper;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelUuid;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.widget.Toast;
+
+import com.android.nfc.R;
+
+/**
+ * Connects / Disconnects from a Bluetooth headset (or any device that
+ * might implement BT HSP, HFP, A2DP, or HOGP sink) when touched with NFC.
+ *
+ * This object is created on an NFC interaction, and determines what
+ * sequence of Bluetooth actions to take, and executes them. It is not
+ * designed to be re-used after the sequence has completed or timed out.
+ * Subsequent NFC interactions should use new objects.
+ *
+ */
+public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceListener {
+ static final String TAG = "BluetoothPeripheralHandover";
+ static final boolean DBG = false;
+
+ static final String ACTION_ALLOW_CONNECT = "com.android.nfc.handover.action.ALLOW_CONNECT";
+ static final String ACTION_DENY_CONNECT = "com.android.nfc.handover.action.DENY_CONNECT";
+
+ static final int TIMEOUT_MS = 20000;
+
+ static final int STATE_INIT = 0;
+ static final int STATE_WAITING_FOR_PROXIES = 1;
+ static final int STATE_INIT_COMPLETE = 2;
+ static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3;
+ static final int STATE_BONDING = 4;
+ static final int STATE_CONNECTING = 5;
+ static final int STATE_DISCONNECTING = 6;
+ static final int STATE_COMPLETE = 7;
+
+ static final int RESULT_PENDING = 0;
+ static final int RESULT_CONNECTED = 1;
+ static final int RESULT_DISCONNECTED = 2;
+
+ static final int ACTION_INIT = 0;
+ static final int ACTION_DISCONNECT = 1;
+ static final int ACTION_CONNECT = 2;
+
+ static final int MSG_TIMEOUT = 1;
+ static final int MSG_NEXT_STEP = 2;
+
+ final Context mContext;
+ final BluetoothDevice mDevice;
+ final String mName;
+ final Callback mCallback;
+ final BluetoothAdapter mBluetoothAdapter;
+ final int mTransport;
+ final boolean mProvisioning;
+
+ final Object mLock = new Object();
+
+ // only used on main thread
+ int mAction;
+ int mState;
+ int mHfpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
+ int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING
+ int mHidResult;
+
+ // protected by mLock
+ BluetoothA2dp mA2dp;
+ BluetoothHeadset mHeadset;
+ BluetoothInputDevice mInput;
+
+ public interface Callback {
+ public void onBluetoothPeripheralHandoverComplete(boolean connected);
+ }
+
+ public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name,
+ int transport, Callback callback) {
+ checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work
+ mContext = context;
+ mDevice = device;
+ mName = name;
+ mTransport = transport;
+ mCallback = callback;
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ ContentResolver contentResolver = mContext.getContentResolver();
+ mProvisioning = Settings.Secure.getInt(contentResolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+
+ mState = STATE_INIT;
+ }
+
+ public boolean hasStarted() {
+ return mState != STATE_INIT;
+ }
+
+ /**
+ * Main entry point. This method is usually called after construction,
+ * to begin the BT sequence. Must be called on Main thread.
+ */
+ public boolean start() {
+ checkMainThread();
+ if (mState != STATE_INIT || mBluetoothAdapter == null
+ || (mProvisioning && mTransport != BluetoothDevice.TRANSPORT_LE)) {
+ return false;
+ }
+
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(ACTION_ALLOW_CONNECT);
+ filter.addAction(ACTION_DENY_CONNECT);
+
+ mContext.registerReceiver(mReceiver, filter);
+
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS);
+
+ mAction = ACTION_INIT;
+
+ nextStep();
+
+ return true;
+ }
+
+ /**
+ * Called to execute next step in state machine
+ */
+ void nextStep() {
+ if (mAction == ACTION_INIT) {
+ nextStepInit();
+ } else if (mAction == ACTION_CONNECT) {
+ nextStepConnect();
+ } else {
+ nextStepDisconnect();
+ }
+ }
+
+ /*
+ * Enables bluetooth and gets the profile proxies
+ */
+ void nextStepInit() {
+ switch (mState) {
+ case STATE_INIT:
+ if (mA2dp == null || mHeadset == null || mInput == null) {
+ mState = STATE_WAITING_FOR_PROXIES;
+ if (!getProfileProxys()) {
+ complete(false);
+ }
+ break;
+ }
+ // fall-through
+ case STATE_WAITING_FOR_PROXIES:
+ mState = STATE_INIT_COMPLETE;
+ // Check connected devices and see if we need to disconnect
+ synchronized(mLock) {
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mInput.getConnectedDevices().contains(mDevice)) {
+ Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
+ mAction = ACTION_DISCONNECT;
+ } else {
+ Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
+ mAction = ACTION_CONNECT;
+ }
+ } else {
+ if (mA2dp.getConnectedDevices().contains(mDevice) ||
+ mHeadset.getConnectedDevices().contains(mDevice)) {
+ Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName);
+ mAction = ACTION_DISCONNECT;
+ } else {
+ Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName);
+ mAction = ACTION_CONNECT;
+ }
+ }
+ }
+ nextStep();
+ }
+
+ }
+
+ void nextStepDisconnect() {
+ switch (mState) {
+ case STATE_INIT_COMPLETE:
+ mState = STATE_DISCONNECTING;
+ synchronized (mLock) {
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mInput.getConnectionState(mDevice)
+ != BluetoothProfile.STATE_DISCONNECTED) {
+ mHidResult = RESULT_PENDING;
+ mInput.disconnect(mDevice);
+ toast(getToastString(R.string.disconnecting_peripheral));
+ break;
+ } else {
+ mHidResult = RESULT_DISCONNECTED;
+ }
+ } else {
+ if (mHeadset.getConnectionState(mDevice)
+ != BluetoothProfile.STATE_DISCONNECTED) {
+ mHfpResult = RESULT_PENDING;
+ mHeadset.disconnect(mDevice);
+ } else {
+ mHfpResult = RESULT_DISCONNECTED;
+ }
+ if (mA2dp.getConnectionState(mDevice)
+ != BluetoothProfile.STATE_DISCONNECTED) {
+ mA2dpResult = RESULT_PENDING;
+ mA2dp.disconnect(mDevice);
+ } else {
+ mA2dpResult = RESULT_DISCONNECTED;
+ }
+ if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
+ toast(getToastString(R.string.disconnecting_peripheral));
+ break;
+ }
+ }
+ }
+ // fall-through
+ case STATE_DISCONNECTING:
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mHidResult == RESULT_DISCONNECTED) {
+ toast(getToastString(R.string.disconnected_peripheral));
+ complete(false);
+ }
+
+ break;
+ } else {
+ if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
+ // still disconnecting
+ break;
+ }
+ if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) {
+ toast(getToastString(R.string.disconnected_peripheral));
+ }
+ complete(false);
+ break;
+ }
+
+ }
+
+ }
+
+ private String getToastString(int resid) {
+ return mContext.getString(resid, mName != null ? mName : R.string.device);
+ }
+
+ boolean getProfileProxys() {
+
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.INPUT_DEVICE))
+ return false;
+ } else {
+ if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HEADSET))
+ return false;
+
+ if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.A2DP))
+ return false;
+ }
+
+ return true;
+ }
+
+ void nextStepConnect() {
+ switch (mState) {
+ case STATE_INIT_COMPLETE:
+
+ if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ requestPairConfirmation();
+ mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
+ break;
+ }
+
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mDevice.getBondState() != BluetoothDevice.BOND_NONE) {
+ mDevice.removeBond();
+ requestPairConfirmation();
+ mState = STATE_WAITING_FOR_BOND_CONFIRMATION;
+ break;
+ }
+ }
+ // fall-through
+ case STATE_WAITING_FOR_BOND_CONFIRMATION:
+ if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ startBonding();
+ break;
+ }
+ // fall-through
+ case STATE_BONDING:
+ // Bluetooth Profile service will correctly serialize
+ // HFP then A2DP connect
+ mState = STATE_CONNECTING;
+ synchronized (mLock) {
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mInput.getConnectionState(mDevice)
+ != BluetoothProfile.STATE_CONNECTED) {
+ mHidResult = RESULT_PENDING;
+ mInput.connect(mDevice);
+ toast(getToastString(R.string.connecting_peripheral));
+ break;
+ } else {
+ mHidResult = RESULT_CONNECTED;
+ }
+ } else {
+ if (mHeadset.getConnectionState(mDevice) !=
+ BluetoothProfile.STATE_CONNECTED) {
+ mHfpResult = RESULT_PENDING;
+ mHeadset.connect(mDevice);
+ } else {
+ mHfpResult = RESULT_CONNECTED;
+ }
+ if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) {
+ mA2dpResult = RESULT_PENDING;
+ mA2dp.connect(mDevice);
+ } else {
+ mA2dpResult = RESULT_CONNECTED;
+ }
+ if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
+ toast(getToastString(R.string.connecting_peripheral));
+ break;
+ }
+ }
+ }
+ // fall-through
+ case STATE_CONNECTING:
+ if (mTransport == BluetoothDevice.TRANSPORT_LE) {
+ if (mHidResult == RESULT_PENDING) {
+ break;
+ } else if (mHidResult == RESULT_CONNECTED) {
+ toast(getToastString(R.string.connected_peripheral));
+ mDevice.setAlias(mName);
+ complete(true);
+ } else {
+ toast (getToastString(R.string.connect_peripheral_failed));
+ complete(false);
+ }
+ } else {
+ if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) {
+ // another connection type still pending
+ break;
+ }
+ if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) {
+ // we'll take either as success
+ toast(getToastString(R.string.connected_peripheral));
+ if (mA2dpResult == RESULT_CONNECTED) startTheMusic();
+ mDevice.setAlias(mName);
+ complete(true);
+ } else {
+ toast (getToastString(R.string.connect_peripheral_failed));
+ complete(false);
+ }
+ }
+ break;
+ }
+ }
+
+ void startBonding() {
+ mState = STATE_BONDING;
+ toast(getToastString(R.string.pairing_peripheral));
+ if (!mDevice.createBond(mTransport)) {
+ toast(getToastString(R.string.pairing_peripheral_failed));
+ complete(false);
+ }
+ }
+
+ void handleIntent(Intent intent) {
+ String action = intent.getAction();
+ // Everything requires the device to match...
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (!mDevice.equals(device)) return;
+
+ if (ACTION_ALLOW_CONNECT.equals(action)) {
+ nextStepConnect();
+ } else if (ACTION_DENY_CONNECT.equals(action)) {
+ complete(false);
+ } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)
+ && mState == STATE_BONDING) {
+ int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothAdapter.ERROR);
+ if (bond == BluetoothDevice.BOND_BONDED) {
+ nextStepConnect();
+ } else if (bond == BluetoothDevice.BOND_NONE) {
+ toast(getToastString(R.string.pairing_peripheral_failed));
+ complete(false);
+ }
+ } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
+ (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ mHfpResult = RESULT_CONNECTED;
+ nextStep();
+ } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ mHfpResult = RESULT_DISCONNECTED;
+ nextStep();
+ }
+ } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
+ (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ mA2dpResult = RESULT_CONNECTED;
+ nextStep();
+ } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ mA2dpResult = RESULT_DISCONNECTED;
+ nextStep();
+ }
+ } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action) &&
+ (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR);
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ mHidResult = RESULT_CONNECTED;
+ nextStep();
+ } else if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ mHidResult = RESULT_DISCONNECTED;
+ nextStep();
+ }
+ }
+ }
+
+ void complete(boolean connected) {
+ if (DBG) Log.d(TAG, "complete()");
+ mState = STATE_COMPLETE;
+ mContext.unregisterReceiver(mReceiver);
+ mHandler.removeMessages(MSG_TIMEOUT);
+ synchronized (mLock) {
+ if (mA2dp != null) {
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp);
+ }
+ if (mHeadset != null) {
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset);
+ }
+
+ if (mInput != null) {
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInput);
+ }
+
+ mA2dp = null;
+ mHeadset = null;
+ mInput = null;
+ }
+ mCallback.onBluetoothPeripheralHandoverComplete(connected);
+ }
+
+ void toast(CharSequence text) {
+ Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
+ }
+
+ void startTheMusic() {
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ if (helper != null) {
+ KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
+ helper.sendMediaButtonEvent(keyEvent, false);
+ keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY);
+ helper.sendMediaButtonEvent(keyEvent, false);
+ } else {
+ Log.w(TAG, "Unable to send media key event");
+ }
+ }
+
+ void requestPairConfirmation() {
+ Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class);
+ dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+
+ mContext.startActivity(dialogIntent);
+ }
+
+ final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_TIMEOUT:
+ if (mState == STATE_COMPLETE) return;
+ Log.i(TAG, "Timeout completing BT handover");
+ complete(false);
+ break;
+ case MSG_NEXT_STEP:
+ nextStep();
+ break;
+ }
+ }
+ };
+
+ final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleIntent(intent);
+ }
+ };
+
+ static void checkMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalThreadStateException("must be called on main thread");
+ }
+ }
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ synchronized (mLock) {
+ switch (profile) {
+ case BluetoothProfile.HEADSET:
+ mHeadset = (BluetoothHeadset) proxy;
+ if (mA2dp != null) {
+ mHandler.sendEmptyMessage(MSG_NEXT_STEP);
+ }
+ break;
+ case BluetoothProfile.A2DP:
+ mA2dp = (BluetoothA2dp) proxy;
+ if (mHeadset != null) {
+ mHandler.sendEmptyMessage(MSG_NEXT_STEP);
+ }
+ break;
+ case BluetoothProfile.INPUT_DEVICE:
+ mInput = (BluetoothInputDevice) proxy;
+ if (mInput != null) {
+ mHandler.sendEmptyMessage(MSG_NEXT_STEP);
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ // We can ignore these
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java b/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java
new file mode 100644
index 0000000..4a7cd75
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+
+import com.android.nfc.R;
+
+public class ConfirmConnectActivity extends Activity {
+ BluetoothDevice mDevice;
+ AlertDialog mAlert = null;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this,
+ AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ Intent launchIntent = getIntent();
+ mDevice = launchIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (mDevice == null) finish();
+ Resources res = getResources();
+ String deviceName = mDevice.getName() != null ? mDevice.getName() : "";
+ String confirmString = String.format(res.getString(R.string.confirm_pairing), deviceName);
+ builder.setMessage(confirmString)
+ .setCancelable(false)
+ .setPositiveButton(res.getString(R.string.pair_yes),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ Intent allowIntent = new Intent(BluetoothPeripheralHandover.ACTION_ALLOW_CONNECT);
+ allowIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ sendBroadcast(allowIntent);
+ ConfirmConnectActivity.this.mAlert = null;
+ ConfirmConnectActivity.this.finish();
+ }
+ })
+ .setNegativeButton(res.getString(R.string.pair_no),
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ Intent denyIntent = new Intent(BluetoothPeripheralHandover.ACTION_DENY_CONNECT);
+ denyIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ sendBroadcast(denyIntent);
+ ConfirmConnectActivity.this.mAlert = null;
+ ConfirmConnectActivity.this.finish();
+ }
+ });
+ mAlert = builder.create();
+ mAlert.show();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mAlert != null) {
+ mAlert.dismiss();
+ Intent denyIntent = new Intent(BluetoothPeripheralHandover.ACTION_DENY_CONNECT);
+ denyIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+ sendBroadcast(denyIntent);
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/handover/HandoverClient.java b/NfcSony/src/com/android/nfc/handover/HandoverClient.java
new file mode 100644
index 0000000..20e4ef6
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/HandoverClient.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.util.Log;
+
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+import com.android.nfc.DeviceHost.LlcpSocket;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public final class HandoverClient {
+ private static final String TAG = "HandoverClient";
+ private static final int MIU = 128;
+ private static final boolean DBG = false;
+
+ private static final int DISCONNECTED = 0;
+ private static final int CONNECTING = 1;
+ private static final int CONNECTED = 2;
+
+ private static final Object mLock = new Object();
+
+ // Variables below synchronized on mLock
+ LlcpSocket mSocket;
+ int mState;
+
+ public void connect() throws IOException {
+ synchronized (mLock) {
+ if (mState != DISCONNECTED) {
+ throw new IOException("Socket in use.");
+ }
+ mState = CONNECTING;
+ }
+ NfcService service = NfcService.getInstance();
+ LlcpSocket sock = null;
+ try {
+ sock = service.createLlcpSocket(0, MIU, 1, 1024);
+ } catch (LlcpException e) {
+ synchronized (mLock) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Could not create socket");
+ }
+ try {
+ if (DBG) Log.d(TAG, "about to connect to service " +
+ HandoverServer.HANDOVER_SERVICE_NAME);
+ sock.connectToService(HandoverServer.HANDOVER_SERVICE_NAME);
+ } catch (IOException e) {
+ if (sock != null) {
+ try {
+ sock.close();
+ } catch (IOException e2) {
+ // Ignore
+ }
+ }
+ synchronized (mLock) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Could not connect to handover service");
+ }
+ synchronized (mLock) {
+ mSocket = sock;
+ mState = CONNECTED;
+ }
+ }
+
+ public void close() {
+ synchronized (mLock) {
+ if (mSocket != null) {
+ try {
+ mSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ mSocket = null;
+ }
+ mState = DISCONNECTED;
+ }
+ }
+ public NdefMessage sendHandoverRequest(NdefMessage msg) throws IOException {
+ if (msg == null) return null;
+
+ LlcpSocket sock = null;
+ synchronized (mLock) {
+ if (mState != CONNECTED) {
+ throw new IOException("Socket not connected");
+ }
+ sock = mSocket;
+ }
+ int offset = 0;
+ byte[] buffer = msg.toByteArray();
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+
+ try {
+ int remoteMiu = sock.getRemoteMiu();
+ if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
+ while (offset < buffer.length) {
+ int length = Math.min(buffer.length - offset, remoteMiu);
+ byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length);
+ if (DBG) Log.d(TAG, "about to send a " + length + " byte packet");
+ sock.send(tmpBuffer);
+ offset += length;
+ }
+
+ // Now, try to read back the handover response
+ byte[] partial = new byte[sock.getLocalMiu()];
+ NdefMessage handoverSelectMsg = null;
+ while (true) {
+ int size = sock.receive(partial);
+ if (size < 0) {
+ break;
+ }
+ byteStream.write(partial, 0, size);
+ try {
+ handoverSelectMsg = new NdefMessage(byteStream.toByteArray());
+ // If we get here, message is complete
+ break;
+ } catch (FormatException e) {
+ // Ignore, and try to fetch more bytes
+ }
+ }
+ return handoverSelectMsg;
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "couldn't connect to handover service");
+ } finally {
+ if (sock != null) {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ sock.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ try {
+ byteStream.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ return null;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java b/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java
new file mode 100644
index 0000000..691488f
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Random;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Manages handover of NFC to other technologies.
+ */
+public class HandoverDataParser {
+ private static final String TAG = "NfcHandover";
+ private static final boolean DBG = false;
+
+ private static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob"
+ .getBytes(StandardCharsets.US_ASCII);
+ private static final byte[] TYPE_BLE_OOB = "application/vnd.bluetooth.le.oob"
+ .getBytes(StandardCharsets.US_ASCII);
+
+ private static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(StandardCharsets.US_ASCII);
+
+ private static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr";
+
+ private static final int CARRIER_POWER_STATE_INACTIVE = 0;
+ private static final int CARRIER_POWER_STATE_ACTIVE = 1;
+ private static final int CARRIER_POWER_STATE_ACTIVATING = 2;
+ private static final int CARRIER_POWER_STATE_UNKNOWN = 3;
+
+ private static final int BT_HANDOVER_TYPE_MAC = 0x1B;
+ private static final int BT_HANDOVER_TYPE_LE_ROLE = 0x1C;
+ private static final int BT_HANDOVER_TYPE_LONG_LOCAL_NAME = 0x09;
+ private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08;
+ public static final int BT_HANDOVER_LE_ROLE_CENTRAL_ONLY = 0x01;
+
+ private final BluetoothAdapter mBluetoothAdapter;
+
+ private final Object mLock = new Object();
+ // Variables below synchronized on mLock
+
+ private String mLocalBluetoothAddress;
+
+ public static class BluetoothHandoverData {
+ public boolean valid = false;
+ public BluetoothDevice device;
+ public String name;
+ public boolean carrierActivating = false;
+ public int transport = BluetoothDevice.TRANSPORT_AUTO;
+ }
+
+ public static class IncomingHandoverData {
+ public final NdefMessage handoverSelect;
+ public final BluetoothHandoverData handoverData;
+
+ public IncomingHandoverData(NdefMessage handoverSelect,
+ BluetoothHandoverData handoverData) {
+ this.handoverSelect = handoverSelect;
+ this.handoverData = handoverData;
+ }
+ }
+
+ public HandoverDataParser() {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ static NdefRecord createCollisionRecord() {
+ byte[] random = new byte[2];
+ new Random().nextBytes(random);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, RTD_COLLISION_RESOLUTION, null, random);
+ }
+
+ NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) {
+ byte[] payload = new byte[4];
+ payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING :
+ CARRIER_POWER_STATE_ACTIVE); // Carrier Power State: Activating or active
+ payload[1] = 1; // length of carrier data reference
+ payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record
+ payload[3] = 0; // Auxiliary data reference count
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null,
+ payload);
+ }
+
+ NdefRecord createBluetoothOobDataRecord() {
+ byte[] payload = new byte[8];
+ // Note: this field should be little-endian per the BTSSP spec
+ // The Android 4.1 implementation used big-endian order here.
+ // No single Android implementation has ever interpreted this
+ // length field when parsing this record though.
+ payload[0] = (byte) (payload.length & 0xFF);
+ payload[1] = (byte) ((payload.length >> 8) & 0xFF);
+
+ synchronized (mLock) {
+ if (mLocalBluetoothAddress == null) {
+ mLocalBluetoothAddress = mBluetoothAdapter.getAddress();
+ }
+
+ byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress);
+ System.arraycopy(addressBytes, 0, payload, 2, 6);
+ }
+
+ return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload);
+ }
+
+ public boolean isHandoverSupported() {
+ return (mBluetoothAdapter != null);
+ }
+
+ public NdefMessage createHandoverRequestMessage() {
+ if (mBluetoothAdapter == null) {
+ return null;
+ }
+
+ NdefRecord[] dataRecords = new NdefRecord[] {
+ createBluetoothOobDataRecord()
+ };
+ return new NdefMessage(
+ createHandoverRequestRecord(),
+ dataRecords);
+ }
+
+ NdefMessage createBluetoothHandoverSelectMessage(boolean activating) {
+ return new NdefMessage(createHandoverSelectRecord(
+ createBluetoothAlternateCarrierRecord(activating)),
+ createBluetoothOobDataRecord());
+ }
+
+ NdefRecord createHandoverSelectRecord(NdefRecord alternateCarrier) {
+ NdefMessage nestedMessage = new NdefMessage(alternateCarrier);
+ byte[] nestedPayload = nestedMessage.toByteArray();
+
+ ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
+ payload.put((byte)0x12); // connection handover v1.2
+ payload.put(nestedPayload);
+
+ byte[] payloadBytes = new byte[payload.position()];
+ payload.position(0);
+ payload.get(payloadBytes);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null,
+ payloadBytes);
+ }
+
+ NdefRecord createHandoverRequestRecord() {
+ NdefRecord[] messages = new NdefRecord[] {
+ createBluetoothAlternateCarrierRecord(false)
+ };
+
+ NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(), messages);
+
+ byte[] nestedPayload = nestedMessage.toByteArray();
+
+ ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1);
+ payload.put((byte) 0x12); // connection handover v1.2
+ payload.put(nestedMessage.toByteArray());
+
+ byte[] payloadBytes = new byte[payload.position()];
+ payload.position(0);
+ payload.get(payloadBytes);
+ return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null,
+ payloadBytes);
+ }
+
+ /**
+ * Returns null if message is not a Handover Request,
+ * returns the IncomingHandoverData (Hs + parsed data) if it is.
+ */
+ public IncomingHandoverData getIncomingHandoverData(NdefMessage handoverRequest) {
+ if (handoverRequest == null) return null;
+ if (mBluetoothAdapter == null) return null;
+
+ if (DBG) Log.d(TAG, "getIncomingHandoverData():" + handoverRequest.toString());
+
+ NdefRecord handoverRequestRecord = handoverRequest.getRecords()[0];
+ if (handoverRequestRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) {
+ return null;
+ }
+
+ if (!Arrays.equals(handoverRequestRecord.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) {
+ return null;
+ }
+
+ // we have a handover request, look for BT OOB record
+ BluetoothHandoverData bluetoothData = null;
+ for (NdefRecord dataRecord : handoverRequest.getRecords()) {
+ if (dataRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) {
+ if (Arrays.equals(dataRecord.getType(), TYPE_BT_OOB)) {
+ bluetoothData = parseBtOob(ByteBuffer.wrap(dataRecord.getPayload()));
+ }
+ }
+ }
+
+ NdefMessage hs = tryBluetoothHandoverRequest(bluetoothData);
+ if (hs != null) {
+ return new IncomingHandoverData(hs, bluetoothData);
+ }
+
+ return null;
+ }
+
+ public BluetoothHandoverData getOutgoingHandoverData(NdefMessage handoverSelect) {
+ return parseBluetooth(handoverSelect);
+ }
+
+ private NdefMessage tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData) {
+ NdefMessage selectMessage = null;
+ if (bluetoothData != null) {
+ // Note: there could be a race where we conclude
+ // that Bluetooth is already enabled, and shortly
+ // after the user turns it off. That will cause
+ // the transfer to fail, but there's nothing
+ // much we can do about it anyway. It shouldn't
+ // be common for the user to be changing BT settings
+ // while waiting to receive a picture.
+ boolean bluetoothActivating = !mBluetoothAdapter.isEnabled();
+
+ // return BT OOB record so they can perform handover
+ selectMessage = (createBluetoothHandoverSelectMessage(bluetoothActivating));
+ if (DBG) Log.d(TAG, "Waiting for incoming transfer, [" +
+ bluetoothData.device.getAddress() + "]->[" + mLocalBluetoothAddress + "]");
+ }
+
+ return selectMessage;
+ }
+
+
+
+ boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) {
+ byte[] payload = handoverRec.getPayload();
+ if (payload == null || payload.length <= 1) return false;
+ // Skip version
+ byte[] payloadNdef = new byte[payload.length - 1];
+ System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1);
+ NdefMessage msg;
+ try {
+ msg = new NdefMessage(payloadNdef);
+ } catch (FormatException e) {
+ return false;
+ }
+
+ for (NdefRecord alt : msg.getRecords()) {
+ byte[] acPayload = alt.getPayload();
+ if (acPayload != null) {
+ ByteBuffer buf = ByteBuffer.wrap(acPayload);
+ int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits
+ int carrierRefLength = buf.get() & 0xFF;
+ if (carrierRefLength != carrierId.length) return false;
+
+ byte[] carrierRefId = new byte[carrierRefLength];
+ buf.get(carrierRefId);
+ if (Arrays.equals(carrierRefId, carrierId)) {
+ // Found match, returning whether power state is activating
+ return (cps == CARRIER_POWER_STATE_ACTIVATING);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ BluetoothHandoverData parseBluetoothHandoverSelect(NdefMessage m) {
+ // TODO we could parse this a lot more strictly; right now
+ // we just search for a BT OOB record, and try to cross-reference
+ // the carrier state inside the 'hs' payload.
+ for (NdefRecord oob : m.getRecords()) {
+ if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
+ Arrays.equals(oob.getType(), TYPE_BT_OOB)) {
+ BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload()));
+ if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) {
+ data.carrierActivating = true;
+ }
+ return data;
+ }
+
+ if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
+ Arrays.equals(oob.getType(), TYPE_BLE_OOB)) {
+ return parseBleOob(ByteBuffer.wrap(oob.getPayload()));
+ }
+ }
+
+ return null;
+ }
+
+ public BluetoothHandoverData parseBluetooth(NdefMessage m) {
+ NdefRecord r = m.getRecords()[0];
+ short tnf = r.getTnf();
+ byte[] type = r.getType();
+
+ // Check for BT OOB record
+ if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) {
+ return parseBtOob(ByteBuffer.wrap(r.getPayload()));
+ }
+
+ // Check for BLE OOB record
+ if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BLE_OOB)) {
+ return parseBleOob(ByteBuffer.wrap(r.getPayload()));
+ }
+
+ // Check for Handover Select, followed by a BT OOB record
+ if (tnf == NdefRecord.TNF_WELL_KNOWN &&
+ Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) {
+ return parseBluetoothHandoverSelect(m);
+ }
+
+ // Check for Nokia BT record, found on some Nokia BH-505 Headsets
+ if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) {
+ return parseNokia(ByteBuffer.wrap(r.getPayload()));
+ }
+
+ return null;
+ }
+
+ BluetoothHandoverData parseNokia(ByteBuffer payload) {
+ BluetoothHandoverData result = new BluetoothHandoverData();
+ result.valid = false;
+
+ try {
+ payload.position(1);
+ byte[] address = new byte[6];
+ payload.get(address);
+ result.device = mBluetoothAdapter.getRemoteDevice(address);
+ result.valid = true;
+ payload.position(14);
+ int nameLength = payload.get();
+ byte[] nameBytes = new byte[nameLength];
+ payload.get(nameBytes);
+ result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ } catch (IllegalArgumentException e) {
+ Log.i(TAG, "nokia: invalid BT address");
+ } catch (BufferUnderflowException e) {
+ Log.i(TAG, "nokia: payload shorter than expected");
+ }
+ if (result.valid && result.name == null) result.name = "";
+ return result;
+ }
+
+ BluetoothHandoverData parseBtOob(ByteBuffer payload) {
+ BluetoothHandoverData result = new BluetoothHandoverData();
+ result.valid = false;
+
+ try {
+ payload.position(2); // length
+ byte[] address = parseMacFromBluetoothRecord(payload);
+ result.device = mBluetoothAdapter.getRemoteDevice(address);
+ result.valid = true;
+
+ while (payload.remaining() > 0) {
+ byte[] nameBytes;
+ int len = payload.get();
+ int type = payload.get();
+ switch (type) {
+ case BT_HANDOVER_TYPE_SHORT_LOCAL_NAME:
+ nameBytes = new byte[len - 1];
+ payload.get(nameBytes);
+ result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ break;
+ case BT_HANDOVER_TYPE_LONG_LOCAL_NAME:
+ if (result.name != null) break; // prefer short name
+ nameBytes = new byte[len - 1];
+ payload.get(nameBytes);
+ result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ break;
+ default:
+ payload.position(payload.position() + len - 1);
+ break;
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ Log.i(TAG, "BT OOB: invalid BT address");
+ } catch (BufferUnderflowException e) {
+ Log.i(TAG, "BT OOB: payload shorter than expected");
+ }
+ if (result.valid && result.name == null) result.name = "";
+ return result;
+ }
+
+ BluetoothHandoverData parseBleOob(ByteBuffer payload) {
+ BluetoothHandoverData result = new BluetoothHandoverData();
+ result.valid = false;
+ result.transport = BluetoothDevice.TRANSPORT_LE;
+
+ try {
+
+ while (payload.remaining() > 0) {
+ byte[] nameBytes;
+ int len = payload.get();
+ int type = payload.get();
+ switch (type) {
+ case BT_HANDOVER_TYPE_MAC: // mac address
+ byte[] address = parseMacFromBluetoothRecord(payload);
+ payload.position(payload.position() + 1); // advance over random byte
+ result.device = mBluetoothAdapter.getRemoteDevice(address);
+ result.valid = true;
+ break;
+ case BT_HANDOVER_TYPE_LE_ROLE:
+ byte role = payload.get();
+ if (role == BT_HANDOVER_LE_ROLE_CENTRAL_ONLY) {
+ // only central role supported, can't pair
+ result.valid = false;
+ return result;
+ }
+ break;
+ case BT_HANDOVER_TYPE_LONG_LOCAL_NAME:
+ nameBytes = new byte[len - 1];
+ payload.get(nameBytes);
+ result.name = new String(nameBytes, StandardCharsets.UTF_8);
+ break;
+ default:
+ payload.position(payload.position() + len - 1);
+ break;
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ Log.i(TAG, "BT OOB: invalid BT address");
+ } catch (BufferUnderflowException e) {
+ Log.i(TAG, "BT OOB: payload shorter than expected");
+ }
+ if (result.valid && result.name == null) result.name = "";
+ return result;
+ }
+
+ private byte[] parseMacFromBluetoothRecord(ByteBuffer payload) {
+ byte[] address = new byte[6];
+ payload.get(address);
+ // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for
+ // ByteBuffer.get(byte[]), so manually swap order
+ for (int i = 0; i < 3; i++) {
+ byte temp = address[i];
+ address[i] = address[5 - i];
+ address[5 - i] = temp;
+ }
+ return address;
+ }
+
+ static byte[] addressToReverseBytes(String address) {
+ String[] split = address.split(":");
+ byte[] result = new byte[split.length];
+
+ for (int i = 0; i < split.length; i++) {
+ // need to parse as int because parseByte() expects a signed byte
+ result[split.length - 1 - i] = (byte)Integer.parseInt(split[i], 16);
+ }
+
+ return result;
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/handover/HandoverServer.java b/NfcSony/src/com/android/nfc/handover/HandoverServer.java
new file mode 100644
index 0000000..23ac1d9
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/HandoverServer.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+import com.android.nfc.beam.BeamManager;
+import com.android.nfc.beam.BeamReceiveService;
+import com.android.nfc.beam.BeamTransferRecord;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public final class HandoverServer {
+ static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover";
+ static final String TAG = "HandoverServer";
+ static final Boolean DBG = false;
+
+ static final int MIU = 128;
+
+ final HandoverDataParser mHandoverDataParser;
+ final int mSap;
+ final Callback mCallback;
+ private final Context mContext;
+
+ ServerThread mServerThread = null;
+ boolean mServerRunning = false;
+
+ public interface Callback {
+ void onHandoverRequestReceived();
+ void onHandoverBusy();
+ }
+
+ public HandoverServer(Context context, int sap, HandoverDataParser manager, Callback callback) {
+ mContext = context;
+ mSap = sap;
+ mHandoverDataParser = manager;
+ mCallback = callback;
+ }
+
+ public synchronized void start() {
+ if (mServerThread == null) {
+ mServerThread = new ServerThread();
+ mServerThread.start();
+ mServerRunning = true;
+ }
+ }
+
+ public synchronized void stop() {
+ if (mServerThread != null) {
+ mServerThread.shutdown();
+ mServerThread = null;
+ mServerRunning = false;
+ }
+ }
+
+ private class ServerThread extends Thread {
+ private boolean mThreadRunning = true;
+ LlcpServerSocket mServerSocket;
+
+ @Override
+ public void run() {
+ boolean threadRunning;
+ synchronized (HandoverServer.this) {
+ threadRunning = mThreadRunning;
+ }
+
+ while (threadRunning) {
+ try {
+ synchronized (HandoverServer.this) {
+ mServerSocket = NfcService.getInstance().createLlcpServerSocket(mSap,
+ HANDOVER_SERVICE_NAME, MIU, 1, 1024);
+ }
+ if (mServerSocket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
+ return;
+ }
+ if (DBG) Log.d(TAG, "created LLCP service socket");
+ synchronized (HandoverServer.this) {
+ threadRunning = mThreadRunning;
+ }
+
+ while (threadRunning) {
+ LlcpServerSocket serverSocket;
+ synchronized (HandoverServer.this) {
+ serverSocket = mServerSocket;
+ }
+
+ if (serverSocket == null) {
+ if (DBG) Log.d(TAG, "Server socket shut down.");
+ return;
+ }
+ if (DBG) Log.d(TAG, "about to accept");
+ LlcpSocket communicationSocket = serverSocket.accept();
+ if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
+ if (communicationSocket != null) {
+ new ConnectionThread(communicationSocket).start();
+ }
+
+ synchronized (HandoverServer.this) {
+ threadRunning = mThreadRunning;
+ }
+ }
+ if (DBG) Log.d(TAG, "stop running");
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ } catch (IOException e) {
+ Log.e(TAG, "IO error", e);
+ } finally {
+ synchronized (HandoverServer.this) {
+ if (mServerSocket != null) {
+ if (DBG) Log.d(TAG, "about to close");
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+
+ synchronized (HandoverServer.this) {
+ threadRunning = mThreadRunning;
+ }
+ }
+ }
+
+ public void shutdown() {
+ synchronized (HandoverServer.this) {
+ mThreadRunning = false;
+ if (mServerSocket != null) {
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+ }
+
+ private class ConnectionThread extends Thread {
+ private final LlcpSocket mSock;
+
+ ConnectionThread(LlcpSocket socket) {
+ super(TAG);
+ mSock = socket;
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "starting connection thread");
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+
+ try {
+ boolean running;
+ synchronized (HandoverServer.this) {
+ running = mServerRunning;
+ }
+
+ byte[] partial = new byte[mSock.getLocalMiu()];
+
+ NdefMessage handoverRequestMsg = null;
+ while (running) {
+ int size = mSock.receive(partial);
+ if (size < 0) {
+ break;
+ }
+ byteStream.write(partial, 0, size);
+ // 1) Try to parse a handover request message from bytes received so far
+ try {
+ handoverRequestMsg = new NdefMessage(byteStream.toByteArray());
+ } catch (FormatException e) {
+ // Ignore, and try to fetch more bytes
+ }
+
+ if (handoverRequestMsg != null) {
+ BeamManager beamManager = BeamManager.getInstance();
+
+ if (beamManager.isBeamInProgress()) {
+ mCallback.onHandoverBusy();
+ break;
+ }
+
+ // 2) convert to handover response
+ HandoverDataParser.IncomingHandoverData handoverData
+ = mHandoverDataParser.getIncomingHandoverData(handoverRequestMsg);
+ if (handoverData == null) {
+ Log.e(TAG, "Failed to create handover response");
+ break;
+ }
+
+ // 3) send handover response
+ int offset = 0;
+ byte[] buffer = handoverData.handoverSelect.toByteArray();
+ int remoteMiu = mSock.getRemoteMiu();
+ while (offset < buffer.length) {
+ int length = Math.min(buffer.length - offset, remoteMiu);
+ byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length);
+ mSock.send(tmpBuffer);
+ offset += length;
+ }
+ // We're done
+ mCallback.onHandoverRequestReceived();
+ if (!beamManager.startBeamReceive(mContext, handoverData.handoverData)) {
+ mCallback.onHandoverBusy();
+ break;
+ }
+ // We can process another handover transfer
+ byteStream = new ByteArrayOutputStream();
+ }
+
+ synchronized (HandoverServer.this) {
+ running = mServerRunning;
+ }
+ }
+
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "IOException");
+ } finally {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ mSock.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ try {
+ byteStream.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (DBG) Log.d(TAG, "finished connection thread");
+ }
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java b/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java
new file mode 100644
index 0000000..6c30244
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 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.handover;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.nfc.R;
+
+public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
+ static final String TAG = "PeripheralHandoverService";
+ static final boolean DBG = true;
+
+ static final int MSG_PAUSE_POLLING = 0;
+
+ public static final String BUNDLE_TRANSFER = "transfer";
+ public static final String EXTRA_PERIPHERAL_DEVICE = "device";
+ public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
+ public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
+
+ // Amount of time to pause polling when connecting to peripherals
+ private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
+ private static final int PAUSE_DELAY_MILLIS = 300;
+
+ private static final Object sLock = new Object();
+
+ // Variables below only accessed on main thread
+ final Messenger mMessenger;
+
+ SoundPool mSoundPool;
+ int mSuccessSound;
+ int mStartId;
+
+ BluetoothAdapter mBluetoothAdapter;
+ NfcAdapter mNfcAdapter;
+ Handler mHandler;
+ BluetoothPeripheralHandover mBluetoothPeripheralHandover;
+ boolean mBluetoothHeadsetConnected;
+ boolean mBluetoothEnabledByNfc;
+
+ class MessageHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_PAUSE_POLLING:
+ mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
+ break;
+ }
+ }
+ }
+
+ final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ handleBluetoothStateChanged(intent);
+ }
+ }
+ };
+
+ public PeripheralHandoverService() {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mHandler = new MessageHandler();
+ mMessenger = new Messenger(mHandler);
+ mBluetoothHeadsetConnected = false;
+ mBluetoothEnabledByNfc = false;
+ mStartId = 0;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+
+ synchronized (sLock) {
+ if (mStartId != 0) {
+ mStartId = startId;
+ // already running
+ return START_STICKY;
+ }
+ mStartId = startId;
+ }
+
+ if (intent == null) {
+ if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+
+ if (doPeripheralHandover(intent.getExtras())) {
+ return START_STICKY;
+ } else {
+ stopSelf(startId);
+ return START_NOT_STICKY;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
+ mSuccessSound = mSoundPool.load(this, R.raw.end, 1);
+ mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+
+ IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+ registerReceiver(mBluetoothStatusReceiver, filter);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mSoundPool != null) {
+ mSoundPool.release();
+ }
+ unregisterReceiver(mBluetoothStatusReceiver);
+ }
+
+ boolean doPeripheralHandover(Bundle msgData) {
+ if (mBluetoothPeripheralHandover != null) {
+ Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
+ return true;
+ }
+
+ if (msgData == null) {
+ return false;
+ }
+
+ BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
+ String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
+ int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
+
+ mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
+ this, device, name, transport, this);
+
+ if (transport == BluetoothDevice.TRANSPORT_LE) {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
+ }
+ if (mBluetoothAdapter.isEnabled()) {
+ if (!mBluetoothPeripheralHandover.start()) {
+ mHandler.removeMessages(MSG_PAUSE_POLLING);
+ mNfcAdapter.resumePolling();
+ }
+ } else {
+ // Once BT is enabled, the headset pairing will be started
+ if (!enableBluetooth()) {
+ Log.e(TAG, "Error enabling Bluetooth.");
+ mBluetoothPeripheralHandover = null;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void handleBluetoothStateChanged(Intent intent) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ if (state == BluetoothAdapter.STATE_ON) {
+ // If there is a pending device pairing, start it
+ if (mBluetoothPeripheralHandover != null &&
+ !mBluetoothPeripheralHandover.hasStarted()) {
+ if (!mBluetoothPeripheralHandover.start()) {
+ mNfcAdapter.resumePolling();
+ }
+ }
+ } else if (state == BluetoothAdapter.STATE_OFF) {
+ mBluetoothEnabledByNfc = false;
+ mBluetoothHeadsetConnected = false;
+ }
+ }
+
+ @Override
+ public void onBluetoothPeripheralHandoverComplete(boolean connected) {
+ // Called on the main thread
+ int transport = mBluetoothPeripheralHandover.mTransport;
+ mBluetoothPeripheralHandover = null;
+ mBluetoothHeadsetConnected = connected;
+
+ // resume polling immediately if the connection failed,
+ // otherwise just wait for polling to come back up after the timeout
+ // This ensures we don't disconnect if the user left the volantis
+ // on the tag after pairing completed, which results in automatic
+ // disconnection
+ if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
+ if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
+ mHandler.removeMessages(MSG_PAUSE_POLLING);
+ }
+
+ // do this unconditionally as the polling could have been paused as we were removing
+ // the message in the handler. It's a no-op if polling is already enabled.
+ mNfcAdapter.resumePolling();
+ }
+ disableBluetoothIfNeeded();
+
+ synchronized (sLock) {
+ stopSelf(mStartId);
+ mStartId = 0;
+ }
+ }
+
+
+ boolean enableBluetooth() {
+ if (!mBluetoothAdapter.isEnabled()) {
+ mBluetoothEnabledByNfc = true;
+ return mBluetoothAdapter.enableNoAutoConnect();
+ }
+ return true;
+ }
+
+ void disableBluetoothIfNeeded() {
+ if (!mBluetoothEnabledByNfc) return;
+
+ if (!mBluetoothHeadsetConnected) {
+ mBluetoothAdapter.disable();
+ mBluetoothEnabledByNfc = false;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ // prevent any future callbacks to the client, no rebind call needed.
+ return false;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java
new file mode 100755
index 0000000..842fb98
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java
@@ -0,0 +1,144 @@
+/*
+ * 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.ndefpush;
+
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+
+import android.nfc.NdefMessage;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * Simple client to push the local NDEF message to a server on the remote side of an
+ * LLCP connection, using the Android Ndef Push Protocol.
+ */
+public class NdefPushClient {
+ private static final String TAG = "NdefPushClient";
+ private static final int MIU = 128;
+ private static final boolean DBG = true;
+
+ private static final int DISCONNECTED = 0;
+ private static final int CONNECTING = 1;
+ private static final int CONNECTED = 2;
+
+ final Object mLock = new Object();
+ // Variables below locked on mLock
+ private int mState = DISCONNECTED;
+ private LlcpSocket mSocket;
+
+ public void connect() throws IOException {
+ synchronized (mLock) {
+ if (mState != DISCONNECTED) {
+ throw new IOException("Socket still in use.");
+ }
+ mState = CONNECTING;
+ }
+ NfcService service = NfcService.getInstance();
+ LlcpSocket sock = null;
+ if (DBG) Log.d(TAG, "about to create socket");
+ try {
+ sock = service.createLlcpSocket(0, MIU, 1, 1024);
+ } catch (LlcpException e) {
+ synchronized (mLock) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Could not create socket.");
+ }
+ try {
+ if (DBG) Log.d(TAG, "about to connect to service " + NdefPushServer.SERVICE_NAME);
+ sock.connectToService(NdefPushServer.SERVICE_NAME);
+ } catch (IOException e) {
+ if (sock != null) {
+ try {
+ sock.close();
+ } catch (IOException e2) {
+
+ }
+ }
+ synchronized (mLock) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Could not connect service.");
+ }
+
+ synchronized (mLock) {
+ mSocket = sock;
+ mState = CONNECTED;
+ }
+ }
+
+ public boolean push(NdefMessage msg) {
+ LlcpSocket sock = null;
+ synchronized (mLock) {
+ if (mState != CONNECTED) {
+ Log.e(TAG, "Not connected to NPP.");
+ return false;
+ }
+ sock = mSocket;
+ }
+ // We only handle a single immediate action for now
+ NdefPushProtocol proto = new NdefPushProtocol(msg, NdefPushProtocol.ACTION_IMMEDIATE);
+ byte[] buffer = proto.toByteArray();
+ int offset = 0;
+ int remoteMiu;
+
+ try {
+ remoteMiu = sock.getRemoteMiu();
+ if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
+ while (offset < buffer.length) {
+ int length = Math.min(buffer.length - offset, remoteMiu);
+ byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length);
+ if (DBG) Log.d(TAG, "about to send a " + length + " byte packet");
+ sock.send(tmpBuffer);
+ offset += length;
+ }
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "couldn't send tag");
+ if (DBG) Log.d(TAG, "exception:", e);
+ } finally {
+ if (sock != null) {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ sock.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+ return false;
+ }
+
+ public void close() {
+ synchronized (mLock) {
+ if (mSocket != null) {
+ try {
+ if (DBG) Log.d(TAG, "About to close NPP socket.");
+ mSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ mSocket = null;
+ }
+ mState = DISCONNECTED;
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java
new file mode 100644
index 0000000..5520615
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 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.ndefpush;
+
+import android.util.Log;
+
+import android.nfc.NdefMessage;
+import android.nfc.FormatException;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+
+/**
+ * Implementation of the NDEF Push Protocol.
+ */
+public class NdefPushProtocol {
+ public static final byte ACTION_IMMEDIATE = (byte) 0x01;
+ public static final byte ACTION_BACKGROUND = (byte) 0x02;
+
+ private static final String TAG = "NdefMessageSet";
+ private static final byte VERSION = 1;
+
+ private int mNumMessages;
+ private byte[] mActions;
+ private NdefMessage[] mMessages;
+
+ public NdefPushProtocol(NdefMessage msg, byte action) {
+ mNumMessages = 1;
+ mActions = new byte[1];
+ mActions[0] = action;
+ mMessages = new NdefMessage[1];
+ mMessages[0] = msg;
+ }
+
+ public NdefPushProtocol(byte[] actions, NdefMessage[] messages) {
+ if (actions.length != messages.length || actions.length == 0) {
+ throw new IllegalArgumentException(
+ "actions and messages must be the same size and non-empty");
+ }
+
+ // Keep a copy of these arrays
+ int numMessages = actions.length;
+ mActions = new byte[numMessages];
+ System.arraycopy(actions, 0, mActions, 0, numMessages);
+ mMessages = new NdefMessage[numMessages];
+ System.arraycopy(messages, 0, mMessages, 0, numMessages);
+ mNumMessages = numMessages;
+ }
+
+ public NdefPushProtocol(byte[] data) throws FormatException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(data);
+ DataInputStream input = new DataInputStream(buffer);
+
+ // Check version of protocol
+ byte version;
+ try {
+ version = input.readByte();
+ } catch (java.io.IOException e) {
+ Log.w(TAG, "Unable to read version");
+ throw new FormatException("Unable to read version");
+ }
+
+ if(version != VERSION) {
+ Log.w(TAG, "Got version " + version + ", expected " + VERSION);
+ throw new FormatException("Got version " + version + ", expected " + VERSION);
+ }
+
+ // Read numMessages
+ try {
+ mNumMessages = input.readInt();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read numMessages");
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ if(mNumMessages == 0) {
+ Log.w(TAG, "No NdefMessage inside NdefMessageSet packet");
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+
+ // Read actions and messages
+ mActions = new byte[mNumMessages];
+ mMessages = new NdefMessage[mNumMessages];
+ for(int i = 0; i < mNumMessages; i++) {
+ // Read action
+ try {
+ mActions[i] = input.readByte();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read action for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ // Read message length
+ int length;
+ try {
+ length = input.readInt();
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read length for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ byte[] bytes = new byte[length];
+ // Read message
+ int lengthRead;
+ try {
+ lengthRead = input.read(bytes);
+ } catch(java.io.IOException e) {
+ Log.w(TAG, "Unable to read bytes for message " + i);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ if(length != lengthRead) {
+ Log.w(TAG, "Read " + lengthRead + " bytes but expected " +
+ length);
+ throw new FormatException("Error while parsing NdefMessageSet");
+ }
+ // Create and store NdefMessage
+ try {
+ mMessages[i] = new NdefMessage(bytes);
+ } catch(FormatException e) {
+ throw e;
+ }
+ }
+ }
+
+ public NdefMessage getImmediate() {
+ // Find and return the first immediate message
+ for(int i = 0; i < mNumMessages; i++) {
+ if(mActions[i] == ACTION_IMMEDIATE) {
+ return mMessages[i];
+ }
+ }
+ return null;
+ }
+
+ public byte[] toByteArray() {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+ DataOutputStream output = new DataOutputStream(buffer);
+
+ try {
+ output.writeByte(VERSION);
+ output.writeInt(mNumMessages);
+ for(int i = 0; i < mNumMessages; i++) {
+ output.writeByte(mActions[i]);
+ byte[] bytes = mMessages[i].toByteArray();
+ output.writeInt(bytes.length);
+ output.write(bytes);
+ }
+ } catch(java.io.IOException e) {
+ return null;
+ }
+
+ return buffer.toByteArray();
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java
new file mode 100755
index 0000000..bf92c17
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010 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.ndefpush;
+
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages
+ * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}.
+ */
+public class NdefPushServer {
+ private static final String TAG = "NdefPushServer";
+ private static final boolean DBG = true;
+
+ private static final int MIU = 248;
+
+ int mSap;
+
+ static final String SERVICE_NAME = "com.android.npp";
+
+ NfcService mService = NfcService.getInstance();
+
+ final Callback mCallback;
+
+ /** Protected by 'this', null when stopped, non-null when running */
+ ServerThread mServerThread = null;
+
+ public interface Callback {
+ void onMessageReceived(NdefMessage msg);
+ }
+
+ public NdefPushServer(final int sap, Callback callback) {
+ mSap = sap;
+ mCallback = callback;
+ }
+
+ /** Connection class, used to handle incoming connections */
+ private class ConnectionThread extends Thread {
+ private LlcpSocket mSock;
+
+ ConnectionThread(LlcpSocket sock) {
+ super(TAG);
+ mSock = sock;
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "starting connection thread");
+ try {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+ byte[] partial = new byte[1024];
+ int size;
+ boolean connectionBroken = false;
+
+ // Get raw data from remote server
+ while(!connectionBroken) {
+ try {
+ size = mSock.receive(partial);
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
+ if (size < 0) {
+ connectionBroken = true;
+ break;
+ } else {
+ buffer.write(partial, 0, size);
+ }
+ } catch (IOException e) {
+ // Connection broken
+ connectionBroken = true;
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
+ }
+ }
+
+ // Build NDEF message set from the stream
+ NdefPushProtocol msg = new NdefPushProtocol(buffer.toByteArray());
+ if (DBG) Log.d(TAG, "got message " + msg.toString());
+
+ // Send the intent for the fake tag
+ mCallback.onMessageReceived(msg.getImmediate());
+ } catch (FormatException e) {
+ Log.e(TAG, "badly formatted NDEF message, ignoring", e);
+ } finally {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ mSock.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ if (DBG) Log.d(TAG, "finished connection thread");
+ }
+ }
+
+ /** Server class, used to listen for incoming connection request */
+ class ServerThread extends Thread {
+ // Variables below synchronized on NdefPushServer.this
+ boolean mRunning = true;
+ LlcpServerSocket mServerSocket;
+
+ @Override
+ public void run() {
+ boolean threadRunning;
+ synchronized (NdefPushServer.this) {
+ threadRunning = mRunning;
+ }
+ while (threadRunning) {
+ if (DBG) Log.d(TAG, "about create LLCP service socket");
+ try {
+ synchronized (NdefPushServer.this) {
+ mServerSocket = mService.createLlcpServerSocket(mSap, SERVICE_NAME,
+ MIU, 1, 1024);
+ }
+ if (mServerSocket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
+ return;
+ }
+ if (DBG) Log.d(TAG, "created LLCP service socket");
+ synchronized (NdefPushServer.this) {
+ threadRunning = mRunning;
+ }
+
+ while (threadRunning) {
+ LlcpServerSocket serverSocket;
+ synchronized (NdefPushServer.this) {
+ serverSocket = mServerSocket;
+ }
+ if (serverSocket == null) return;
+
+ if (DBG) Log.d(TAG, "about to accept");
+ LlcpSocket communicationSocket = serverSocket.accept();
+ if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
+ if (communicationSocket != null) {
+ new ConnectionThread(communicationSocket).start();
+ }
+
+ synchronized (NdefPushServer.this) {
+ threadRunning = mRunning;
+ }
+ }
+ if (DBG) Log.d(TAG, "stop running");
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ } catch (IOException e) {
+ Log.e(TAG, "IO error", e);
+ } finally {
+ synchronized (NdefPushServer.this) {
+ if (mServerSocket != null) {
+ if (DBG) Log.d(TAG, "about to close");
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+
+ synchronized (NdefPushServer.this) {
+ threadRunning = mRunning;
+ }
+ }
+ }
+
+ public void shutdown() {
+ synchronized (NdefPushServer.this) {
+ mRunning = false;
+ if (mServerSocket != null) {
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+ }
+
+ public void start() {
+ synchronized (this) {
+ if (DBG) Log.d(TAG, "start, thread = " + mServerThread);
+ if (mServerThread == null) {
+ if (DBG) Log.d(TAG, "starting new server thread");
+ mServerThread = new ServerThread();
+ mServerThread.start();
+ }
+ }
+ }
+
+ public void stop() {
+ synchronized (this) {
+ if (DBG) Log.d(TAG, "stop, thread = " + mServerThread);
+ if (mServerThread != null) {
+ if (DBG) Log.d(TAG, "shuting down server thread");
+ mServerThread.shutdown();
+ mServerThread = null;
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/snep/SnepClient.java b/NfcSony/src/com/android/nfc/snep/SnepClient.java
new file mode 100644
index 0000000..03824cc
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/snep/SnepClient.java
@@ -0,0 +1,197 @@
+/*
+ * 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.snep;
+
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+
+import android.nfc.NdefMessage;
+import android.util.Log;
+
+import java.io.IOException;
+
+public final class SnepClient {
+ private static final String TAG = "SnepClient";
+ private static final boolean DBG = false;
+ private static final int DEFAULT_ACCEPTABLE_LENGTH = 100*1024;
+ private static final int DEFAULT_MIU = 128;
+ private static final int DEFAULT_RWSIZE = 1;
+ SnepMessenger mMessenger = null;
+ private final Object mTransmissionLock = new Object();
+
+ private final String mServiceName;
+ private final int mPort;
+ private int mState = DISCONNECTED;
+ private final int mAcceptableLength;
+ private final int mFragmentLength;
+ private final int mMiu;
+ private final int mRwSize;
+
+ private static final int DISCONNECTED = 0;
+ private static final int CONNECTING = 1;
+ private static final int CONNECTED = 2;
+
+ public SnepClient() {
+ mServiceName = SnepServer.DEFAULT_SERVICE_NAME;
+ mPort = SnepServer.DEFAULT_PORT;
+ mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH;
+ mFragmentLength = -1;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RWSIZE;
+ }
+
+ public SnepClient(String serviceName) {
+ mServiceName = serviceName;
+ mPort = -1;
+ mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH;
+ mFragmentLength = -1;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RWSIZE;
+ }
+
+ public SnepClient(int miu, int rwSize) {
+ mServiceName = SnepServer.DEFAULT_SERVICE_NAME;
+ mPort = SnepServer.DEFAULT_PORT;
+ mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH;
+ mFragmentLength = -1;
+ mMiu = miu;
+ mRwSize = rwSize;
+ }
+
+ SnepClient(String serviceName, int fragmentLength) {
+ mServiceName = serviceName;
+ mPort = -1;
+ mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH;
+ mFragmentLength = fragmentLength;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RWSIZE;
+ }
+
+ SnepClient(String serviceName, int acceptableLength, int fragmentLength) {
+ mServiceName = serviceName;
+ mPort = -1;
+ mAcceptableLength = acceptableLength;
+ mFragmentLength = fragmentLength;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RWSIZE;
+ }
+
+ public void put(NdefMessage msg) throws IOException {
+ SnepMessenger messenger;
+ synchronized (this) {
+ if (mState != CONNECTED) {
+ throw new IOException("Socket not connected.");
+ }
+ messenger = mMessenger;
+ }
+
+ synchronized (mTransmissionLock) {
+ try {
+ messenger.sendMessage(SnepMessage.getPutRequest(msg));
+ messenger.getMessage();
+ } catch (SnepException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ public SnepMessage get(NdefMessage msg) throws IOException {
+ SnepMessenger messenger;
+ synchronized (this) {
+ if (mState != CONNECTED) {
+ throw new IOException("Socket not connected.");
+ }
+ messenger = mMessenger;
+ }
+
+ synchronized (mTransmissionLock) {
+ try {
+ messenger.sendMessage(SnepMessage.getGetRequest(mAcceptableLength, msg));
+ return messenger.getMessage();
+ } catch (SnepException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ public void connect() throws IOException {
+ synchronized (this) {
+ if (mState != DISCONNECTED) {
+ throw new IOException("Socket already in use.");
+ }
+ mState = CONNECTING;
+ }
+
+ LlcpSocket socket = null;
+ SnepMessenger messenger;
+ try {
+ if (DBG) Log.d(TAG, "about to create socket");
+ // Connect to the snep server on the remote side
+ socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024);
+ if (socket == null) {
+ throw new IOException("Could not connect to socket.");
+ }
+ if (mPort == -1) {
+ if (DBG) Log.d(TAG, "about to connect to service " + mServiceName);
+ socket.connectToService(mServiceName);
+ } else {
+ if (DBG) Log.d(TAG, "about to connect to port " + mPort);
+ socket.connectToSap(mPort);
+ }
+ int miu = socket.getRemoteMiu();
+ int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength);
+ messenger = new SnepMessenger(true, socket, fragmentLength);
+ } catch (LlcpException e) {
+ synchronized (this) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Could not connect to socket");
+ } catch (IOException e) {
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e2) {
+ }
+ }
+ synchronized (this) {
+ mState = DISCONNECTED;
+ }
+ throw new IOException("Failed to connect to socket");
+ }
+
+ synchronized (this) {
+ mMessenger = messenger;
+ mState = CONNECTED;
+ }
+ }
+
+ public void close() {
+ synchronized (this) {
+ if (mMessenger != null) {
+ try {
+ mMessenger.close();
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ mMessenger = null;
+ mState = DISCONNECTED;
+ }
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/snep/SnepException.java b/NfcSony/src/com/android/nfc/snep/SnepException.java
new file mode 100644
index 0000000..b8b717e
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/snep/SnepException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.snep;
+
+public class SnepException extends Exception {
+
+ public SnepException(String message) {
+ super(message);
+ }
+
+ public SnepException(Exception cause) {
+ super(cause);
+ }
+
+ public SnepException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/snep/SnepMessage.java b/NfcSony/src/com/android/nfc/snep/SnepMessage.java
new file mode 100644
index 0000000..50996d0
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/snep/SnepMessage.java
@@ -0,0 +1,170 @@
+/*
+ * 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.snep;
+
+import android.nfc.FormatException;
+import android.nfc.NdefMessage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public final class SnepMessage {
+ public static final byte VERSION_MAJOR = (byte) 0x1;
+ public static final byte VERSION_MINOR = (byte) 0x0;
+ public static final byte VERSION = (0xF0 & (VERSION_MAJOR << 4)) | (0x0F & VERSION_MINOR);
+
+ public static final byte REQUEST_CONTINUE = (byte) 0x00;
+ public static final byte REQUEST_GET = (byte) 0x01;
+ public static final byte REQUEST_PUT = (byte) 0x02;
+ public static final byte REQUEST_REJECT = (byte) 0x7F;
+
+ public static final byte RESPONSE_CONTINUE = (byte) 0x80;
+ public static final byte RESPONSE_SUCCESS = (byte) 0x81;
+ public static final byte RESPONSE_NOT_FOUND = (byte) 0xC0;
+ public static final byte RESPONSE_EXCESS_DATA = (byte) 0xC1;
+ public static final byte RESPONSE_BAD_REQUEST = (byte) 0xC2;
+ public static final byte RESPONSE_NOT_IMPLEMENTED = (byte) 0xE0;
+ public static final byte RESPONSE_UNSUPPORTED_VERSION = (byte) 0xE1;
+ public static final byte RESPONSE_REJECT = (byte) 0xFF;
+
+ private static final int HEADER_LENGTH = 6;
+ private final byte mVersion;
+ private final byte mField;
+ private final int mLength;
+ private final int mAcceptableLength;
+ private final NdefMessage mNdefMessage;
+
+ public static SnepMessage getGetRequest(int acceptableLength, NdefMessage ndef) {
+ return new SnepMessage(VERSION, REQUEST_GET, 4 + ndef.toByteArray().length,
+ acceptableLength, ndef);
+ }
+
+ public static SnepMessage getPutRequest(NdefMessage ndef) {
+ return new SnepMessage(VERSION, REQUEST_PUT, ndef.toByteArray().length, 0, ndef);
+ }
+
+ public static SnepMessage getMessage(byte field) {
+ return new SnepMessage(VERSION, field, 0, 0, null);
+ }
+
+ public static SnepMessage getSuccessResponse(NdefMessage ndef) {
+ if (ndef == null) {
+ return new SnepMessage(VERSION, RESPONSE_SUCCESS, 0, 0, null);
+ } else {
+ return new SnepMessage(VERSION, RESPONSE_SUCCESS, ndef.toByteArray().length, 0, ndef);
+ }
+ }
+
+ public static SnepMessage fromByteArray(byte[] data) throws FormatException {
+ return new SnepMessage(data);
+ }
+
+ private SnepMessage(byte[] data) throws FormatException {
+ ByteBuffer input = ByteBuffer.wrap(data);
+ int ndefOffset;
+ int ndefLength;
+
+ mVersion = input.get();
+ mField = input.get();
+ mLength = input.getInt();
+ if (mField == REQUEST_GET) {
+ mAcceptableLength = input.getInt();
+ ndefOffset = HEADER_LENGTH + 4;
+ ndefLength = mLength - 4;
+ } else {
+ mAcceptableLength = -1;
+ ndefOffset = HEADER_LENGTH;
+ ndefLength = mLength;
+ }
+
+ if (ndefLength > 0) {
+ byte[] bytes = new byte[ndefLength];
+ System.arraycopy(data, ndefOffset, bytes, 0, ndefLength);
+ mNdefMessage = new NdefMessage(bytes);
+ } else {
+ mNdefMessage = null;
+ }
+ }
+
+ SnepMessage(byte version, byte field, int length, int acceptableLength,
+ NdefMessage ndefMessage) {
+ mVersion = version;
+ mField = field;
+ mLength = length;
+ mAcceptableLength = acceptableLength;
+ mNdefMessage = ndefMessage;
+ }
+
+ public byte[] toByteArray() {
+ byte[] bytes;
+ if (mNdefMessage != null) {
+ bytes = mNdefMessage.toByteArray();
+ } else {
+ bytes = new byte[0];
+ }
+
+ ByteArrayOutputStream buffer;
+ try {
+ if (mField == REQUEST_GET) {
+ buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH + 4);
+ } else {
+ buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH);
+ }
+
+ DataOutputStream output = new DataOutputStream(buffer);
+ output.writeByte(mVersion);
+ output.writeByte(mField);
+ if (mField == REQUEST_GET) {
+ output.writeInt(bytes.length + 4);
+ output.writeInt(mAcceptableLength);
+ } else {
+ output.writeInt(bytes.length);
+ }
+ output.write(bytes);
+ } catch(IOException e) {
+ return null;
+ }
+
+ return buffer.toByteArray();
+ }
+
+ public NdefMessage getNdefMessage() {
+ return mNdefMessage;
+ }
+
+ public byte getField() {
+ return mField;
+ }
+
+ public byte getVersion() {
+ return mVersion;
+ }
+
+ public int getLength() {
+ return mLength;
+ }
+
+ public int getAcceptableLength() {
+ if (mField != REQUEST_GET) {
+ throw new UnsupportedOperationException(
+ "Acceptable Length only available on get request messages.");
+ }
+ return mAcceptableLength;
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/snep/SnepMessenger.java b/NfcSony/src/com/android/nfc/snep/SnepMessenger.java
new file mode 100644
index 0000000..97ac1f0
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/snep/SnepMessenger.java
@@ -0,0 +1,190 @@
+/*
+ * 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.snep;
+
+import com.android.nfc.DeviceHost.LlcpSocket;
+
+import android.nfc.FormatException;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+public class SnepMessenger {
+ private static final String TAG = "SnepMessager";
+ private static final boolean DBG = false;
+ private static final int HEADER_LENGTH = 6;
+ final LlcpSocket mSocket;
+ final int mFragmentLength;
+ final boolean mIsClient;
+
+ public SnepMessenger(boolean isClient, LlcpSocket socket, int fragmentLength) {
+ mSocket = socket;
+ mFragmentLength = fragmentLength;
+ mIsClient = isClient;
+ }
+
+ public void sendMessage(SnepMessage msg) throws IOException {
+ byte[] buffer = msg.toByteArray();
+ byte remoteContinue;
+ if (mIsClient) {
+ remoteContinue = SnepMessage.RESPONSE_CONTINUE;
+ } else {
+ remoteContinue = SnepMessage.REQUEST_CONTINUE;
+ }
+ if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message");
+
+ // Send first fragment
+ int length = Math.min(buffer.length, mFragmentLength);
+ byte[] tmpBuffer = Arrays.copyOfRange(buffer, 0, length);
+ if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
+ mSocket.send(tmpBuffer);
+
+ if (length == buffer.length) {
+ return;
+ }
+
+ // Look for Continue or Reject from peer.
+ int offset = length;
+ byte[] responseBytes = new byte[HEADER_LENGTH];
+ mSocket.receive(responseBytes);
+ SnepMessage snepResponse;
+ try {
+ snepResponse = SnepMessage.fromByteArray(responseBytes);
+ } catch (FormatException e) {
+ throw new IOException("Invalid SNEP message", e);
+ }
+
+ if (DBG) Log.d(TAG, "Got response from first fragment: " + snepResponse.getField());
+ if (snepResponse.getField() != remoteContinue) {
+ throw new IOException("Invalid response from server (" +
+ snepResponse.getField() + ")");
+ }
+
+ // Send remaining fragments.
+ while (offset < buffer.length) {
+ length = Math.min(buffer.length - offset, mFragmentLength);
+ tmpBuffer = Arrays.copyOfRange(buffer, offset, offset + length);
+ if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment");
+ mSocket.send(tmpBuffer);
+ offset += length;
+ }
+ }
+
+ public SnepMessage getMessage() throws IOException, SnepException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream(mFragmentLength);
+ byte[] partial = new byte[mFragmentLength];
+ int size;
+ int requestSize = 0;
+ int readSize = 0;
+ byte requestVersion = 0;
+ boolean doneReading = false;
+ byte fieldContinue;
+ byte fieldReject;
+ if (mIsClient) {
+ fieldContinue = SnepMessage.REQUEST_CONTINUE;
+ fieldReject = SnepMessage.REQUEST_REJECT;
+ } else {
+ fieldContinue = SnepMessage.RESPONSE_CONTINUE;
+ fieldReject = SnepMessage.RESPONSE_REJECT;
+ }
+
+ size = mSocket.receive(partial);
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
+ if (size < 0) {
+ try {
+ mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
+ } catch (IOException e) {
+ // Ignore
+ }
+ throw new IOException("Error reading SNEP message.");
+ } else if (size < HEADER_LENGTH) {
+ try {
+ mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
+ } catch (IOException e) {
+ // Ignore
+ }
+ throw new IOException("Invalid fragment from sender.");
+ } else {
+ readSize = size - HEADER_LENGTH;
+ buffer.write(partial, 0, size);
+ }
+
+ DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(partial));
+ requestVersion = dataIn.readByte();
+ byte requestField = dataIn.readByte();
+ requestSize = dataIn.readInt();
+
+ if (DBG) Log.d(TAG, "read " + readSize + " of " + requestSize);
+
+ if (((requestVersion & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
+ // Invalid protocol version; treat message as complete.
+ return new SnepMessage(requestVersion, requestField, 0, 0, null);
+ }
+
+ if (requestSize > readSize) {
+ if (DBG) Log.d(TAG, "requesting continuation");
+ mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray());
+ } else {
+ doneReading = true;
+ }
+
+ // Remaining fragments
+ while (!doneReading) {
+ try {
+ size = mSocket.receive(partial);
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
+ if (size < 0) {
+ try {
+ mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
+ } catch (IOException e) {
+ // Ignore
+ }
+ throw new IOException();
+ } else {
+ readSize += size;
+ buffer.write(partial, 0, size);
+ if (readSize == requestSize) {
+ doneReading = true;
+ }
+ }
+ } catch (IOException e) {
+ try {
+ mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray());
+ } catch (IOException e2) {
+ // Ignore
+ }
+ throw e;
+ }
+ }
+
+ // Build NDEF message set from the stream
+ try {
+ return SnepMessage.fromByteArray(buffer.toByteArray());
+ } catch (FormatException e) {
+ Log.e(TAG, "Badly formatted NDEF message, ignoring", e);
+ throw new SnepException(e);
+ }
+ }
+
+ public void close() throws IOException {
+ mSocket.close();
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/snep/SnepServer.java b/NfcSony/src/com/android/nfc/snep/SnepServer.java
new file mode 100644
index 0000000..866ed32
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/snep/SnepServer.java
@@ -0,0 +1,286 @@
+/*
+ * 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.snep;
+
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.NfcService;
+
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages
+ * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}.
+ */
+public final class SnepServer {
+ private static final String TAG = "SnepServer";
+ private static final boolean DBG = false;
+ private static final int DEFAULT_MIU = 248;
+ private static final int DEFAULT_RW_SIZE = 1;
+
+ public static final int DEFAULT_PORT = 4;
+
+ public static final String DEFAULT_SERVICE_NAME = "urn:nfc:sn:snep";
+
+ final Callback mCallback;
+ final String mServiceName;
+ final int mServiceSap;
+ final int mFragmentLength;
+ final int mMiu;
+ final int mRwSize;
+
+ /** Protected by 'this', null when stopped, non-null when running */
+ ServerThread mServerThread = null;
+ boolean mServerRunning = false;
+
+ public interface Callback {
+ public SnepMessage doPut(NdefMessage msg);
+ public SnepMessage doGet(int acceptableLength, NdefMessage msg);
+ }
+
+ public SnepServer(Callback callback) {
+ mCallback = callback;
+ mServiceName = DEFAULT_SERVICE_NAME;
+ mServiceSap = DEFAULT_PORT;
+ mFragmentLength = -1;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RW_SIZE;
+ }
+
+ public SnepServer(String serviceName, int serviceSap, Callback callback) {
+ mCallback = callback;
+ mServiceName = serviceName;
+ mServiceSap = serviceSap;
+ mFragmentLength = -1;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RW_SIZE;
+ }
+
+ public SnepServer(Callback callback, int miu, int rwSize) {
+ mCallback = callback;
+ mServiceName = DEFAULT_SERVICE_NAME;
+ mServiceSap = DEFAULT_PORT;
+ mFragmentLength = -1;
+ mMiu = miu;
+ mRwSize = rwSize;
+ }
+
+ SnepServer(String serviceName, int serviceSap, int fragmentLength, Callback callback) {
+ mCallback = callback;
+ mServiceName = serviceName;
+ mServiceSap = serviceSap;
+ mFragmentLength = fragmentLength;
+ mMiu = DEFAULT_MIU;
+ mRwSize = DEFAULT_RW_SIZE;
+ }
+
+ /** Connection class, used to handle incoming connections */
+ private class ConnectionThread extends Thread {
+ private final LlcpSocket mSock;
+ private final SnepMessenger mMessager;
+
+ ConnectionThread(LlcpSocket socket, int fragmentLength) {
+ super(TAG);
+ mSock = socket;
+ mMessager = new SnepMessenger(false, socket, fragmentLength);
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "starting connection thread");
+ try {
+ boolean running;
+ synchronized (SnepServer.this) {
+ running = mServerRunning;
+ }
+
+ while (running) {
+ if (!handleRequest(mMessager, mCallback)) {
+ break;
+ }
+
+ synchronized (SnepServer.this) {
+ running = mServerRunning;
+ }
+ }
+ } catch (IOException e) {
+ if (DBG) Log.e(TAG, "Closing from IOException");
+ } finally {
+ try {
+ if (DBG) Log.d(TAG, "about to close");
+ mSock.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ if (DBG) Log.d(TAG, "finished connection thread");
+ }
+ }
+
+ static boolean handleRequest(SnepMessenger messenger, Callback callback) throws IOException {
+ SnepMessage request;
+ try {
+ request = messenger.getMessage();
+ } catch (SnepException e) {
+ if (DBG) Log.w(TAG, "Bad snep message", e);
+ try {
+ messenger.sendMessage(SnepMessage.getMessage(
+ SnepMessage.RESPONSE_BAD_REQUEST));
+ } catch (IOException e2) {
+ // Ignore
+ }
+ return false;
+ }
+
+ if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
+ messenger.sendMessage(SnepMessage.getMessage(
+ SnepMessage.RESPONSE_UNSUPPORTED_VERSION));
+ } else if (request.getField() == SnepMessage.REQUEST_GET) {
+ messenger.sendMessage(callback.doGet(request.getAcceptableLength(),
+ request.getNdefMessage()));
+ } else if (request.getField() == SnepMessage.REQUEST_PUT) {
+ if (DBG) Log.d(TAG, "putting message " + request.toString());
+ messenger.sendMessage(callback.doPut(request.getNdefMessage()));
+ } else {
+ if (DBG) Log.d(TAG, "Unknown request (" + request.getField() +")");
+ messenger.sendMessage(SnepMessage.getMessage(
+ SnepMessage.RESPONSE_BAD_REQUEST));
+ }
+ return true;
+ }
+
+ /** Server class, used to listen for incoming connection request */
+ class ServerThread extends Thread {
+ private boolean mThreadRunning = true;
+ LlcpServerSocket mServerSocket;
+
+ @Override
+ public void run() {
+ boolean threadRunning;
+ synchronized (SnepServer.this) {
+ threadRunning = mThreadRunning;
+ }
+
+ while (threadRunning) {
+ if (DBG) Log.d(TAG, "about create LLCP service socket");
+ try {
+ synchronized (SnepServer.this) {
+ mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap,
+ mServiceName, mMiu, mRwSize, 1024);
+ }
+ if (mServerSocket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
+ return;
+ }
+ if (DBG) Log.d(TAG, "created LLCP service socket");
+ synchronized (SnepServer.this) {
+ threadRunning = mThreadRunning;
+ }
+
+ while (threadRunning) {
+ LlcpServerSocket serverSocket;
+ synchronized (SnepServer.this) {
+ serverSocket = mServerSocket;
+ }
+
+ if (serverSocket == null) {
+ if (DBG) Log.d(TAG, "Server socket shut down.");
+ return;
+ }
+ if (DBG) Log.d(TAG, "about to accept");
+ LlcpSocket communicationSocket = serverSocket.accept();
+ if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
+ if (communicationSocket != null) {
+ int fragmentLength = (mFragmentLength == -1) ?
+ mMiu : Math.min(mMiu, mFragmentLength);
+ new ConnectionThread(communicationSocket, fragmentLength).start();
+ }
+
+ synchronized (SnepServer.this) {
+ threadRunning = mThreadRunning;
+ }
+ }
+ if (DBG) Log.d(TAG, "stop running");
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ } catch (IOException e) {
+ Log.e(TAG, "IO error", e);
+ } finally {
+ synchronized (SnepServer.this) {
+ if (mServerSocket != null) {
+ if (DBG) Log.d(TAG, "about to close");
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+
+ synchronized (SnepServer.this) {
+ threadRunning = mThreadRunning;
+ }
+ }
+ }
+
+ public void shutdown() {
+ synchronized (SnepServer.this) {
+ mThreadRunning = false;
+ if (mServerSocket != null) {
+ try {
+ mServerSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ mServerSocket = null;
+ }
+ }
+ }
+ }
+
+ public void start() {
+ synchronized (SnepServer.this) {
+ if (DBG) Log.d(TAG, "start, thread = " + mServerThread);
+ if (mServerThread == null) {
+ if (DBG) Log.d(TAG, "starting new server thread");
+ mServerThread = new ServerThread();
+ mServerThread.start();
+ mServerRunning = true;
+ }
+ }
+ }
+
+ public void stop() {
+ synchronized (SnepServer.this) {
+ if (DBG) Log.d(TAG, "stop, thread = " + mServerThread);
+ if (mServerThread != null) {
+ if (DBG) Log.d(TAG, "shuting down server thread");
+ mServerThread.shutdown();
+ mServerThread = null;
+ mServerRunning = false;
+ }
+ }
+ }
+}
diff --git a/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java b/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java
new file mode 100644
index 0000000..dfe1611
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2014 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.sony;
+
+import android.util.Log;
+
+public class NativeNfcSetting {
+ private static final String TAG = "NativeNfcSetting";
+
+ private static final byte BIT_0 = 1;
+ private static final byte BIT_1 = 2;
+ private static final byte BIT_2 = 4;
+ private static final byte BIT_3 = 8;
+ private static final byte BIT_4 = 16;
+ private static final byte BIT_5 = 32;
+ private static final byte BIT_ALL = 15;
+ private static final byte BIT_OFF = 0;
+ private static final boolean DBG = false;
+ private static final int INT_DEFAULT = 0;
+ private static final int INT_TYPE_A = 1;
+ private static final int INT_TYPE_ALL = 4;
+ private static final int INT_TYPE_F212 = 2;
+ private static final int INT_TYPE_F424 = 3;
+ private static final byte RF_REG_6 = 32;
+ private static final byte RF_REG_7 = 36;
+ private static final byte RF_REG_8 = 52;
+ private static final int TGT_DEFAULT = 0;
+ private static final int TGT_TYPE_ALL = 3;
+ private static final int TGT_TYPE_A_WAIT = 1;
+ private static final int TGT_TYPE_F_WAIT = 2;
+
+ private byte mAutoPollOffListen;
+ private byte mListenChangeSwitch;
+ private int mListenTime;
+ private int[] mPollGapListenTime;
+ private int[] mPollTimeRfOnToPoll;
+ private byte mPolloingChangeSwitch;
+ private int mReg6Data;
+ private int mReg7Data;
+ private int mReg8Data;
+ private int mReg14Data;
+ private int mReg15Data;
+ private byte mRegAddress;
+ private byte mSelectPollType;
+ private byte mSpdTime;
+ private byte mTypeAWait;
+ private boolean mTypeBWait;
+ private byte mTypeFWait;
+
+ public NativeNfcSetting() {
+ initPollingParam();
+ initListenParam();
+ initRfParam();
+ }
+
+ private native int nativeSetListenParameter(byte listenchangeSwitch, int[] pollGapListenTime,
+ int listenTime, byte typeAWait, byte typeFWait, boolean typeBWait, byte autoPollOffListen);
+ private boolean changeListenParam() {
+ int ret = nativeSetListenParameter(mListenChangeSwitch, mPollGapListenTime,
+ mListenTime, mTypeAWait, mTypeFWait, mTypeBWait, mAutoPollOffListen);
+ if (ret != 0) {
+ Log.e("NativeNfcSetting", "Error!!! nativeSetListenParameter() return = " + ret);
+ return false;
+ }
+ return true;
+ }
+
+ private native int nativeSetPollParameter(byte polloingChangeSwitch, byte selectPollType, int[] pollTimeRfOnToPoll);
+ private boolean changePollParam() {
+ int ret = nativeSetPollParameter(this.mPolloingChangeSwitch, this.mSelectPollType, this.mPollTimeRfOnToPoll);
+ if (ret != 0) {
+ Log.e("NativeNfcSetting", "Error!!! nativeSetPollParameter() return = " + ret);
+ return false;
+ }
+ return true;
+ }
+
+ private native int nativeRfParameter(byte[] param);
+ private boolean changeRfParam() {
+ return nativeRfParameter(concat(new byte[][] {
+ createRegData((byte)32, mReg6Data),
+ createRegData((byte)36, mReg7Data),
+ createRegData((byte)52, mReg8Data),
+ createRegData((byte)48, mReg14Data),
+ createRegData((byte) 5, mReg15Data), })) == 0;
+ }
+
+ private byte[] concat(byte[]... arrays) {
+ int len = 0;
+ for (int i = 0; i < arrays.length; i++) {
+ len += arrays[i].length;
+ }
+ byte[] result = new byte[len];
+ int pos = 0;
+ for (int i = 0; i < arrays.length; i++) {
+ byte[] array = arrays[i];
+ System.arraycopy(array, 0, result, pos, array.length);
+ pos += array.length;
+ }
+ return result;
+ }
+
+ private byte[] createRegData(byte address, int value) {
+ Log.i(TAG, "call: createRegData address=" + address + ", value=" + value);
+ byte[] regData = new byte[6];
+ regData[0] = 0;
+ regData[1] = address;
+ regData[2] = ((byte)(0xFF & value >>> 24));
+ regData[3] = ((byte)(0xFF & value >>> 16));
+ regData[4] = ((byte)(0xFF & value >>> 8));
+ regData[5] = ((byte)(0xFF & value >>> 0));
+ return regData;
+ }
+
+ private void initListenParam() {
+ Log.i(TAG, "call: initListenParam");
+ mListenChangeSwitch = 0;
+ mPollGapListenTime = new int[3];
+ mPollGapListenTime[0] = 10;
+ mPollGapListenTime[1] = 10;
+ mPollGapListenTime[2] = 10;
+ mListenTime = 15;
+ mTypeAWait = 4;
+ mTypeFWait = 0;
+ mTypeBWait = true;
+ mAutoPollOffListen = 7;
+ }
+
+ private void initPollingParam() {
+ Log.i(TAG, "call: initPollingParam");
+ mPolloingChangeSwitch = 0;
+ mSelectPollType = 15;
+ mPollTimeRfOnToPoll = new int[4];
+ mPollTimeRfOnToPoll[0] = 6;
+ mPollTimeRfOnToPoll[1] = 6;
+ mPollTimeRfOnToPoll[2] = 21;
+ mPollTimeRfOnToPoll[3] = 21;
+ }
+
+ private void initRfParam() {
+ Log.i(TAG, "call: initRfParam");
+ mRegAddress = 0;
+ mReg6Data = 0x40c043f;
+ mReg7Data = 0x143f133f;
+ mReg8Data = 0x60000000;
+ mReg14Data = 0x1f280000;
+ mReg15Data = 0x28080000;
+ }
+
+ private native boolean nativeClearCIDSupport();
+
+ private native boolean nativeSetCIDSupport();
+
+ private boolean setListenSetting(byte switchBit, int index, int value) {
+ Log.i(TAG, "call: setListenSetting switchBit=" + switchBit + ", index=" + index + ", value=" + value);
+ mListenChangeSwitch = ((byte)(switchBit | mListenChangeSwitch));
+ switch (index) {
+ case 8:
+ mPollGapListenTime[0] = value;
+ return true;
+ case 9:
+ mPollGapListenTime[1] = value;
+ return true;
+ case 10:
+ mPollGapListenTime[2] = value;
+ return true;
+ case 11:
+ mListenTime = value;
+ return true;
+ case 12:
+ mTypeAWait = ((byte)value);
+ return true;
+ case 13:
+ mTypeFWait = ((byte)value);
+ return true;
+ case 14:
+ if (value != 0) {
+ mTypeBWait = true;
+ return true;
+ }
+ mTypeBWait = false;
+ return true;
+ default:
+ mAutoPollOffListen = ((byte)value);
+ return true;
+ }
+ }
+
+ private boolean setPollSetting(byte switchBit, int index, int value) {
+ Log.i(TAG, "call: setPollSetting switchBit=" + switchBit + ", index=" + index + ", value=" + value);
+ mPolloingChangeSwitch = ((byte)(switchBit | mPolloingChangeSwitch));
+ switch (index) {
+ case 0:
+ if (value != 0) {
+ this.mSelectPollType = ((byte)(0x1 | this.mSelectPollType));
+ return true;
+ }
+ this.mSelectPollType = ((byte)(0xFFFFFFFE & this.mSelectPollType));
+ return true;
+ case 1:
+ if (value != 0) {
+ this.mSelectPollType = ((byte)(0x2 | this.mSelectPollType));
+ return true;
+ }
+ this.mSelectPollType = ((byte)(0xFFFFFFFD & this.mSelectPollType));
+ return true;
+ case 2:
+ if (value != 0) {
+ this.mSelectPollType = ((byte)(0x4 | this.mSelectPollType));
+ return true;
+ }
+ this.mSelectPollType = ((byte)(0xFFFFFFFB & this.mSelectPollType));
+ return true;
+ case 3:
+ if (value != 0) {
+ this.mSelectPollType = ((byte)(0x8 | this.mSelectPollType));
+ return true;
+ }
+ this.mSelectPollType = ((byte)(0xFFFFFFF7 & this.mSelectPollType));
+ return true;
+ case 4:
+ this.mPollTimeRfOnToPoll[0] = value;
+ return true;
+ case 5:
+ this.mPollTimeRfOnToPoll[1] = value;
+ return true;
+ case 6:
+ this.mPollTimeRfOnToPoll[2] = value;
+ return true;
+ default:
+ mPollTimeRfOnToPoll[3] = value;
+ return true;
+ }
+ }
+
+ private boolean setRfSetting(byte address, int index, int value) {
+ Log.i(TAG, "call: setRfSetting address=" + address + ", index=" + index + " value=" + value);
+ // TODO
+ return true;
+ }
+
+ public boolean changeParameter(int target) {
+ Log.i(TAG, "call: changeParameter target=" + target);
+ if (target == 1) {
+ return changePollParam();
+ } else if (target == 2) {
+ return changeListenParam();
+ } else if (target == 3) {
+ return changeRfParam();
+ }
+ return false;
+ }
+
+ public void setP2pModes(int initiatorModes, int targetModes) {
+ Log.i(TAG, "call: setP2pModes initiatorModes=" + initiatorModes + ", targetModes=" + targetModes);
+ initPollingParam();
+ initListenParam();
+ }
+
+ public boolean setParameter(int index, int value) {
+ Log.i(TAG, "call: setParameter index=" + index + ", value=" + value);
+ // TODO
+ setPollSetting((byte)1, index, value);
+ setPollSetting((byte)2, index, value);
+ setListenSetting((byte)1, index, value);
+ setListenSetting((byte)2, index, value);
+ setListenSetting((byte)4, index, value);
+ setListenSetting((byte)8, index, value);
+ setListenSetting((byte)16, index, value);
+ setListenSetting((byte)32, index, value);
+ setRfSetting((byte)32, index, value);
+ setRfSetting((byte)36, index, value);
+ setRfSetting((byte)52, index, value);
+ setRfSetting((byte)40, index, value);
+ setRfSetting((byte)48, index, value);
+ return true;
+ }
+}
+
diff --git a/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java b/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java
new file mode 100644
index 0000000..a1ff10f
--- /dev/null
+++ b/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.sony;
+
+//import android.nfc.INfcUtilityCallback;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+
+public class NativeNfcUtility {
+ private static final String TAG = "NativeNfcUtility";
+
+ private static final boolean DBG = false;
+ private static final int MSG_WAIT_SIM_BOOT = 1;
+ private static boolean mIsLock = false;
+ //private static INfcUtilityCallback mStaticCallback;
+ final NativeNfcUtilityHandler mHandler = new NativeNfcUtilityHandler();
+ private native int nativeWaitSimBoot(boolean paramBoolean);
+
+ void startWaitSimBoot() {
+ nativeWaitSimBoot(mIsLock);
+// if (mStaticCallback != null) {
+// try {
+// mStaticCallback.SimBootComplete();
+// } catch (RemoteException localRemoteException) {
+// e.printStackTrace();
+// }
+ }
+
+// public boolean waitSimBoot(INfcUtilityCallback callback, boolean isLock) {
+// mIsLock = isLock;
+// mStaticCallback = callback;
+// this.mHandler.sendEmptyMessage(1);
+// return true;
+// }
+
+ final class NativeNfcUtilityHandler extends Handler {
+ NativeNfcUtilityHandler() {
+ }
+
+ public void handleMessage(Message paramMessage) {
+ NativeNfcUtility.this.startWaitSimBoot();
+ }
+ }
+}
+
diff --git a/configs/libnfc-brcm.conf b/configs/libnfc-brcm.conf
index bb754fb..8e245ce 100644
--- a/configs/libnfc-brcm.conf
+++ b/configs/libnfc-brcm.conf
@@ -1,21 +1,10 @@
+## this file is used by Broadcom's Hardware Abstraction Layer at external/libnfc-nci/halimpl/
+
###############################################################################
# Application options
-APPL_TRACE_LEVEL=0xFF
+APPL_TRACE_LEVEL=0x02
PROTOCOL_TRACE_LEVEL=0xFFFFFFFF
-###############################################################################
-# Logging
-# Set USE_RAW_NCI_TRACE = 1 for protocol logging in a raw format
-# instead of decoded
-# Set LOG_TO_FILE = 1 to log protocol traces to a file on the sdcard
-# instead of to adb logcat
-# Set LOGCAT_FILTER to the filter string which is passed to logcat
-#USE_RAW_NCI_TRACE=0
-#LOG_TO_FILE=0
-#LOGCAT_FILTER="BrcmNciX:V BrcmNciR:V BrcmNote:V *:S"
-
-PRESERVE_STORAGE=1
-
###############################################################################
# performance measurement
# Change this setting to control how often USERIAL log the performance (throughput)
@@ -25,26 +14,23 @@ PRESERVE_STORAGE=1
###############################################################################
# File used for NFA storage
-NFA_STORAGE="/data/bcmnfc"
+NFA_STORAGE="/data/nfc"
###############################################################################
-# Low Power Mode Settings
+# Snooze Mode Settings
+#
+# By default snooze mode is enabled. Set SNOOZE_MODE_CFG byte[0] to 0
+# to disable.
#
-# If NFA_DM_LP_CFG is not provided, stack default settings are
-# used (see nfa_dm_brcm_cfg.c). They are as follows:
-# 1 Power cycle to full power mode from CEx
-# 5 Parameter for low power mode command
-# 0 Primary Threshold for battery monitor
-# 0-7 representing below voltages:
-# {2, 2.2, 2.7, 2.8, 2.9, 3, 3.1, 3.2}
-# 8 Secondary Threshold for battery monitor
-# 0-15 representing below voltages:
-# {5.2, 4.87, 4.54, 4.22, 3.9, 3.73, 3.57, 3.4,
-# 3.2, 3.1, 3.0, 2.9, 2.8, 2.7, 2.2, 2.0}
+# If SNOOZE_MODE_CFG is not provided, the default settings are used:
+# They are as follows:
+# 8 Sleep Mode (0=Disabled 1=UART 8=SPI/I2C)
+# 0 Idle Threshold Host
+# 0 Idle Threshold HC
+# 0 NFC Wake active mode (0=ActiveLow 1=ActiveHigh)
+# 1 Host Wake active mode (0=ActiveLow 1=ActiveHigh)
#
-#NFA_DM_LP_CFG={01:05:00:08}
-# LPM Disable FW VBAT MON
-NFA_DM_LP_CFG={01:01:00:08}
+#SNOOZE_MODE_CFG={08:00:00:00:01}
###############################################################################
# Insert a delay in milliseconds after NFC_WAKE and before write to NFCC
@@ -58,30 +44,35 @@ NFC_WAKE_DELAY=20
# Delay after deasserting NFC-Wake before turn off chip (default 0)
# POST_POWER_OFF_DELAY
# Delay after turning off chip, before USERIAL_close returns (default 0)
-# CE3_PRE_POWER_OFF_DELAY
-# Delay after deasserting NFC-Wake before turn off chip (default 1000)
-# when going to CE3 Switch Off mode
#
#POWER_ON_DELAY=300
-PRE_POWER_OFF_DELAY=1500
+#PRE_POWER_OFF_DELAY=0
#POST_POWER_OFF_DELAY=0
-#CE3_PRE_POWER_OFF_DELAY=1000
+###############################################################################
+# LPTD mode configuration
+# byte[0] is the length of the remaining bytes in this value
+# if set to 0, LPTD params will NOT be sent to NFCC (i.e. disabled).
+# byte[1] is the param id it should be set to B9.
+# byte[2] is the length of the LPTD parameters
+# byte[3] indicates if LPTD is enabled
+# if set to 0, LPTD will be disabled (parameters will still be sent).
+# byte[4-n] are the LPTD parameters.
+# By default, LPTD is enabled and default settings are used.
+# See nfc_hal_dm_cfg.c for defaults
+LPTD_CFG={23:B9:21:01:02:FF:FF:04:A0:0F:40:00:80:02:02:10:00:00:00:31:0C:30:00:00:00:00:00:00:00:00:00:00:00:00:00:00}
###############################################################################
-# Device Manager Config
+# Startup Configuration (100 bytes maximum)
#
-# If NFA_DM_CFG is not provided, stack default settings are
-# used (see nfa_dm_cfg.c). They are as follows:
-# 0 (FALSE) Automatic NDEF detection when background polling
-# 0 (FALSE) Automatic NDEF read when background polling
+# For the 0xCA parameter, byte[9] (marked by 'AA') is for UICC0, and byte[10] (marked by BB) is
+# for UICC1. The values are defined as:
+# 0 : UICCx only supports ISO_DEP in low power mode.
+# 2 : UICCx only supports Mifare in low power mode.
+# 3 : UICCx supports both ISO_DEP and Mifare in low power mode.
#
-#NFA_DM_CFG={00:00}
-
-###############################################################################
-# Default poll duration (in ms)
-# The defualt is 500ms if not set (see nfc_target.h) same as M0
-NFA_DM_DISC_DURATION_POLL=500
+# AA BB
+NFA_DM_START_UP_CFG={1F:CB:01:01:A5:01:01:CA:14:00:00:00:00:06:E8:03:00:00:00:00:00:00:00:00:00:00:00:00:00:80:01:01}
###############################################################################
# Startup Vendor Specific Configuration (100 bytes maximum);
@@ -93,39 +84,44 @@ NFA_DM_DISC_DURATION_POLL=500
# byte[5] 0=turn off SWP frame logging; 1=turn on
# NFA_DM_START_UP_VSC_CFG={05:2F:09:02:01:01}
-#HW FSM
-#NFA_DM_START_UP_VSC_CFG={04:2F:06:01:00}
+###############################################################################
+# Antenna Configuration - This data is used when setting 0xC8 config item
+# at startup (before discovery is started). If not used, no value is sent.
+#
+# The settings for this value are documented here:
+# http://wcgbu.broadcom.com/wpan/PM/Project%20Document%20Library/bcm20791B0/
+# Design/Doc/PHY%20register%20settings/BCM20791-B2-1027-02_PHY_Recommended_Reg_Settings.xlsx
+# This document is maintained by Paul Forshaw.
+#
+# The values marked as ?? should be tweaked per antenna or customer/app:
+# {20:C8:1E:06:??:00:??:??:??:00:??:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:??:01:00:00:40:04}
+# array[0] = 0x20 is length of the payload from array[1] to the end
+# array[1] = 0xC8 is PREINIT_DSP_CFG
+#PREINIT_DSP_CFG={20:C8:1E:06:1F:00:0F:03:3C:00:04:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:48:01:00:00:40:04}
###############################################################################
# Configure crystal frequency when internal LPO can't detect the frequency.
#XTAL_FREQUENCY=0
-
-###############################################################################
-# Use Nexus S NXP RC work to allow our stack/firmware to work with a retail
-# Nexus S that causes IOP issues. Note, this will not pass conformance and
-# should be removed for production.
-#USE_NXP_P2P_RC_WORKAROUND=1
-
###############################################################################
# Configure the default Destination Gate used by HCI (the default is 4, which
# is the ETSI loopback gate.
-#NFA_HCI_DEFAULT_DEST_GATE=0x04
+NFA_HCI_DEFAULT_DEST_GATE=0xF0
###############################################################################
# Configure the single default SE to use. The default is to use the first
# SE that is detected by the stack. This value might be used when the phone
# supports multiple SE (e.g. 0xF3 and 0xF4) but you want to force it to use
# one of them (e.g. 0xF4).
-ACTIVE_SE=0xF4
+#ACTIVE_SE=0xF3
###############################################################################
# Configure the NFC Extras to open and use a static pipe. If the value is
# not set or set to 0, then the default is use a dynamic pipe based on a
# destination gate (see NFA_HCI_DEFAULT_DEST_GATE). Note there is a value
# for each UICC (where F3="UICC0" and F4="UICC1")
-NFA_HCI_STATIC_PIPE_ID_F3=0x71
-NFA_HCI_STATIC_PIPE_ID_F4=0x71
-
+#NFA_HCI_STATIC_PIPE_ID_F3=0x70
+#NFA_HCI_STATIC_PIPE_ID_01=0x19
+NFA_HCI_STATIC_PIPE_ID_C0=0x19
###############################################################################
# When disconnecting from Oberthur secure element, perform a warm-reset of
# the secure element to deselect the applet.
@@ -136,41 +132,26 @@ OBERTHUR_WARM_RESET_COMMAND=0x03
###############################################################################
# Force UICC to only listen to the following technology(s).
# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h.
-# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B.
-#UICC_LISTEN_TECH_MASK=0x03
+# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B | NFA_TECHNOLOGY_MASK_F
+UICC_LISTEN_TECH_MASK=0x07
###############################################################################
-# AID for Empty Select command
-# If specified, this AID will be substituted when an Empty SELECT command is
-# detected. The first byte is the length of the AID. Maximum length is 16.
-AID_FOR_EMPTY_SELECT={08:A0:00:00:01:51:00:00:00}
+# Force HOST listen feature enable or disable.
+# 0: Disable
+# 1: Enable
+HOST_LISTEN_ENABLE=0x01
###############################################################################
-# Force tag polling for the following technology(s).
-# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h.
-# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B |
-# NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_ISO15693 |
-# NFA_TECHNOLOGY_MASK_B_PRIME | NFA_TECHNOLOGY_MASK_A_ACTIVE |
-# NFA_TECHNOLOGY_MASK_F_ACTIVE.
-#
-# Notable bits:
-# NFA_TECHNOLOGY_MASK_A 0x01
-# NFA_TECHNOLOGY_MASK_B 0x02
-# NFA_TECHNOLOGY_MASK_F 0x04
-# NFA_TECHNOLOGY_MASK_ISO15693 0x08
-# NFA_TECHNOLOGY_MASK_B_PRIME 0x10
-# NFA_TECHNOLOGY_MASK_KOVIO 0x20
-# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40
-# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80
-POLLING_TECH_MASK=0xEF
+# Allow UICC to be powered off if there is no traffic.
+# Timeout is in ms. If set to 0, then UICC will not be powered off.
+#UICC_IDLE_TIMEOUT=30000
+UICC_IDLE_TIMEOUT=0
###############################################################################
-# Force P2P to only listen for the following technology(s).
-# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h.
-# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_F |
-# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE
-P2P_LISTEN_TECH_MASK=0xC4
-
+# AID for Empty Select command
+# If specified, this AID will be substituted when an Empty SELECT command is
+# detected. The first byte is the length of the AID. Maximum length is 16.
+AID_FOR_EMPTY_SELECT={08:A0:00:00:01:51:00:00:00}
###############################################################################
# Maximum Number of Credits to be allowed by the NFCC
# This value overrides what the NFCC specifices allowing the host to have
@@ -183,15 +164,29 @@ MAX_RF_DATA_CREDITS=1
# the NFCC to send PPSE requests to the DH.
# The default setting is enabled (i.e. T4t Virtual SE is registered).
#REGISTER_VIRTUAL_SE=1
-REGISTER_VIRTUAL_SE=0
###############################################################################
# When screen is turned off, specify the desired power state of the controller.
# 0: power-off-sleep state; DEFAULT
# 1: full-power state
# 2: screen-off card-emulation (CE4/CE3/CE1 modes are used)
-# 3: FPM CE in snooze mode, Switch Off, Battery Off still available.
-SCREEN_OFF_POWER_STATE=3
+SCREEN_OFF_POWER_STATE=1
+
+###############################################################################
+# Firmware patch file
+# If the value is not set then patch download is disabled.
+FW_PATCH="/vendor/firmware/bcm2079x_firmware.ncd"
+
+###############################################################################
+# Firmware pre-patch file (sent before the above patch file)
+# If the value is not set then pre-patch is not used.
+FW_PRE_PATCH="/vendor/firmware/bcm2079x_pre_firmware.ncd"
+
+###############################################################################
+# Firmware patch format
+# 1 = HCD
+# 2 = NCD (default)
+#NFA_CONFIG_FORMAT=2
###############################################################################
# SPD Debug mode
@@ -204,14 +199,6 @@ SCREEN_OFF_POWER_STATE=3
# Note, this resets after a power-cycle.
#SPD_MAX_RETRY_COUNT=3
-###############################################################################
-# Patch RAM Version Checking
-# By default the stack will reject any attempt to download a patch where major
-# version is < the one that is in NVM. If this config item is set to 1 then
-# this version check is skipped.
-#
-#SPD_IGNORE_VERSION=0
-
###############################################################################
# transport driver
#
@@ -276,20 +263,67 @@ BCMI2CNFC_ADDRESS=0
NFC_WRITE_DELAY=20
###############################################################################
-# Configure the default NfcA/IsoDep techology and protocol route. Can be
-# either a secure element (e.g. 0xF4) or the host (0x00)
-DEFAULT_ISODEP_ROUTE=0xF3
+# Maximum Number of Credits to be allowed by the NFCC
+# This value overrides what the NFCC specifices allowing the host to have
+# the control to work-around transport limitations. If this value does
+# not exist or is set to 0, the NFCC will provide the number of credits.
+MAX_RF_DATA_CREDITS=1
###############################################################################
-# Configure the default "off-host" AID route. The default is 0xF4
-DEFAULT_OFFHOST_ROUTE=0xF4
+# Antenna Configuration - This data is used when setting 0xC8 config item
+# at startup (before discovery is started). If not used, no value is sent.
+#
+# The settings for this value are documented here:
+# http://wcgbu.broadcom.com/wpan/PM/Project%20Document%20Library/bcm20791B0/
+# Design/Doc/PHY%20register%20settings/BCM20791-B2-1027-02_PHY_Recommended_Reg_Settings.xlsx
+# This document is maintained by Paul Forshaw.
+#
+# The values marked as ?? should be tweaked per antenna or customer/app:
+# {20:C8:1E:06:??:00:??:??:??:00:??:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:??:01:00:00:40:04}
+# array[0] = 0x20 is length of the payload from array[1] to the end
+# array[1] = 0xC8 is PREINIT_DSP_CFG
+#PREINIT_DSP_CFG={20:C8:1E:06:1F:00:0F:03:3C:00:04:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:48:01:00:00:40:04}
-POWER_SAVER_WORKAROUND_1=0xF3
-PRESENCE_CHECK_ALGORITHM=1
+###############################################################################
+# Force tag polling for the following technology(s).
+# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h.
+# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B |
+# NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_ISO15693 |
+# NFA_TECHNOLOGY_MASK_B_PRIME | NFA_TECHNOLOGY_MASK_KOVIO |
+# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE.
+#
+# Notable bits:
+# NFA_TECHNOLOGY_MASK_A 0x01 /* NFC Technology A */
+# NFA_TECHNOLOGY_MASK_B 0x02 /* NFC Technology B */
+# NFA_TECHNOLOGY_MASK_F 0x04 /* NFC Technology F */
+# NFA_TECHNOLOGY_MASK_ISO15693 0x08 /* Proprietary Technology */
+# NFA_TECHNOLOGY_MASK_KOVIO 0x20 /* Proprietary Technology */
+# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40 /* NFC Technology A active mode */
+# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80 /* NFC Technology F active mode */
+POLLING_TECH_MASK=0xEF
-AID_MATCHING_MODE=2
-AUTO_ADJUST_ISO_BITRATE=1
+###############################################################################
+# Force P2P to only listen for the following technology(s).
+# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h.
+# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_F |
+# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE
+#
+# Notable bits:
+# NFA_TECHNOLOGY_MASK_A 0x01 /* NFC Technology A */
+# NFA_TECHNOLOGY_MASK_F 0x04 /* NFC Technology F */
+# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40 /* NFC Technology A active mode */
+# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80 /* NFC Technology F active mode */
+P2P_LISTEN_TECH_MASK=0xC5
+PRESERVE_STORAGE=0x01
+
+###############################################################################
+# Maximum EE supported number
+# NXP PN547C2 0x02
+# NXP PN65T 0x03
+NFA_MAX_EE_SUPPORTED=0x03
+
+###############################################################################
# NCI Hal Module name
-NCI_HAL_MODULE="nfc_nci.bcm2079x"
+NCI_HAL_MODULE="nfc_nci.pn54x"
diff --git a/configs/libnfc-nxp.conf b/configs/libnfc-nxp.conf
new file mode 100644
index 0000000..e3cc0ea
--- /dev/null
+++ b/configs/libnfc-nxp.conf
@@ -0,0 +1,340 @@
+## This file is used by NFC NXP NCI HAL(external/libnfc-nci/halimpl/pn547)
+## and NFC Service Java Native Interface Extensions (packages/apps/Nfc/nci/jni/extns/pn547)
+
+###############################################################################
+# Application options
+# Logging Levels
+# NXPLOG_DEFAULT_LOGLEVEL 0x01
+# ANDROID_LOG_DEBUG 0x03
+# ANDROID_LOG_WARN 0x02
+# ANDROID_LOG_ERROR 0x01
+# ANDROID_LOG_SILENT 0x00
+#
+NXPLOG_EXTNS_LOGLEVEL=0x02
+NXPLOG_NCIHAL_LOGLEVEL=0x02
+NXPLOG_NCIX_LOGLEVEL=0x02
+NXPLOG_NCIR_LOGLEVEL=0x02
+NXPLOG_FWDNLD_LOGLEVEL=0x02
+NXPLOG_TML_LOGLEVEL=0x02
+
+###############################################################################
+# Extension for Mifare reader enable
+# 0x00 - Disabled
+# 0x01 - Enabled
+MIFARE_READER_ENABLE=0x01
+
+###############################################################################
+# File location for Firmware
+#FW_STORAGE="/vendor/firmware/libpn547_fw.so"
+
+###############################################################################
+# System clock source selection configuration
+#define CLK_SRC_XTAL 1
+#define CLK_SRC_PLL 2
+
+NXP_SYS_CLK_SRC_SEL=0x02
+
+###############################################################################
+# System clock frequency selection configuration
+#define CLK_FREQ_13MHZ 1
+#define CLK_FREQ_19_2MHZ 2
+#define CLK_FREQ_24MHZ 3
+#define CLK_FREQ_26MHZ 4
+#define CLK_FREQ_38_4MHZ 5
+#define CLK_FREQ_52MHZ 6
+
+NXP_SYS_CLK_FREQ_SEL=0x02
+
+###############################################################################
+# The timeout value to be used for clock request acknowledgment
+# min value = 0x01 to max = 0x19
+
+NXP_SYS_CLOCK_TO_CFG=0x0A
+
+###############################################################################
+# NXP proprietary settings
+NXP_ACT_PROP_EXTN={2F, 02, 00}
+
+###############################################################################
+# NFC forum profile settings
+NXP_NFC_PROFILE_EXTN={20, 02, 05, 01, A0, 44, 01, 00}
+
+###############################################################################
+# Standby enable settings
+NXP_CORE_STANDBY={2F, 00, 01, 01}
+
+
+###############################################################################
+# NXP RF ALM (NO BOOSTER) configuration settings for FW VERSION = 08.01.1D
+###############################################################################
+
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_1={
+ 20, 02, F3, 20,
+ A0, 0D, 03, 00, 40, 03,
+ A0, 0D, 03, 04, 43, 20,
+ A0, 0D, 03, 04, FF, 05,
+ A0, 0D, 06, 06, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 06, 30, CF, 00, 08, 00,
+ A0, 0D, 06, 06, 2F, 8F, 05, 80, 0C,
+ A0, 0D, 04, 06, 03, 00, 71,
+ A0, 0D, 03, 06, 48, 18,
+ A0, 0D, 03, 06, 43, A0,
+ A0, 0D, 06, 06, 42, 00, 00, F1, F6,
+ A0, 0D, 06, 06, 41, 80, 00, 00, 00,
+ A0, 0D, 03, 06, 37, 18,
+ A0, 0D, 03, 06, 16, 00,
+ A0, 0D, 03, 06, 15, 00,
+ A0, 0D, 06, 06, FF, 05, 00, 00, 00,
+ A0, 0D, 06, 08, 44, 00, 00, 00, 00,
+ A0, 0D, 06, 20, 4A, 00, 00, 00, 00,
+ A0, 0D, 06, 20, 42, 88, 10, FF, FF,
+ A0, 0D, 03, 20, 16, 00,
+ A0, 0D, 03, 20, 15, 00,
+ A0, 0D, 06, 22, 44, 22, 00, 02, 00,
+ A0, 0D, 06, 22, 2D, 50, 44, 0C, 00,
+ A0, 0D, 04, 32, 03, 40, 3D,
+ A0, 0D, 06, 32, 42, F8, 10, FF, FF,
+ A0, 0D, 03, 32, 16, 00,
+ A0, 0D, 03, 32, 15, 01,
+ A0, 0D, 03, 32, 0D, 22,
+ A0, 0D, 03, 32, 14, 22,
+ A0, 0D, 06, 32, 4A, 30, 07, 01, 1F,
+ A0, 0D, 06, 34, 2D, 24, 77, 0C, 00,
+ A0, 0D, 06, 34, 34, 00, 00, E4, 03,
+ A0, 0D, 06, 34, 44, 21, 00, 02, 00
+}
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_2={
+ 20, 02, F4, 1F,
+ A0, 0D, 06, 35, 44, 21, 00, 02, 00,
+ A0, 0D, 06, 38, 4A, 53, 07, 01, 1B,
+ A0, 0D, 06, 38, 42, 68, 10, FF, FF,
+ A0, 0D, 03, 38, 16, 00,
+ A0, 0D, 03, 38, 15, 00,
+ A0, 0D, 06, 3A, 2D, 15, 47, 0D, 00,
+ A0, 0D, 06, 3C, 4A, 52, 07, 01, 1B,
+ A0, 0D, 06, 3C, 42, 68, 10, FF, FF,
+ A0, 0D, 03, 3C, 16, 00,
+ A0, 0D, 03, 3C, 15, 00,
+ A0, 0D, 06, 3E, 2D, 15, 47, 0D, 00,
+ A0, 0D, 06, 40, 42, F0, 10, FF, FF,
+ A0, 0D, 03, 40, 0D, 02,
+ A0, 0D, 03, 40, 14, 02,
+ A0, 0D, 06, 40, 4A, 12, 07, 00, 00,
+ A0, 0D, 03, 40, 16, 00,
+ A0, 0D, 03, 40, 15, 00,
+ A0, 0D, 06, 42, 2D, 15, 47, 0D, 00,
+ A0, 0D, 06, 46, 44, 21, 00, 02, 00,
+ A0, 0D, 06, 46, 2D, 05, 47, 0E, 00,
+ A0, 0D, 06, 44, 4A, 33, 07, 01, 07,
+ A0, 0D, 06, 44, 42, 88, 10, FF, FF,
+ A0, 0D, 03, 44, 16, 00,
+ A0, 0D, 03, 44, 15, 00,
+ A0, 0D, 06, 4A, 44, 22, 00, 02, 00,
+ A0, 0D, 06, 4A, 2D, 05, 37, 0C, 00,
+ A0, 0D, 06, 48, 4A, 33, 07, 01, 07,
+ A0, 0D, 06, 48, 42, 88, 10, FF, FF,
+ A0, 0D, 03, 48, 16, 00,
+ A0, 0D, 03, 48, 15, 00,
+ A0, 0D, 06, 4E, 44, 22, 00, 02, 00
+}
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_3={
+ 20, 02, F7, 1E,
+ A0, 0D, 06, 4E, 2D, 05, 37, 0C, 00,
+ A0, 0D, 06, 4C, 4A, 33, 07, 01, 07,
+ A0, 0D, 06, 4C, 42, 88, 10, FF, FF,
+ A0, 0D, 03, 4C, 16, 00,
+ A0, 0D, 03, 4C, 15, 00,
+ A0, 0D, 06, 52, 44, 22, 00, 02, 00,
+ A0, 0D, 06, 52, 2D, 05, 25, 0C, 00,
+ A0, 0D, 06, 50, 42, 90, 10, FF, FF,
+ A0, 0D, 06, 50, 4A, 11, 0F, 01, 07,
+ A0, 0D, 03, 50, 16, 00,
+ A0, 0D, 03, 50, 15, 00,
+ A0, 0D, 06, 56, 2D, 05, 9E, 0C, 00,
+ A0, 0D, 06, 56, 44, 22, 00, 02, 00,
+ A0, 0D, 06, 5C, 2D, 05, 69, 0C, 00,
+ A0, 0D, 06, 5C, 44, 21, 00, 02, 00,
+ A0, 0D, 06, 54, 42, 88, 10, FF, FF,
+ A0, 0D, 06, 54, 4A, 33, 07, 01, 07,
+ A0, 0D, 03, 54, 16, 00,
+ A0, 0D, 03, 54, 15, 00,
+ A0, 0D, 06, 5A, 42, 90, 10, FF, FF,
+ A0, 0D, 06, 5A, 4A, 31, 07, 01, 07,
+ A0, 0D, 03, 5A, 16, 00,
+ A0, 0D, 03, 5A, 15, 00,
+ A0, 0D, 06, 98, 2F, AF, 05, 80, 0F,
+ A0, 0D, 06, 9A, 42, 00, 00, F1, F6,
+ A0, 0D, 06, 30, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 6C, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 6C, 30, CF, 00, 08, 00,
+ A0, 0D, 06, 6C, 2F, 8F, 05, 80, 0C,
+ A0, 0D, 06, 70, 2F, 8F, 05, 80, 12
+}
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_4={
+ 20, 02, F7, 1E,
+ A0, 0D, 06, 70, 30, CF, 00, 08, 00,
+ A0, 0D, 06, 74, 2F, 8F, 05, 80, 12,
+ A0, 0D, 06, 74, 30, DF, 00, 07, 00,
+ A0, 0D, 06, 78, 2F, 1F, 06, 80, 01,
+ A0, 0D, 06, 78, 30, 3F, 00, 04, 00,
+ A0, 0D, 06, 78, 44, A2, 90, 03, 00,
+ A0, 0D, 03, 78, 47, 00,
+ A0, 0D, 06, 7C, 2F, AF, 05, 80, 0F,
+ A0, 0D, 06, 7C, 30, CF, 00, 07, 00,
+ A0, 0D, 06, 7C, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 7D, 30, CF, 00, 08, 00,
+ A0, 0D, 06, 80, 2F, AF, 05, 80, 90,
+ A0, 0D, 06, 80, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 84, 2F, AF, 05, 80, 92,
+ A0, 0D, 06, 84, 44, A3, 90, 03, 00,
+ A0, 0D, 06, 88, 2F, 7F, 04, 80, 10,
+ A0, 0D, 06, 88, 30, 5F, 00, 16, 00,
+ A0, 0D, 03, 88, 47, 00,
+ A0, 0D, 06, 88, 44, A1, 90, 03, 00,
+ A0, 0D, 03, 0C, 48, 18,
+ A0, 0D, 03, 10, 43, 20,
+ A0, 0D, 06, 6A, 42, F8, 10, FF, FF,
+ A0, 0D, 03, 6A, 16, 00,
+ A0, 0D, 03, 6A, 15, 01,
+ A0, 0D, 06, 6A, 4A, 30, 0F, 01, 1F,
+ A0, 0D, 06, 8C, 42, 88, 10, FF, FF,
+ A0, 0D, 06, 8C, 4A, 33, 07, 01, 07,
+ A0, 0D, 03, 8C, 16, 00,
+ A0, 0D, 03, 8C, 15, 00,
+ A0, 0D, 06, 92, 42, 90, 10, FF, FF
+}
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_5={
+ 20, 02, 37, 07,
+ A0, 0D, 06, 92, 4A, 31, 07, 01, 07,
+ A0, 0D, 03, 92, 16, 00,
+ A0, 0D, 03, 92, 15, 00,
+ A0, 0D, 06, 0A, 30, CF, 00, 08, 00,
+ A0, 0D, 06, 0A, 2F, 8F, 05, 80, 0C,
+ A0, 0D, 03, 0A, 48, 10,
+ A0, 0D, 06, 0A, 44, A3, 90, 03, 00
+}
+# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D ***
+NXP_RF_CONF_BLK_6={
+}
+
+###############################################################################
+# Core configuration extensions
+# It includes
+# A002 - Clock Request
+# 0x00 - Disabled
+# 0x01 - Enabled
+# A003 - Clock Selection
+# Please refer to User Manual
+# A004 - Clock Time Out
+# Defined in ms
+# A00E - Load Modulation Mode
+# 0x00 - PLM
+# 0x01 - ALM
+# A011 - Clock specific configuration
+# Please refer to User Manual
+# A012 - NFCEE interface 2 configuration
+# 0x00 - SWP 2 interface is used
+# 0x02 - DWP interface is used
+# A013 - TVdd configuration
+# 0x00 - TVdd is set to 3.1V in Poll mode
+# 0x02 - TVdd is set to 2.7V in Poll mode
+# A040-A043 - Low Power Card Detector
+# Please refer to Application Note of LPCD
+# A05E - Jewel Reader
+# 0x00 - RID is not sent during activation
+# 0x01 - RID is sent during activation
+# A061 - Retry after LPCD
+# 0b0000XXXX - Number of retry if activation failed
+# 0bXXXX0000 - Duration to wait before retry (10ms per step)
+# Please refer to User Manual
+# A0CD - SWP interface 1: S1 line behavior
+# Defined S1 High time-out during Activation sequence
+# A0EC - SWP1 interface
+# 0x00 - Disabled
+# 0x01 - Enabled
+# A0ED - SWP2 interface
+# 0x00 - Disabled
+# 0x01 - Enabled
+NXP_CORE_CONF_EXTN={20, 02, 40, 0F,
+ A0, 02, 01, 01,
+ A0, 04, 01, 0A,
+ A0, 0E, 01, 01,
+ A0, 11, 04, 01, 22, 67, CD,
+ A0, 12, 01, 00,
+ A0, 13, 01, 00,
+ A0, 40, 01, 01,
+ A0, 41, 01, 02,
+ A0, 42, 01, 19,
+ A0, 43, 01, 00,
+ A0, 5E, 01, 01,
+ A0, 61, 01, 00,
+ A0, CD, 01, 0F,
+ A0, EC, 01, 01,
+ A0, ED, 01, 00
+ }
+
+###############################################################################
+# Core configuration settings
+# It includes
+# 18 - Poll Mode NFC-F: PF_BIT_RATE
+# 21 - Poll Mode ISO-DEP: PI_BIT_RATE
+# 28 - Poll Mode NFC-DEP: PN_NFC_DEP_SPEED
+# 30 - Lis. Mode NFC-A: LA_BIT_FRAME_SDD
+# 31 - Lis. Mode NFC-A: LA_PLATFORM_CONFIG
+# 33 - Lis. Mode NFC-A: LA_SEL_INFO
+# 50 - Lis. Mode NFC-F: LF_PROTOCOL_TYPE
+# 54 - Lis. Mode NFC-F: LF_CON_BITR_F
+# 5B - Lis. Mode ISO-DEP: LI_BIT_RATE
+# 60 - Lis. Mode NFC-DEP: LN_WT
+# 80 - Other Param.: RF_FIELD_INFO
+# 81 - Other Param.: RF_NFCEE_ACTION
+# 82 - Other Param.: NFCDEP_OP
+NXP_CORE_CONF={ 20, 02, 2E, 0E,
+ 18, 01, 01,
+ 21, 01, 00,
+ 28, 01, 01,
+ 30, 01, 08,
+ 31, 01, 03,
+ 33, 04, 01, 02, 03, 04,
+ 50, 01, 02,
+ 54, 01, 06,
+ 5B, 01, 00,
+ 60, 01, 0E,
+ 80, 01, 01,
+ 81, 01, 01,
+ 82, 01, 0E,
+ 3C, 01, 04
+ }
+
+###############################################################################
+# Mifare Classic Key settings
+#NXP_CORE_MFCKEY_SETTING={20, 02, 25,04, A0, 51, 06, A0, A1, A2, A3, A4, A5,
+# A0, 52, 06, D3, F7, D3, F7, D3, F7,
+# A0, 53, 06, FF, FF, FF, FF, FF, FF,
+# A0, 54, 06, 00, 00, 00, 00, 00, 00}
+
+###############################################################################
+# Default SE Options
+# No secure element 0x00
+# eSE 0x01
+# UICC 0x02
+# MULTI_SE 0x03
+NXP_DEFAULT_SE=0x02
+
+###############################################################################
+NXP_DEFAULT_NFCEE_TIMEOUT=0x06
+
+###############################################################################
+#Enable SWP full power mode when phone is power off
+NXP_SWP_FULL_PWR_ON=0x00
+
+###############################################################################
+#Chip type
+#PN547C2 0x01
+#PN65T 0x02
+NXP_NFC_CHIP=0x01
diff --git a/device.mk b/device.mk
index bd92c36..d9326b9 100644
--- a/device.mk
+++ b/device.mk
@@ -48,11 +48,14 @@ PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.telephony.gsm.xml:system/etc/permissions/android.hardware.telephony.gsm.xml \
frameworks/native/data/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml
-# NFC Permissions
-#PRODUCT_COPY_FILES += \
-# frameworks/native/data/etc/android.hardware.nfc.xml:system/etc/permissions/android.hardware.nfc.xml \
-# frameworks/native/data/etc/android.hardware.nfc.hce.xml:system/etc/permissions/android.hardware.nfc.hce.xml \
-# frameworks/native/data/etc/com.android.nfc_extras.xml:system/etc/permissions/com.android.nfc_extras.xml
+# SONY NFC Permissions
+PRODUCT_COPY_FILES += \
+ frameworks/native/data/etc/android.hardware.nfc.xml:system/etc/permissions/android.hardware.nfc.xml \
+ frameworks/native/data/etc/com.android.nfc_extras.xml:system/etc/permissions/com.android.nfc_extras.xml \
+ frameworks/native/data/etc/com.nxp.mifare.xml:system/etc/permissions/com.nxp.mifare.xml \
+ $(LOCAL_PATH)/configs/libnfc-brcm.conf:system/etc/libnfc-brcm.conf \
+ $(LOCAL_PATH)/configs/libnfc-nxp.conf:system/etc/libnfc-nxp.conf \
+ $(LOCAL_PATH)/configs/nfcee_access.xml:system/etc/nfcee_access.xml
# Audio
PRODUCT_COPY_FILES += \
@@ -110,18 +113,11 @@ PRODUCT_PACKAGES += \
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/configs/media_profiles.xml:system/etc/media_profiles.xml
-# NFC
-#PRODUCT_PACKAGES += \
-# com.android.nfc_extras \
-# NfcNci \
-# nfc_nci.bcm2079x.msm8974 \
-# Tag
-
-#PRODUCT_COPY_FILES += \
-# $(LOCAL_PATH)/configs/nfcee_access.xml:system/etc/nfcee_access.xml \
-# $(LOCAL_PATH)/configs/libnfc-brcm-20791b05.conf:system/etc/libnfc-brcm-20791b05.conf \
-# $(LOCAL_PATH)/configs/libnfc-brcm-20791b04.conf:system/etc/libnfc-brcm-20791b04.conf \
-# $(LOCAL_PATH)/configs/libnfc-brcm.conf:system/etc/libnfc-brcm.conf
+# SONY NFC
+PRODUCT_PACKAGES += \
+ com.android.nfc_extras \
+ NfcSony \
+ Tag
# Radio
PRODUCT_PACKAGES += \
@@ -136,7 +132,12 @@ PRODUCT_PACKAGES += \
init.crda.sh \
init.qcom.rc \
init.qcom.usb.rc \
- ueventd.qcom.rc
+ ueventd.qcom.rc \
+
+# Ramdisk for felica
+PRODUCT_PACKAGES += \
+ init.carrier.rc \
+ init.felica.sh
# Thermal
PRODUCT_COPY_FILES += \
diff --git a/overlay/frameworks/base/core/res/res/values/config.xml b/overlay/frameworks/base/core/res/res/values/config.xml
index e7223b7..d09404f 100644
--- a/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/overlay/frameworks/base/core/res/res/values/config.xml
@@ -19,7 +19,37 @@
-
+
+
+
+ - managed_profile
+ - ime
+ - sync_failing
+ - sync_active
+ - cast
+ - hotspot
+ - location
+ - su
+ - bluetooth
+ - nfc
+ - tty
+ - speakerphone
+ - headset
+ - zen
+ - mute
+ - volume
+ - wifi
+ - cdma_eri
+ - data_connection
+ - phone_evdo_signal
+ - phone_signal
+ - battery
+ - alarm_clock
+ - secure
+ - clock
+ - felica_lock
+
false
diff --git a/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml b/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..be4efdc
--- /dev/null
+++ b/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ "Keyguard"
+
+ "I'm Full Man, UnPlug Me !!"
+
+ "Leave Me Alone, I'm Eating"
+
+ "Come On, Charge Faster You Piece Of Crap"
+
+ "Come On, Charge Faster You Piece Of Crap"
+
+ "Hey, Need Some Electricity Here !!"
+
+
diff --git a/overlay/frameworks/base/packages/Keyguard/res/values/config.xml b/overlay/frameworks/base/packages/Keyguard/res/values/config.xml
new file mode 100644
index 0000000..90d3b18
--- /dev/null
+++ b/overlay/frameworks/base/packages/Keyguard/res/values/config.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+ 900
+
+
+ 1500
+
diff --git a/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml b/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml
new file mode 100644
index 0000000..437414a
--- /dev/null
+++ b/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ Keyguard
+
+
+ Im Full Man, UnPlug Me !!
+
+
+ Leave Me Alone, Im Eating
+
+
+ Come On, Charge Faster You Piece Of Crap
+
+
+ Come On, Charge Faster You Piece Of Crap
+
+
+ Hey, Need Some Electricity Here !!
+
+
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 84881d3..c71546b 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -39,3 +39,19 @@ LOCAL_MODULE_TAGS := optional eng
LOCAL_MODULE_CLASS := ETC
LOCAL_SRC_FILES := etc/init.crda.sh
include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.carrier.rc
+LOCAL_MODULE_TAGS := optional eng
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := etc/init.carrier.rc
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+include $(BUILD_PREBUILT)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := init.felica.sh
+LOCAL_MODULE_TAGS := optional eng
+LOCAL_MODULE_CLASS := ETC
+LOCAL_SRC_FILES := etc/init.felica.sh
+LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
+include $(BUILD_PREBUILT)
diff --git a/rootdir/etc/fstab.twrp b/rootdir/etc/fstab.twrp
index 2fd92a7..0f2e7e1 100644
--- a/rootdir/etc/fstab.twrp
+++ b/rootdir/etc/fstab.twrp
@@ -8,6 +8,6 @@
/efs3 emmc /dev/block/platform/msm_sdcc.1/by-name/modemst2 flags=backup=1;subpartitionof=/efs1
/external_sd vfat /dev/block/mmcblk1p1 /dev/block/mmcblk1 flags=display="Micro SDcard";storage;wipeingui;removable
/usbstorage vfat /dev/block/sda1 /dev/block/sda flags=display="USB Storage";storage;wipeingui;removable
-/modem emmc /dev/block/platform/msm_sdcc.1/by-name/modem flags=backup=1;display="Modem";fsflags=ro
+/modem vfat /dev/block/platform/msm_sdcc.1/by-name/modem flags=backup=1;display="Modem";fsflags=rw,context=u:object_r:firmware_file:s0
/firmware vfat /dev/block/platform/msm_sdcc.1/by-name/apnhlos flags=backup=1;subpartitionof=/modem;mounttodecrypt;fsflags=ro
/misc emmc /dev/block/platform/msm_sdcc.1/by-name/fota
diff --git a/rootdir/etc/init.carrier.rc b/rootdir/etc/init.carrier.rc
new file mode 100644
index 0000000..5bda92f
--- /dev/null
+++ b/rootdir/etc/init.carrier.rc
@@ -0,0 +1,54 @@
+# Copyright (C) 2012 The Android Open Source Project
+#
+# IMPORTANT: Do not create world writable files or directories.
+# This is a common source of Android security bugs.
+#
+
+# Tmm Add Start
+on init
+ export EXTERNAL_STORAGE_DOCOMO /storage/extSdCard
+
+on post-fs-data
+# FeliCa
+ exec /system/bin/sh /init.felica.sh
+
+ mkdir /efs/FeliCaLock 0770 system system
+ chown system system /efs/FeliCaLock/01
+ chmod 0660 /efs/FeliCaLock/01
+ chown system system /efs/FeliCaLock/02
+ chmod 0660 /efs/FeliCaLock/02
+
+# Japan Add NFC Type Setting(Osaifu.cfg)
+ mkdir /data/misc/osaifu 0755 system system
+ chmod 644 /data/misc/osaifu/osaifu.cfg
+ chown system system /data/misc/osaifu/osaifu.cfg
+
+# Fingerprint
+ mkdir /dev/validity 0770 system system
+
+service mfsc /system/bin/mfsc
+ class core
+ user root
+ group system felicalock nfc
+ oneshot
+
+service mfdp /system/bin/mfdp
+ class core
+ user root
+ group system felicalock nfc
+ oneshot
+
+# JPN: For MobileTV [ISDBT] \android\device\samsung\kltedcm\init.kltedcm.rc
+ mkdir /data/one-seg 0775 system system
+ chown system system /data/one-seg
+ chmod 0777 /data/one-seg
+
+ chown system system /dev/isdbt
+ chmod 0666 /dev/isdbt
+
+# JPN: For MobileTV [ISDBT] \android\device\samsung\kltedcm\init.kltedcm.rc
+service mobileTV /system/bin/broadcastProcessObserver
+ class main
+ user system
+ group system radio audio camera graphics inet net_bt net_bt_admin net_raw sdcard_rw sdcard_r shell
+
diff --git a/rootdir/etc/init.felica.sh b/rootdir/etc/init.felica.sh
new file mode 100755
index 0000000..fced8b7
--- /dev/null
+++ b/rootdir/etc/init.felica.sh
@@ -0,0 +1,21 @@
+#!/system/bin/sh
+
+#/sbin/setpropex ro.warranty_bit 0
+#/sbin/setpropex ro.emmc_checksum 0
+
+KBC_DATA_PATH=/data/media/0/kbc
+
+CMDLINE_FILE=$KBC_DATA_PATH/cmdline
+if [ -f $CMDLINE_FILE ]; then
+ FELICA_CMDLINE=`cat $CMDLINE_FILE`
+ echo "$FELICA_CMDLINE" > /proc/cmdline
+ exit 0
+fi
+
+FELICA_KEY_FILE=$KBC_DATA_PATH/felica_key
+if [ -f $FELICA_KEY_FILE ]; then
+ FELICA_KEY=`cat $FELICA_KEY_FILE`
+ BASE_CMDLINE=`cat /proc/cmdline`
+ echo "cordon=$FELICA_KEY $BASE_CMDLINE" > /proc/cmdline
+ exit 0
+fi
diff --git a/rootdir/etc/init.qcom.rc b/rootdir/etc/init.qcom.rc
index 95dfd2b..a1f4377 100644
--- a/rootdir/etc/init.qcom.rc
+++ b/rootdir/etc/init.qcom.rc
@@ -26,6 +26,7 @@
#
import init.qcom.usb.rc
+import init.carrier.rc
on early-init
export LD_SHIM_LIBS "/system/lib/libril.so|libril_shim.so"
diff --git a/rootdir/etc/ueventd.qcom.rc b/rootdir/etc/ueventd.qcom.rc
index 19025dd..46565f7 100644
--- a/rootdir/etc/ueventd.qcom.rc
+++ b/rootdir/etc/ueventd.qcom.rc
@@ -226,15 +226,15 @@ v/ppp 0660 radio vpn
/sys/devices/i2c.73/i2c-16/16-0018/input/input* delay 0664 system system
#JPN FeliCa
-/dev/felica 0660 mfc system
-/dev/felica_pon 0660 mfc system
-/dev/felica_cen 0660 mfc felicalock
-/dev/felica_rfs 0440 mfc system
-/dev/felica_rws 0660 mfc system
-/dev/felica_ant 0660 mfc system
-/dev/felica_int_poll 0400 mfc system
-/dev/felica_uid 0220 mfc system
-/dev/felica_uicc 0660 mfc system
+/dev/felica 0666 root system
+/dev/felica_pon 0666 root system
+/dev/felica_cen 0666 root system
+/dev/felica_rfs 0444 root system
+/dev/felica_rws 0666 root system
+/dev/felica_ant 0666 root system
+/dev/felica_int_poll 0400 root system
+/dev/felica_uid 0222 root system
+/dev/felica_uicc 0666 root system
#JPN NFC
/dev/ttyHSL1 0660 nfc nfc