package mobisocial.nfc.legacy;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import mobisocial.ndefexchange.NdefExchangeContract;
import mobisocial.nfc.R;
import com.android.apps.tag.record.UriRecord;
import edu.stanford.mobisocial.appmanifest.ApplicationManifest;
import edu.stanford.mobisocial.appmanifest.platforms.PlatformReference;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.os.Parcelable;
import android.util.Log;
import android.widget.Toast;
public class NfcBridgeService extends Service implements NdefExchangeContract {
private static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
private static final byte[] RTD_APP_MANIFEST = "application/vnd.mobisocial-appmanifest".getBytes();
private static final String TAG = NfcBridgeActivity.TAG;
private static NfcBridge mNfcBridge = null;
private NotificationManager mNotificationManager;
public static final String ACTION_HANDLE_NDEF = "mobisocial.intent.action.HANDLE_NDEF";
public static final String ACTION_SET_NDEF = "mobisocial.intent.action.SET_NDEF";
public static final String EXTRA_APPLICATION_ARGUMENT = "android.intent.extra.APPLICATION_ARGUMENT";
public static final String MESSAGE_RECEIVED = "Nfc message received.";
private boolean mAutoLaunchActivities = true;
private Intent mNotifyIntent;
private UUID mServiceUuid;
private NdefMessage mForegroundMessage;
private static final String ROOT_DIR = "legacynfc";
private static NfcBridgeService sInstance;
// TODO: this is a hack.
public static NfcBridgeService getInstance() { return sInstance; }
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
mNotificationManager = (NotificationManager)getSystemService(Service.NOTIFICATION_SERVICE);
mNotifyIntent = new Intent(NfcBridgeActivity.ACTION_UPDATE);
mNotifyIntent.setPackage(getPackageName());
mScreenChangedFilter = new IntentFilter();
mScreenChangedFilter.addAction(Intent.ACTION_SCREEN_ON);
mScreenChangedFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenChangedReceiver, mScreenChangedFilter);
mSetNdefFilter = new IntentFilter();
mSetNdefFilter.addAction(ACTION_SET_NDEF);
registerReceiver(mNdefSharedReceiver, mSetNdefFilter);
SharedPreferences preferences = getSharedPreferences("main", 0);
String uuid = preferences.getString("serviceUuid", null);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
preferences.edit().putString("serviceUuid", uuid).commit();
}
mServiceUuid = UUID.fromString(uuid);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mScreenChangedReceiver);
}
public void startSelf() {
startService(new Intent(this, NfcBridgeService.class));
}
public class LocalBinder extends Binder {
NfcBridgeService getService() {
return NfcBridgeService.this;
}
}
public synchronized void enableTcpBridge() {
if (mNfcBridge != null) {
Log.w(TAG, "Tcp bridge already running.");
return;
}
mNfcBridge = new NfcTcpBridge(this);
mNfcBridge.start();
sendBroadcast(mNotifyIntent);
startSelf();
}
public synchronized void enableBridge() {
if (mNfcBridge != null) {
Log.w(TAG, "Bt bridge already running.");
return;
}
mNfcBridge = new NfcBluetoothBridge(this, mServiceUuid);
//mNfcBridge = new NfcTcpBridge(this);
mNfcBridge.start();
sendBroadcast(mNotifyIntent);
startSelf();
}
public synchronized void disableBridge() {
if (mNfcBridge == null) {
Log.w(TAG, "Nfc bridge not running.");
return;
}
mNfcBridge.stop();
mNfcBridge = null;
sendBroadcast(mNotifyIntent);
stopForeground(false);
stopSelf();
}
public boolean isBridgeRunning() {
// Always running if the screen is on and mNfcBridge is not null.
return (mNfcBridge != null);
}
public String getBridgeReference() {
return (mNfcBridge == null) ? null : mNfcBridge.getReference();
}
public void setAutoLaunch(boolean doLaunch) {
mAutoLaunchActivities = doLaunch;
}
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
IntentFilter mScreenChangedFilter;
BroadcastReceiver mScreenChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized(NfcBridgeService.this) {
if (mNfcBridge == null) {
return;
}
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
mNfcBridge.stop();
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
mNfcBridge.start();
}
}
}
};
IntentFilter mSetNdefFilter;
BroadcastReceiver mNdefSharedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(EXTRA_NDEF_MESSAGES)) {
// Prepare an actionable NDEF exchange and notify user.
Log.d(TAG, "Handling received ndef.");
Parcelable[] messages = intent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES);
mForegroundMessage = (NdefMessage)messages[0];
Notification notification = new Notification(R.drawable.stat_sys_nfc, null, System.currentTimeMillis());
//Log.d(TAG, "TODO: use data field with ndef://wkt:hr/[base64]");
//Uri ndef = new Uri.Builder().scheme("ndef").authority("wkt:ndef").appendEncodedPath(arg0)
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra("ndef", mForegroundMessage);
sendIntent.setPackage(getPackageName());
sendIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
// sendIntent.setComponentName("mobisocial.vnfc", "mobisocial.indef.ShareActivity");
//intent.putExtra(EXTRA_NDEF_MESSAGES, messages);
PendingIntent contentIntent = PendingIntent.getActivity(NfcBridgeService.this, 0, sendIntent, PendingIntent.FLAG_CANCEL_CURRENT);
// try local broadcast
// else try local activity
// else try global broadcast
// else try global activity
// TODO: Show notification even if nfc service isn't running?
notification.setLatestEventInfo(NfcBridgeService.this, "Share current activity.", "Click to send to another device.", contentIntent);
mNotificationManager.notify(0, notification);
} else {
mNotificationManager.cancel(0);
mForegroundMessage = null;
}
}
};
@Override
public int handleNdef(NdefMessage[] ndef) {
Log.d(TAG, "broadcasting receipt of ndef");
Intent handleNdefIntent = new Intent(ACTION_HANDLE_NDEF);
handleNdefIntent.putExtra(EXTRA_NDEF_MESSAGES, ndef);
sendOrderedBroadcast(handleNdefIntent, "android.permission.NFC", mNdefRouter, null, Activity.RESULT_OK, null, null);
return NDEF_CONSUME;
}
/**
* Returns true if the given Ndef message contains a connection
* handover request.
*/
public static boolean isHandoverRequest(NdefMessage ndef) {
NdefRecord[] records = (ndef).getRecords();
return (records.length >= 3
&& records[0].getTnf() == NdefRecord.TNF_WELL_KNOWN
&& Arrays.equals(records[0].getType(), NdefRecord.RTD_HANDOVER_REQUEST));
}
private BroadcastReceiver mNdefRouter = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent inboundIntent) {
if (getResultCode() != Activity.RESULT_OK) {
return;
}
// TODO: Send to Junction.
Parcelable[] messages = inboundIntent.getParcelableArrayExtra(EXTRA_NDEF_MESSAGES);
NdefMessage ndef = (NdefMessage)messages[0];
NdefRecord firstRecord = ndef.getRecords()[0];
Notification notification = null;
// TODO: Use NdefHandler paradigm and code from DesktopNfc
// TODO: If isConnectionHandover
// addToKnownHandovers()
// then, when sharing, use this list to send ndef.
if (firstRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN) {
if (isHandoverRequest(ndef)) {
// mTargetHandover = ndef;
}
}
if (UriRecord.isUri(firstRecord)) {
UriRecord uriRecord = UriRecord.parse(firstRecord);
final Uri uri = uriRecord.getUri();
// handle certain types directly
final String type = typeFromUri(uri);
if (type != null) {
// TODO, download the file
new Thread() {
public void run() {
handleFileFromUri(type, uri);
};
}.start();
return;
}
Intent intent = new Intent(NfcAdapter.ACTION_NDEF_DISCOVERED, uri);
intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, messages);
if (null == getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)) {
intent = uriRecord.getIntentForUri();
}
notification = new Notification(R.drawable.stat_sys_nfc, MESSAGE_RECEIVED, System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(NfcBridgeService.this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
notification.setLatestEventInfo(NfcBridgeService.this, MESSAGE_RECEIVED,
"Click to visit " + uriRecord.getUri() + ".", contentIntent);
} else if (firstRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA &&
Arrays.equals(firstRecord.getType(), RTD_APP_MANIFEST)) {
String webpage = null;
String androidReference = null;
byte[] manifestBytes = ndef.getRecords()[0].getPayload();
ApplicationManifest manifest = new ApplicationManifest(
manifestBytes);
List<PlatformReference> platforms = manifest
.getPlatformReferences();
for (PlatformReference platform : platforms) {
int platformId = platform.getPlatformIdentifier();
switch (platformId) {
case ApplicationManifest.PLATFORM_WEB_GET:
webpage = new String(platform.getAppReference());
break;
case ApplicationManifest.PLATFORM_ANDROID_PACKAGE:
androidReference = new String(platform.getAppReference());
break;
}
}
boolean foundMatch = false;
if (androidReference != null) {
int col = androidReference.indexOf(":");
String pkg = androidReference.substring(0, col);
String arg = androidReference.substring(col+1);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(pkg);
intent.putExtra(EXTRA_APPLICATION_ARGUMENT, arg);
// TODO: support applications that aren't yet installed.
List<ResolveInfo> resolved = getPackageManager().queryIntentActivities(intent, 0);
if (resolved != null && resolved.size() > 0) {
ActivityInfo info = resolved.get(0).activityInfo;
intent.setComponent(new ComponentName(info.packageName, info.name));
notification = new Notification(R.drawable.stat_sys_nfc, MESSAGE_RECEIVED, System.currentTimeMillis());
PendingIntent contentIntent = PendingIntent.getActivity(NfcBridgeService.this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
notification.setLatestEventInfo(NfcBridgeService.this, "Nfc message received.", "Click to launch application.", contentIntent);
foundMatch = true;
}
}
if (!foundMatch && webpage != null) {
notification = new Notification(R.drawable.stat_sys_nfc, MESSAGE_RECEIVED, System.currentTimeMillis());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(webpage));
PendingIntent contentIntent = PendingIntent.getActivity(NfcBridgeService.this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
notification.setLatestEventInfo(NfcBridgeService.this, "Nfc message received.",
"Click to visit " + webpage + ".", contentIntent);
foundMatch = true;
}
}
if (notification != null) {
if (mAutoLaunchActivities) {
try {
notification.contentIntent.send();
return;
} catch (CanceledException e) {
}
}
notification.flags = Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(0, notification);
}
}
};
@Override
public NdefMessage getForegroundNdefMessage() {
return mForegroundMessage;
}
public void setForegroundNdefMessage(NdefMessage ndef) {
mForegroundMessage = ndef;
}
public void share(Object shared) {
throw new UnsupportedOperationException("Sharing not available.");
}
private void toast(String text) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
private String typeFromUri(Uri uri) {
String str = uri.toString();
int dot = str.lastIndexOf(".");
if (dot == -1) {
return null;
}
String suffix = str.substring(dot + 1);
if (suffix.equals("m3u")) {
return "audio/x-mpegurl";
}
return null;
}
private synchronized void handleFileFromUri(String type, Uri uri) {
File tempFile = null;
Log.d(TAG, "downloading nfc delivered content: " + uri);
File dlDir = new File(Environment.getExternalStorageDirectory(), ROOT_DIR);
if (!dlDir.exists()) {
dlDir.mkdir();
}
try {
tempFile = File.createTempFile("ndef", ".tmp", dlDir);
tempFile.deleteOnExit();
} catch (IOException e) {
Log.e(TAG, "could not create temporary file", e);
}
boolean cancelCurrentDownload = false;
try {
FileOutputStream out = new FileOutputStream(tempFile);
URL dlURL = new URL(uri.toString());
InputStream in = dlURL.openStream();
byte[] buf = new byte[4 * 1024];
int bytesRead;
while (!cancelCurrentDownload && (bytesRead = in.read(buf)) != -1) {
out.write(buf, 0, bytesRead);
}
in.close();
out.close();
if (cancelCurrentDownload) {
tempFile.delete();
cancelCurrentDownload = false;
} else {
Intent view = new Intent(Intent.ACTION_VIEW);
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.setDataAndType(Uri.fromFile(tempFile), type);
startActivity(view);
}
} catch (Exception e) {
Log.e(TAG,"failed to download file " + uri, e);
if (tempFile != null) {
try {
tempFile.delete();
} catch (Exception e2) {}
}
}
}
}