Wrap up discussion

What specifically you like about this internship?
This internship has a mentor for every intern, anytime I have questions about this project the mentor can help me, so I learned a lot from this internship. Also this internship is quite flexible, that it allows me work anywhere and anytime. Finally, it has the “office hour” and making suggestions and sharing information to interns for the future development.

What was lacking?
For internship based in company, it may have better communication face to face, and if the internship is based in company, there is a bigger chance for intern to stay in this company.

What are my future plans?
It is a great to work in the area of open source, in the future, the first thing is to look for a job, and this experience must be great help. If this internship can bring some job opportunities it will be perfect.

Summary Post

Challenges/lessons learned
At first, we want to use PhoneGap to develop the Android client, because MediaGrid has already has a web client based on HTML, CSS and JavaScript, and my job is to port it on Android, it is very easy for me to move code on MediaGrid to PhoneGap. However, problems exist. The first is that the user interface on PC’s browser is very different from that on mobile device, and there’s very little space for us to show the chat and media interface. So after discussing with Dan, I decide to redesign an interface like a local app. The second is Dan’s concern about local features that PhoneGap may not support, for example mDNS. The third is our concern about the efficiency, of course app built by PhoneGap runs much slower than local app.
So we changed plans after serveral weeks, and decided to develop a local app. The side effects are obvous. We have to redesign and implement the user interface on Android, rewrite the data access interface with CouchDB server, deal with dataflows, and decide the interaction between data and interface, and we have got less time.
Another challenge is that I have to learn some stuffs that I didn’t even hear about. These includes Git, PhoneGap, AngularJS, CouchDB. All the new stuffs sound interesting and inspire me to learn, but sometimes it becomes very boring when I get stuck. For example, I got sutck when I tried to upload files into server because I didn’t know how the server deals with my file(content-type, and encoding). Then I had to turn to Dan for help:), or in other cases use web developer’s tool to track the HTML request.
New Skills
In this project, I learned how to make use of GIT to manage my development process. But I should have updated my code more often, to save my changes to the server, and to inform my mentor Dan about my new improvement.
PhoneGap is the first new stuff for me to learn. It is a rapid mobile development platform based on HTML, CSS and JavaScript. The application developed on PhoneGap can be easily ported into other mobile systems. It also has access to devices’ camera, campus, contacts, documents, geolocation, network and so on.
Compared with relational database such as MySQL or MS-SQL, CouchDB is a fresh-new idea for me because it is NO-SQL and document based. It discomforts me at first, but then it becomes very interesting and easy to use. CouchDB can provide a more flexible data model than relational database, and provide scalability and performance advantages. And what’s more, it can spread its data across servers automatically, making it extremely suitable for distributed system.
AngularJS is another new JS framework I have learned. The most fashion thing about AngularJS is bi-directional data binding, to avoid complex callbacks to listeners. And the directives of AngularJS is very useful to define my own tags, so I can do something interesting with my browser. It also support other features such as model, MVC, dependency injection. These features are very different from what I have learned in jQuery, and are really cool. Although I didn’t write any AngularJS code in this internship, I would love to try it in my future work.
Also, I’m more familiar with Android development. I learned on how to operates a listview to show different types of items with different views, how to control the styles and themes of application, how to find the ID of views on system widgets through source code and more.
Future plans
There are 2 aspects that could be implemented in this project in the future. The first is about the encryption communication. For the sake of debugging, I didn’t encrypt and check the integrity of the message. But in fact, encryption is very important to avoid network sniffing, and also integrity checking is important to avoid message tampering.
The second is that multicast DNS (mDNS), which can be used to discover MediaGrid servers on LAN automatically. Usually, the IP of MediaGrid server is hard or unable to know manually, so mDNS will make MediaGrid much easier to use.
In these three internship, I not only mastered a lot of new skills and improved my programming skill, but also had the opportunity to know some excellent people and their excellent project. And their open-source spirit is a great encouragement for me.

Last, great thanks to Dan’s MediaGrid project and his kind help.

The video demo of MediaGridAndroid is located at MediaGridAndroidDemo

Interface redesign and improvement

This week, I mainly redesigned the chat interface, redesigned styles of MediaGrid, and added long polling function for Media, and beautified the local file explorer.
As for the chat interface, three main features are implemented: The first is bubble chat item. To implement this, I used the 9-patch PNG for the bubble. 9-patch PNG is a special PNG format which specifies how the image is scaled and how text is shown when the image has to fits different sizes. The introduction about 9-patch PNG can be found at http://radleymarx.com/blog/simple-guide-to-9-patch. The second is left or right alignment for user or one’s own chat item. And they also use different background colors and different bubbles. The third is that time is shown when the chat time is more than one minute from last shown time, rather than every time. So the pictures show the result:
p1p2
As for the styles of MediaGrid, I decided to use a blue-based styles. So the basic color is blue, and every widget uses blue, light-blue, steel-blue or sky-blue. And the colors for different file icons are also different.
p4p3

As for the long polling for Media, because I didn’t even know there’s also a polling function for Media, so I missed this part, and implemented it.

As for the local file explorer, I used the icons and colors that are consistent with ones in Media part.

p5

And I also fix some bug, and added the ProcessDialog for login, uploading, downloading.

p6

First Version

This week, I mainly finished server things: Server configure dialog, Implementation of switching room, separate message list for different recipient, font awesome icons for MediaGrid.

For the server configure, I save the server IP and port into SharedPreferences, so next time it shows user’s last configuration.

For the switching room, first an Alert dialog is shown for user to input a room id. Then it clears the message queue, message showing list, and interrupts three long polling thread including longPollingChat, longPollingUser, longPollingIM. Then saveUserDoc is recalled to restart a new room, and reset long polling thread.

1

For the separate message list, I created a HashMap to map the user name to its corresponding message list. Every time when message is reached, it is written into its message list. And if the message recipient is the same as the recipient that user choose, then message is shown to the list view.

23

For the font awesome part, the non-system font could be only add with java code dynamically. So I created a getFontAwesome() function in GlobalUtil to load the font data. And the most annoying part is the icon of an item in a list view. Because I’m unable to specify the font in the xml file of an item, I have to extend the SimpleAdapter’s getView() method to specify the font.

45

 

 

MediaGrid Notes

10 weeks have been past since the internship, it’s great fun working on this project and working with my mentor Dan. While doing this project, there are some difficulties and finally successfully find the way to work it out. In this blog, some conclusions are made for these ten weeks’ work to help the reader have a further understanding about this project. Here is the link for this project I’m doing now: https://github.com/Jescy/MediaGridAndroid

File structures:

CallBackBundle: callback interface for FileSelectionList’s OnItemClick.

ChatFragment: Chat Fragment, main chat interface.

ChooseMemberActivity: Chooses the recipient of the message.

CouchDB: Provides functions for data access of CouchDB.

GlobalUtils: Defines util functions and all kinds of constants.

HttpService: Provides http functions such as post, put, get, delete.

IPDialog: Shows dialog for server configure.

LoginActivity: Login interface.

MainActivity: Main Activity frame, includes chat and media fragment.

MediaFragment: Media list fragment.

MIMEType: MIME table.

OpenFileDialog: Shows the file explorer.

RTPullListView: A pull-down-to-refresh list view. Used to show messages and medias.

User: Defines classes such as State, Member, UserDoc, User.

1. When MediaGird is disconnected from network, the longPollingUser, longPollingChat, longPollingIM fails.

To stop this, the ConnectionTimeout exception is caught, and reconnect after sometime (GlobalUtil.RECONNECT_INTERVAL).

2. Unable to change the size of AlertDialog. An AlertDialog is used to configure the IP and port of the server. In order to keep it at a good size, I used this code but failed.

AlertDialog dialog = new AlertDialog.Builder(this).create();

LayoutParams params = dialog.getWindow().getAttributes();

width = 200;

height = 200 ;

getWindow().setAttributes(params);

show();

To change the size of an AlertDialog, we have to call setAttributes() after the method show(), so this code works.

AlertDialog dialog = new AlertDialog.Builder(this).create();

  1. show();
  2. LayoutParams params = dialog.getWindow().getAttributes();
  3. width = 200;
  4. height = 200 ;
  5. getWindow().setAttributes(params);
  1. To upload file using Android to MediaGrid.

3. I have implemented an App, with the function of uploading files to server. But in that application, I can both control the server side and the client side, so uploading it as text using base64 coding. But in MediaGrid, I don’t know how the server side will deal with format of the file. Finally, I got to know the way to do it by using Chrome web developer’s tool. To upload a file to MediaGrid using Http, we have do following stuffs:

  1> Get a new document ID from server.

POST http://127.0.0.1:5984/media/_design/media/_update/file

type: File

Attention that we have to get ID and Rev information from the header of the response: “X-Couch-ID” and “X-Couch-Update-NewRev”. This is really weird.

 2> Compose the URL of request:

http://127.0.0.1:5984/media/f7ccb13cca3f5a136679eec50c000f37?_attachments=C%3A%5Cfakepath%5CRedSkirt.si&_rev=1-3fc78757a5cf29ac0c80515c45953745

f7ccb13cca3f5a136679eec50c000f37 is the ID of document, while 1-3fc78757a5cf29ac0c80515c45953745 is the version of the document. Both are required to change the document. And the _attchments is the name of the attachment.

   3> Compose the Payload of request.

  1. ——WebKitFormBoundaryYKjsIrgph5DK
  2. Content-Disposition: form-data; name=”_attachments”; filename=”RedSkirt.si” Content-Type: application/octet-stream
  3. (here’s binary of the file content)
  4. ——WebKitFormBoundaryYKjsIrgph5DK Content-Disposition: form-data; name=”_rev”
  5. 1-3fc78757a5cf29ac0c80515c45953745
  6. ——WebKitFormBoundaryYKjsIrgph5DK–

The ——WebKitFormBoundaryYKjsIrgph5DKZaZb is the boundary of the Payload, it could be anything else started by “–” and end by “\r\n”.

For the first part of request, the content-Disposition is “form-data”, name is “_attachments”, filename is name of file, and Content-Type is MIME type of the file. Then comes the file content.

For the second part of request, it contains the _rev information of the file.

   4> Compose the whole Request.

POST http://127.0.0.1:5984/media/f7ccb13cca3f5a136679eec50c000f37?_attachments=C%3A%5Cfakepath%5CRedSkirt.si&_rev=1-3fc78757a5cf29ac0c80515c45953745

Header:

Content-Type:multipart/form-data;boundary=—-WebKitFormBoundaryYKjsIrgph5DK

The most important in Header is to specify the content-type, with boundary in it. And the whole code is included in HTTPService.doPostFile() in HTTPService.java

4. To download the file from server:

With the URL in hand, I opened a URLConnection, and get the input stream, the write the stream to File. The whole code is included in HTTPService.doDownloadFile() in HTTPService.java.

5. SwitchRoom implementation. Because the room changed, I have to stop the current longPollingUser, longPollingIM, longPollingChat and then create new polling connections using the new room name. However, I am not able to stop the polling thread with Thread.Stop() or Thread.Destroy(), they are both deprecated in current version, and if I use the methods, UnsupportedOperation exception is thrown. Then Dan suggested me to catch the exception, it does work, but maybe be against the designer of Java. Then I thought Thread.interupt() could stop the thread, but it does not work. So for now, I just left the old polling running with the new one, and when the old polling returns, the whole thread exits.

6. Server Configure dialog. This dialog is also implemented using AlertDialog. And in order to show a more complex view in AlertDialog rather than just an EditText, I create a view dynamically, by calling :

View viewConfig = LayoutInflater.from(context).inflate(R.layout.server_config, null);

 

And the corresponding layout is defined in R/layout/server_config.xml. This is a very powerful function to create any view you like.

7. In an AlertDialog, we click the OK button, it will execute the callback function we specified in setPositiveButton(), and the dialog is dismissed. But sometimes, we invalidate the input, and find it illegal, so we want to show a promote message to user, and then remains the dialog. How to stop the dialog dismissed? We need to get the OK button, and rewrite the OnClickListener by our own.Then you can dismiss it whenever you want. Here’s the sample code:

final AlertDialog alertDialog = new AlertDialog.Builder(context)

                .setTitle(title)

             .setView(viewConfig)

             .setPositiveButton(android.R.string.ok,

                     new DialogInterface.OnClickListener() {

                         public void onClick(DialogInterface dialog,

                                 int which) {

                               //do nothing here

                            }

                 });

//rewrite this method

alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(

                new View.OnClickListener() {

                    @Override

                 public void onClick(View v) {

                     if (WantToDismiss)

    alertDialog.dismiss();

}

8. MIME type of a file. In order to get MIME type of a file, we can use javax.activation.MimetypesFileTypeMap, Apache Tika to get the MIME type by extension name. While JMimeMagic will get the MIME type by magic headers rather than just extension. And I used a MIME type table, and search the MIME type by extension.

9. This class is defined in OpenFileDialog.java, and is used to explore the files and directories of the file system. This dialog is also shown in AlertDialog. The point is that I define a CallbackBundle interface, to specify the operation after an item is clicked. This is very useful because I can pass the selected file to CallBackBoundle and call it automatically after the click operation. The whole code is in OpenFileDialog.java, and the brief code is like this:

CODE

//callbackbundle.java

public interface CallbackBundle {

    abstract void callback(Bundle bundle);

}

//OpenFileDialog.java

static class FileSelectView extends ListView {

        private CallbackBundle mCallback = null;

public FileSelectView(Context context, CallbackBundle callback){

         mCallback= callback;

}

@Override

        public void onItemClick(AdapterView<?> parent, View v, int position,long id) {

if (fl.isFile()) {

                    // parameters for the callback

                    Bundle bundle = new Bundle();

  1. putString(“path”, pt);
  2. putString(“name”, fn);

                    // call the callback

  1. this.mCallback.callback(bundle);

}

}

 

//use the FileSelectView

final FileSelectView fileSelectView = new FileSelectView(context,

                new CallbackBundle(){

         void callback(Bundle bundle){

//retrieve path and name, and do something

}

});

10. In new version of Android, if we access the Internet on main thread, a NetworkOnMainThread will be thrown, and the access will fail. So the Android designer force us to access internet on a separate thread. However, the UI updates are not allowed in non-UI thread (non-main thread). How to update UI after the network access returns?

I used the handler and message to solve this. First, define a message Handler to handle message in main thread. Then the network access happens in a network thread, and after getting the access result, send a message to the handler, with the result, and the handler in main thread updates the UI.

CODE

// message handler

    private Handler myHandler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            switch (msg.what) {

            case GlobalUtil.MSG_LOAD_SUCCESS:

             // message contains network results

             // do update UI

            }

}

}

new Thread() {

    public void run() {

         //access internet

        resJson = CouchDB.upload(id, rev, path);

        myHandler                             .sendEmptyMessage(GlobalUtil.MSG_UPLOAD_SUCCESS);

}.start();

So I defined plenty of messages to control the interactions between main thread and network threads. The messages are defined in GlobalUtil.java, starting by MSG_. Take the login process for example, the operations are getSession, Register, Login, getUserDoc, updateUserDoc, getDBInfo, longPollingUser, longPollingIM, longPollingChat in a row, so messages include the following:

   public static final int MSG_LOGIN_SUCCESS = 9;

public static final int MSG_LOGIN_FAILED = 10;

public static final int MSG_SAVE_USER_DOC_SUCCESS = 11;

public static final int MSG_SAVE_USER_DOC_FAILED = 12;

public static final int MSG_GET_DB_INFO_SUCCESS = 13;

public static final int MSG_GET_DB_INFO_FAILED = 14;

public static final int MSG_POLLING_USER = 15;

public static final int MSG_POLLING_CHAT = 16;

public static final int MSG_POLLING_IM = 17;

public static final int MSG_CHAT_LIST = 18;

public static final int MSG_REGISTER_NAME_TAKEN = 19;

public static final int MSG_GET_SESSION_SUCCESS = 20;

public static final int MSG_GET_SESSION_FAILED = 21;

public static final int MSG_GET_USER_DOC_SUCCESS = 22;

    public static final int MSG_REGISTER_SUCCESS = 23;

11. The previously used user name, password, and server information is all saved in local using SharedPreferences. SharedPreferences is like localStorage in a browser. It’s convenient for us to save previously used information like user name. The result is saved in xml file in local file system.

SharedPreferences sp = LoginActivity.this.getSharedPreferences(

                “SavedNames”, MODE_PRIVATE);

Editor editor = sp.edit();

for (int i = 0; i < names.size(); i++) {

  1. putString(“name” + i, names.get(i));
  2. putString(“password” + i, passwords.get(i));

}

editor.commit();

12. Most of the requests content-type are “application/json”, while the Content-Type of createFileDocument() must be application/x-www-form-urlencoded. This really makes me confused.

13. The most important challenge is to figure out the data flow of MediaGrid. Very great thank to Dan’s dataflow diagram, https://github.com/dismantl/MediaGrid/wiki/Chat-program-data-flow-diagram. It’s so detailed, and helps so much. And the Chrome’s web developer’s tool is also a great tool for me to figure out how to interact with server.

Chat Part Implementation

Chat&IM message

This week, I mainly completed functions of Chat&Instant message sending and receiving. And now, Android client can send and receive both chat and instant message among other devices.

As for the chatting part, one can send a message to everyone in his chat room. When the send buton is clicked, a queueMessage is called to queue a message, and then postQueue is called to post message queue to server one by one. And at current, there’s no encryption in communication.

As for the IM part, one can send a message to someone in his chat room privately. As for choosing the recepiant, I changed the orginal design a little. Instead of putting the “all” button at the left of input box, I put a small icon at the top of ChatListFragment, and when user clicks it, a user list is shown. The first row is “Everyone” indicating public message, otherwise indicating private instant message.

When a chat message reaches CouchDB, the longpollingChat returns the first and last id of new messages. Then a getMessage is called to retrieve message contents from server, then message is shown to Android client. When an instant message reaches CouchDB, the longpollingIM returns the message document, including message receiver, sender, and message content. Then it is directly shown to Android client.

Here shows the result of chat message.

12.0

Here shows the recipient list after click “to: Everyone”:

3.0

Here shows the instant message:

44.0

 

 

Media Part Implementation

Uploading file

This week, I mainly completed the implementation of Media part, including uploading file, downloading file and making directory. And I was starting to code for Chat. So for the rest of the work are Chart part we will get our first version. (even there is login part as well, but quite small part)

According to Dan’s uploading tutorial, I finally learned how to upload a file. I used to do file uploading by coding for both client and server by using Base64, but I’ve never coded for a just client, so it’s a little tricky for me. But in fact, I just need to put the file content in payload of HTTP, and boundry it with something.

In order to pick a file to upload, I used a OpenFileDialog to explore the files of Android client. When a file is clicked in OpenFileDialog, the CouchDB.upload is executed to upload it to current directory. And the same dialog is used for choosing a direcotry for the file to download. When a file in the Media fragment, the dialog is shown for choosing a directory, and when “select this directory” is clicked, the downloading process is started.

As for making directory, it’s pretty easy. But it’s a little tricky when I create a file in root direcotry, because instead of using null or empty string for parameter dir, no parameter dir should be passed to server.

The images shows the dialog:

1> Choose a directory to download file:

2

2> Choose a file to upload:

3

3> Make a new direcotry:

4