Revert "Remove My Tag support."

This reverts commit 825f01522a1d68cadb634c88101e96f842478926.

Change-Id: I75e027c066ba7bddaab87f910083707d8eb380b0
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index db6d758..2aab0cc 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -18,8 +18,11 @@
 
 import com.android.internal.nfc.LlcpServiceSocket;
 import com.android.internal.nfc.LlcpSocket;
+import com.android.nfc.mytag.MyTagClient;
+import com.android.nfc.mytag.MyTagServer;
 
 import android.app.Application;
+import android.app.StatusBarManager;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -48,6 +51,11 @@
 import android.os.ServiceManager;
 import android.util.Log;
 
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.ListIterator;
@@ -55,6 +63,8 @@
 public class NfcService extends Application {
     static final boolean DBG = false;
 
+    private static final String MY_TAG_FILE_NAME = "mytag";
+
     static {
         System.loadLibrary("nfc_jni");
     }
@@ -148,6 +158,9 @@
     static final int MSG_LLCP_LINK_ACTIVATION = 2;
     static final int MSG_LLCP_LINK_DEACTIVATED = 3;
     static final int MSG_TARGET_DESELECTED = 4;
+    static final int MSG_SHOW_MY_TAG_ICON = 5;
+    static final int MSG_HIDE_MY_TAG_ICON = 6;
+    static final int MSG_MOCK_NDEF_TAG = 7;
 
     // TODO: none of these appear to be synchronized but are
     // read/written from different threads (notably Binder threads)...
@@ -170,6 +183,8 @@
     private SharedPreferences mPrefs;
     private SharedPreferences.Editor mPrefsEditor;
     private PowerManager.WakeLock mWakeLock;
+    private MyTagServer mMyTagServer;
+    private MyTagClient mMyTagClient;
 
     private static NfcService sService;
 
@@ -189,6 +204,9 @@
         mManager = new NativeNfcManager(mContext, this);
         mManager.initializeNativeStructure();
 
+        mMyTagServer = new MyTagServer();
+        mMyTagClient = new MyTagClient(this);
+
         mPrefs = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE);
         mPrefsEditor = mPrefs.edit();
 
@@ -250,6 +268,7 @@
 
             if (previouslyEnabled) {
                 /* tear down the my tag server */
+                mMyTagServer.stop();
                 isSuccess = mManager.deinitialize();
                 if (DBG) Log.d(TAG, "NFC success of deinitialize = " + isSuccess);
                 if (isSuccess) {
@@ -798,12 +817,56 @@
 
         @Override
         public NdefMessage localGet() throws RemoteException {
-            throw new UnsupportedOperationException("local tag not supported");
+            mContext.enforceCallingOrSelfPermission(NFC_PERM, NFC_PERM_ERROR);
+
+            synchronized (this) {
+                return mLocalMessage;
+            }
         }
 
         @Override
         public void localSet(NdefMessage message) throws RemoteException {
-            throw new UnsupportedOperationException("local tag not supported");
+            mContext.enforceCallingOrSelfPermission(ADMIN_PERM, ADMIN_PERM_ERROR);
+
+            synchronized (this) {
+                mLocalMessage = message;
+                Context context = NfcService.this.getApplicationContext();
+
+                // Send a message to the UI thread to show or hide the icon so the requests are
+                // serialized and the icon can't get out of sync with reality.
+                if (message != null) {
+                    FileOutputStream out = null;
+
+                    try {
+                        out = context.openFileOutput(MY_TAG_FILE_NAME, Context.MODE_PRIVATE);
+                        byte[] bytes = message.toByteArray();
+                        if (bytes.length == 0) {
+                            Log.w(TAG, "Setting a empty mytag");
+                        }
+
+                        out.write(bytes);
+                    } catch (IOException e) {
+                        Log.e(TAG, "Could not write mytag file", e);
+                    } finally {
+                        try {
+                            if (out != null) {
+                                out.flush();
+                                out.close();
+                            }
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    }
+
+                    // Only show the icon if NFC is enabled.
+                    if (mIsNfcEnabled) {
+                        sendMessage(MSG_SHOW_MY_TAG_ICON, null);
+                    }
+                } else {
+                    context.deleteFile(MY_TAG_FILE_NAME);
+                    sendMessage(MSG_HIDE_MY_TAG_ICON, null);
+                }
+            }
         }
     };
 
@@ -1713,6 +1776,10 @@
 
             /* Start polling loop */
             maybeEnableDiscovery();
+
+            /* bring up the my tag server */
+            mMyTagServer.start();
+
         } else {
             mIsNfcEnabled = false;
         }
@@ -1766,6 +1833,55 @@
                 intent.putExtra(NfcAdapter.EXTRA_NEW_BOOLEAN_STATE, mIsNfcEnabled);
                 mContext.sendBroadcast(intent);
             }
+
+            if (mIsNfcEnabled) {
+
+                Context context = getApplicationContext();
+
+                // Set this to null by default. If there isn't a tag on disk
+                // or if there was an error reading the tag then this will cause
+                // the status bar icon to be removed.
+                NdefMessage myTag = null;
+
+                FileInputStream input = null;
+
+                try {
+                    input = context.openFileInput(MY_TAG_FILE_NAME);
+                    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+                    byte[] buffer = new byte[4096];
+                    int read = 0;
+                    while ((read = input.read(buffer)) > 0) {
+                        bytes.write(buffer, 0, read);
+                    }
+
+                    myTag = new NdefMessage(bytes.toByteArray());
+                } catch (FileNotFoundException e) {
+                    // Ignore.
+                } catch (IOException e) {
+                    Log.e(TAG, "Could not read mytag file: ", e);
+                    context.deleteFile(MY_TAG_FILE_NAME);
+                } catch (FormatException e) {
+                    Log.e(TAG, "Invalid NdefMessage for mytag", e);
+                    context.deleteFile(MY_TAG_FILE_NAME);
+                } finally {
+                    try {
+                        if (input != null) {
+                            input.close();
+                        }
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+
+                try {
+                    mNfcAdapter.localSet(myTag);
+                } catch (RemoteException e) {
+                    // Ignore
+                }
+            } else {
+                sendMessage(MSG_HIDE_MY_TAG_ICON, null);
+            }
         }
     }
 
@@ -2044,6 +2160,14 @@
         mContext.sendOrderedBroadcast(LlcpLinkIntent, NFC_PERM);
     }
 
+    public void sendMockNdefTag(NdefMessage msg) {
+        NdefTag tag = NdefTag.createMockNdefTag(new byte[] { 0x00 },
+                new String[] { Tag.TARGET_OTHER },
+                null, null, new String[] { NdefTag.TARGET_OTHER },
+                new NdefMessage[][] { new NdefMessage[] { msg } });
+        sendMessage(MSG_MOCK_NDEF_TAG, tag);
+    }
+
     void sendMessage(int what, Object obj) {
         Message msg = mHandler.obtainMessage();
         msg.what = what;
@@ -2055,6 +2179,19 @@
         @Override
         public void handleMessage(Message msg) {
            switch (msg.what) {
+           case MSG_MOCK_NDEF_TAG: {
+               NdefTag tag = (NdefTag) msg.obj;
+               Intent intent = buildNdefTagIntent(tag);
+               Log.d(TAG, "mock NDEF tag, starting corresponding activity");
+               Log.d(TAG, tag.toString());
+               try {
+                   mContext.startActivity(intent);
+               } catch (ActivityNotFoundException e) {
+                   Log.w(TAG, "No activity found for mock tag");
+               }
+               break;
+           }
+
            case MSG_NDEF_TAG:
                if (DBG) Log.d(TAG, "Tag detected, notifying applications");
                NativeNfcTag nativeTag = (NativeNfcTag) msg.obj;
@@ -2221,6 +2358,20 @@
                mContext.sendOrderedBroadcast(TargetDeselectedIntent, NFC_PERM);
                break;
 
+           case MSG_SHOW_MY_TAG_ICON: {
+               StatusBarManager sb = (StatusBarManager) getSystemService(
+                       Context.STATUS_BAR_SERVICE);
+               sb.setIcon("nfc", R.drawable.stat_sys_nfc, 0);
+               break;
+           }
+
+           case MSG_HIDE_MY_TAG_ICON: {
+               StatusBarManager sb = (StatusBarManager) getSystemService(
+                       Context.STATUS_BAR_SERVICE);
+               sb.removeIcon("nfc");
+               break;
+           }
+
            default:
                Log.e(TAG, "Unknown message received");
                break;
diff --git a/src/com/android/nfc/mytag/MyTagClient.java b/src/com/android/nfc/mytag/MyTagClient.java
new file mode 100755
index 0000000..241d9e2
--- /dev/null
+++ b/src/com/android/nfc/mytag/MyTagClient.java
@@ -0,0 +1,97 @@
+/*
+ * 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.mytag;
+
+import com.android.internal.nfc.LlcpException;
+import com.android.internal.nfc.LlcpSocket;
+import com.android.nfc.NfcService;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.nfc.NdefMessage;
+import android.nfc.NfcAdapter;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * Simple client to push the local NDEF message to a server on the remote side of an
+ * LLCP connection. The message is set via {@link NfcAdapter#setLocalNdefMessage}.
+ */
+public class MyTagClient extends BroadcastReceiver {
+    private static final String TAG = "MyTagClient";
+    private static final boolean DBG = true;
+
+    public MyTagClient(Context context) {
+        context.registerReceiver(this, new IntentFilter(NfcAdapter.ACTION_LLCP_LINK_STATE_CHANGED));
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DBG) Log.d(TAG, "LLCP connection up and running");
+        NfcAdapter adapter = NfcAdapter.getDefaultAdapter();
+        NdefMessage msg = adapter.getLocalNdefMessage();
+        
+        if (msg == null) {
+            if (DBG) Log.d(TAG, "No MyTag set, exiting");
+            // Nothing to send to the server
+            return;
+        }
+
+        int linkState = intent.getIntExtra(NfcAdapter.EXTRA_LLCP_LINK_STATE_CHANGED,
+                    NfcAdapter.LLCP_LINK_STATE_DEACTIVATED);
+
+        if (linkState != NfcAdapter.LLCP_LINK_STATE_ACTIVATED) {
+            if (DBG) Log.d(TAG, "LLCP connection not activated, exiting");
+            return;
+        }
+
+        new SendAsync().execute(msg);
+    }
+
+    final class SendAsync extends AsyncTask<NdefMessage, Void, Void> {
+        @Override
+        public Void doInBackground(NdefMessage... msgs) {
+            NfcService service = NfcService.getInstance();
+            NdefMessage msg = msgs[0];
+            try {
+                if (DBG) Log.d(TAG, "about to create socket");
+                // Connect to the my tag server on the remote side
+                LlcpSocket sock = service.createLlcpSocket(0, 128, 1, 1024);
+                if (DBG) Log.d(TAG, "about to connect");
+//                sock.connect(MyTagServer.SERVICE_NAME);
+                sock.connect(0x20);
+
+                // Push the local NDEF message to the server
+                if (DBG) Log.d(TAG, "about to send");
+                sock.send(msg.toByteArray());
+                if (DBG) Log.d(TAG, "about to close");
+                sock.close();
+
+            } catch (IOException e) {
+                Log.e(TAG, "couldn't send tag", e);
+            } catch (LlcpException e) {
+                // Most likely the other side doesn't support the my tag protocol
+                Log.e(TAG, "couldn't send tag", e);
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/com/android/nfc/mytag/MyTagServer.java b/src/com/android/nfc/mytag/MyTagServer.java
new file mode 100755
index 0000000..cb2059f
--- /dev/null
+++ b/src/com/android/nfc/mytag/MyTagServer.java
@@ -0,0 +1,156 @@
+/*
+ * 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.mytag;
+
+import com.android.internal.nfc.LlcpException;
+import com.android.internal.nfc.LlcpServiceSocket;
+import com.android.internal.nfc.LlcpSocket;
+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#setLocalNdefMessage}.
+ */
+public class MyTagServer {
+    private static final String TAG = "~~~~~~~~~~~~~~~";
+    private static final boolean DBG = true;
+    private static final int SERVICE_SAP = 0x20;
+
+    static final String SERVICE_NAME = "com.android.mytag";
+
+    NfcService mService = NfcService.getInstance();
+    /** Protected by 'this', null when stopped, non-null when running */
+    ServerThread mServerThread = null;
+
+    /** Connection class, used to handle incoming connections */
+    private class ConnectionThread extends Thread {
+        private LlcpSocket mSock;
+
+        ConnectionThread(LlcpSocket sock) {
+            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");
+                    }
+                }
+
+                // Build NDEF message from the stream
+                NdefMessage msg = new NdefMessage(buffer.toByteArray());
+                if (DBG) Log.d(TAG, "got message " + msg.toString());
+
+                // Send the intent for the fake tag
+                mService.sendMockNdefTag(msg);
+            } catch (FormatException e) {
+                Log.e(TAG, "badly formatted NDEF message, ignoring", e);
+            }
+        }
+    };
+
+    /** Server class, used to listen for incoming connection request */
+    class ServerThread extends Thread {
+        boolean mRunning = true;
+        LlcpServiceSocket mServerSocket;
+
+        @Override
+        public void run() {
+            if (DBG) Log.d(TAG, "about create LLCP service socket");
+            mServerSocket = mService.createLlcpServiceSocket(SERVICE_SAP, null,
+                    128, 1, 1024);
+            if (mServerSocket == null) {
+                Log.d(TAG, "failed to create LLCP service socket");
+                return;
+            }
+            if (DBG) Log.d(TAG, "created LLCP service socket");
+            try {
+                while (mRunning) {
+                    if (DBG) Log.d(TAG, "about to accept");
+                    LlcpSocket communicationSocket = mServerSocket.accept();
+                    if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
+                    if (communicationSocket != null) {
+                        new ConnectionThread(communicationSocket).start();
+                    }
+                }
+            } catch (LlcpException e) {
+                Log.e(TAG, "llcp error", e);
+            } catch (IOException e) {
+                Log.e(TAG, "IO error", e);
+            } finally {
+                if (mServerSocket != null) mServerSocket.close();
+            }
+        }
+
+        public void shutdown() {
+            mRunning = false;
+            if (mServerSocket != null) {
+                mServerSocket.close();
+            }
+        }
+    };
+
+    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;
+            }
+        }
+    }
+}