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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s