- eBook reader that can open pdf files
- Synchronize two devices that can show same part of the document via Wi-Fi Direct
- Markup on the pdf file and share the view to the other device
Group members: Luwen Miao, Xun Jin
LinkSrevice Activity Finding peer device and connecting function is performed in LinkService.java class. In order to implement this function we first obtained an instance of WifiP2pManager and registered our application with the Wi-Fi Direct framework by calling initialize(). This method returns a WifiP2pManager.Channel, which is used to connect your application to the Wi-Fi Direct framework. We also created an instance of your broadcast receiver with the WifiP2pManager and WifiP2pManager.Channel objects along with a reference to our activity. This allows our broadcast receiver to notify our activity of interesting events and update it accordingly. To discover peers that are available to connect to, call discoverPeers() to detect available peers that are in range. The call to this function is asynchronous and a success or failure is communicated to your application with onSuccess() and onFailure() if you created a WifiP2pManager.ActionListener. The onSuccess() method only notifies you that the discovery process succeeded and does not provide any information about the actual peers that it discovered. If the discovery process succeeds and detects peers, the system broadcasts the WIFI_P2P_PEERS_CHANGED_ACTION intent, which you can listen for in a broadcast receiver to obtain a list of peers. When your application receives the WIFI_P2P_PEERS_CHANGED_ACTION intent, you can request a list of the discovered peers with requestPeers(). When you have figured out the device that you want to connect to after obtaining a list of possible peers, call the connect() method to connect to the device. This method call requires a WifiP2pConfig object that contains the information of the device to connect to. You can be notified of a connection success or failure through the WifiP2pManager.ActionListener. Once a connection is established, we jumped into BaseViewerActivity to create socket and send data.
Problems solved:
- Wi-fi direct peer IP address When a Wi-Fi direct connection is established between two devices, we can get wifip2p information about this connection, however, only the group owner’s socket IP address is available at this particular time, it causes problems because we cannot send initial data from the non-group owner peer. And it can be solved by receiving data in the group owner device and analyzing the client socket after the first transmission.
- Socket timeout When we first built our app, it is quite likely to crash whenever we are trying to connect the other device, and it turned out that we should set a much large socket connection timeout in Wi-Fi Direct connection. We set 5000ms in this project.
- Burst socket input & output stream Since we have to listen to the other device continuously, the data stream can be bursty sometimes, especially when one device is trying to send coordinate data containing drawing path. In order to solve this problem, we tried to control output stream speed in sender side by setting multiple parameters such as an array list to store coordinates and Boolean variable to check the availability of the receiver side socket.
- Thread conflict between multiple activities In this project, we used several activities so that we can clarify functionality of each class. Just after we finished turning page sync, we tried to sync the drawing path sync function. The application crashed every time we tried to draw on top of our pdf file. We finally found out that it was the thread conflict. You cannot simply update UI from non-UI thread to perform drawing function. It was the invalidate() method that causing problems. We have to call postInvalidate() instead of invalidate() because we want to re-draw our view from non-UI thread.
BaseBrowser Activity This is the activity to filter the PDF files and display them on a list. We use the “FileFilter” which is the public interface for filtering File objects based on their names or other information, then we get the files with the name end with “pdf”, and return the path name of the files in the Tab, in order to show the list in the Tab, we create a “BrowserAdapter” extends from the BaseAdapter to fill the Tab with the list and set the OnItemClickListener to the Tab. So, after the PDF files being selected, the application will jump to the BaseViewerActivity. As we need to use the master device, which is the group owner of this Wi-Fi-Direct connection group, to create a server socket to wait for the client socket that created by the client device to connect, the master device should open the PDF file firstly. BaseViewer Activity In the BaseViewer Activity, we firstly connect the two devices by using the client socket and the server socket, the IP address of the group owner that will be used to create the server socket is got by using the Bundle to deliver the information from the LinkServive Activity. After the devices being connected, the client thread and the server thread will start correspondingly. After the threads started, we will use a “while(true)” loop, which is the infinite loop to read the bytes in the buffer sent from the other device. The “buffer” is a byte[] that can save 10000 bytes, we use it to store the messages that will be delivered between the two devices. For the turning on page function, we use two buttons named “prev” and “next”, and set the OnClickListener correspondingly. When the users click on the buttons, we will use the nextpage() or prevpage() method to call the changeCanvasLeft(),changeCanvasRight(), goToPage() methods to change the document view and the canvas view, and at the same time, to send the messages to the other device to respond to this action, which will accomplish the synchronization. We also add the “Clear” function in the option list to clear the Bitmap made by the users, we do this by recycle the old one and create a new Bitmap.
Challenges: The most challenge here is to handle the synchronization of the turn on page function. Here we use a Boolean variable named “canChangePage” to restrict the application to turn on the pages after it send out the “prev” or “next” message. Initiate, we set the “canChangePage” variable to be “true”, and after we click on the “prev” or “next” button, the variable will be set to “false”. In the while(ture) loop that is continuously reading the messages sent from the other device, we only call the nextpage() or prevpage() method when the application received a “next” or “prev” message and the “canChangePage” is “true”, so when the user click on the turn on page buttons, the nextpage() or prevpage() method will be called again as the “canChangePage” is set to “false”. After the device received the message to turn on the pages, it will call the nextpage() or prevpage() method and send back a “done” message to set the “canChangePage” in the other device to be “true”, which will enable the turn on page function again. CanvasView View This view is a draw able view where the users can make marks on it. We use canvas to draw on the mdrawBitmap = Bitmap.createBitmap(1000, 2000, Bitmap.Config.ARGB_8888) with the “mPaint” which is the red paint of this device the “uPaint” which is the green paint of the other device, these variables will be initiated at the time the BaseViewer Activity add the canvas view. Each time the user turn on the pages, the canvas view will firstly save the Bitmap of the current page into “zzz” folder in the format of “PNG”, can check if there is already a “PNG” file for the next page, if not, it will create a new Bitmap for that page; if exists, it will decode the “PNG” file into Bitmap and use it to mark again. In this view, we use “onTouchEvent” to catch the action as touch down, touch move, and touch up. Each time the user point on the screen, the touch_start() method will save the x, y coordinates of that point to start record the path made by the touch_move() method, and use the “quadTo()” function to draw the path, after the user finish the marking, the touch_up() method will be called, and use postInvalidate() (The reason we use the postInvalidate() method, not the invalidate() method, is that only the static method can be used by the activity which is not initially create the canvas) to re-draw the Bitmap, which means to show the marking made by the users.
Challenges: Here are some challenges when we desire to deliver the x, y coordinates made by the user to the other device, as there is delay for the thread to send and read the byte[]. We solve the problem by using a tricky method. 1. Every time the user move the finger when marking, we save the x, y coordinates into a string, and after the touch_up() method be called, we send the string to the other device. At the other device, we firstly save the string into an ArrayList, then we use the splitPoints()method to split the string in the ArrayList to get the x, y coordinates to draw on the Bitmap, each string in the ArrayList is a draw path made by the other device. 2. Another problem is that as we have to create a Bitmap for every page, then we have encountered the “OutOfMemory” problem. We solved this problem by recycle the old Bitmap and decode the “PNG” file or create a new Bitmap. 3. The other problem is that when we what to decode the “PNG” files and use them to draw path, it will pop up a “Immutable Bitmap” problem, because the canvas cannot draw on the working Bitmap, so we copy the Bitmap created by decoding the “PNG” files to a new Bitmap, then we solved the problem. DocumentVie View This view is a view to show the PDF files’ context, we use the decode service extends from the NDK to decode the PDF files, and use the RectF to scroll on the document view, only show the context when the RectF intersects with the TargetRectF, which is the RectF the application scrolled to, and this is how we can accomplish the turn on page function. Future work We want to solve the delay problem and add more users to this sharing group.