SM App structure
๐ Folder structure
๐ Activities
CameraActivity
Tasks:
- Display camera preview
- Take photos
- Discover/connect to servers
- Manage settings
Noteworthy code:
onCreate()
- hideStatusAndActionBars() โ hide status/action bars
- setContentView() โ load layout and views
- verifyPermissions() โ verifies all permissions
- read/write external storage
- access wifi state
- internet
- camera
- setupSharedPref() โ initializes the PreferenceManager
- createDeviceID() โ creates unique ID for the device when using the app for the first time
- initViews() โ assigns views to variables
- setViewListeners() โ sets listeners
- start FragmentHandler
- start ServerConnection
- start CameraInstance
capturePhoto()
- sets
isCapturing = true
- extracts bitmap from camera preview (CameraInstance)
- converts image to greyscale (PhotoConverter)
- sends image to server (Http)
- listens to server response (โ sendBitmap())
fillUpImageList()
Loads all images from the app directory into memory so that GalleryFragment doesnโt lag when opened up the first time
updateConnectionStatus()
- changes colour and text of the
mSelectDeviceButton
based on the connection status (ServerConnection.isConnectedToServer):- true: green, โHOSTNAMEโ
- false: red, โnot connectedโ
onPermissionDenied()
- sets
isCapturing = false
- shows toast to the user, informing them that the matching permission was denied
onMatchResult(matchID, img)
- sets
isCapturing = false
- starts ResultsActivity
ResultsActivity
Tasks:
- Display the cropped screenshot that was returned from the server
- optional: download and display full screenshot when the user requests it
- share/save screenshots
- return to CameraActivity
Noteworthy code:
onCreate()
- get serverURL/matchID/images from Intent
- check whether it received a cropped image
- if not: enter fullScreenshotOnlyMode
- no switching between screenshots
- saving both images not allowed
- if not: enter fullScreenshotOnlyMode
- downloadFullScreenshotInThread()
downloadFullScreenshotInThread()
- starts download of full screenshot in a new thread
- โ onScreenshotDownloaded()
- saves screenshot to app directory, displays it on ImageView
saveCurrentPreviewImage()
- saves the currently displayed image to the phone gallery
- hasSharedImage = true โ when closing the activity, the image(s) will also be saved to the app storage โ accessed by GalleryFragment
saveBothImages()
- only executed when the cropped and full screenshot are available
- saves both images to phone gallery
- hasSharedImage = true โ when closing the activity, both images will also be saved to the app storage โ accessed by GalleryFragment
shareImage()
- opens android share context menu, allowing users to share the currently displayed screenshot
- hasSharedImage = true โ when closing the activity, both images will also be saved to the app storage โ accessed by GalleryFragment
๐ Fragments
๐ Rotation Fragments
RotationFragment
Tasks:
= abstract class that extends Fragment
and adds extra functionality allowing the fragment to change orientation without defining an extra layout
- rotation works by:
- removing the fragment (โ removeThisFragmentForRotation())
- getting a rotated view of the fragment (โ rotateView())
- attaching the rotated fragment
Noteworthy code:
rotateView(rotationDeg: Int, v: View)
called when the view gets created (or reloaded), returns view thats rotated by rotationDeg
degrees
removeThisFragmentForRotation()
called when the view should be rotated (detach, rotate, attach), removes the view without an animation
removeThisFragment(removeBackground: Boolean)
called when the fragment should actually be closed (not rotated)
- removes the dark background of the fragment (if
removeBackground
= true) - plays animation
SelectDeviceFragment
Tasks:
- display all available servers
- allow users to connect to servers on the list by clicking on them
Noteworthy code:
initServerList()
- gets server list from ServerConnection object in the CameraActivity
- (if not null): adds all servers from the list to the
mServerList
variable - (else): retry after 100ms
initViews()
- initializes all views
- attaches ArrayAdapter that fills up the ListView with the elements from
mServerList
- sets onClickListener that listens to clicks on items of the ListView
- onClick:
- change color of clicked item
- call setServerUrl(hostname) of the ServerConnection object in the CameraActivity with the url of the clicked server item as parameter
- onClick:
GalleryFragment
Tasks:
- show a list of all cropped (and full) screenshots the user has taken and then either saved/shared in the ResultsActivity
- allow users to click on screenshots โ GalleryPreviewFragment
Noteworthy code:
initViews()
- initializes all view variables
- attaches GridBaseAdapter to the GridView
GalleryPreviewFragment
Tasks:
- allow users to save/share previously matched screenshots like in ResultsActivity
Noteworthy code:
similar to ResultsActivity
getFilesFromBundle
- retrieves the provided image files from the bundle
- increments
numberOfAvailableImages
for each image retrieved
โธ Fixed Fragments
ErrorFragment
Tasks:
- notify the user when there is not match result
- allow user to open the feedback fragment
- allow user to request and show the full screenshot
FeedbackFragment
Tasks:
- allow user to send feedback to a server
SettingsFragment
Tasks:
- allow user to modify app preferences (currently only matching mode)
- the preference layout (and the keys to access the values) are stored in โ/res/xml/rood_preferences.xmlโ
List of preferences keys:
- settings_algorithm_key: โALG_MODE_KEYโ (stored in โ/values/strings.xmlโ, access via getString(@StringRes int resId))
- settings_logging_key: โLOG_MODE_KEYโ (stored in โ/values/strings.xmlโ, accessed via getString(@StringRes int resId))
Example:
- get algorithm mode key:
MATCHING_MODE_PREF_KEY = getString(R.string.settings_algorithm_key)
Accessing preferences:
- get default shared preferences via:
val sp = PreferenceManager.getDefaultSharedPreferences(context: Context)
- access the preferences via key value principle e.g.:
sp.getBoolean(PREFERENCE_KEY_HERE: String, defaultValue: Boolean)
Noteworthy code:
onCreatePreferences()
- loads
root_preferences
layout which lets users change predefined preferences
๐ Helpers
CameraActivityFragmentHandler
Tasks:
- outsource code to launch fragments from the CameraActivity to an extra file
- launch any CameraActivity fragments
Noteworthy code:
openFragment(fragment: Fragment, containerID: Int, transition: Int? = null)
- attaches the provided fragment in the given container (with an animation)
rotateGalleryFragment()
- removes the GalleryFragment if it is open (removeThisFragmentForRotation())
- re-attaches (openFragment())
- re-attaches the GalleryPreviewFragment if it was open
PhotoConverter
Tasks:
- rescale a photo and convert it to grayscale
Utils
Tasks:
- provide various utility functions
Noteworthy code:
CompareSizesByArea: Comparator
- comparator class used by CameraInstance to compare camera preview sizes
๐ Network
ServerConnection
Tasks:
- start network discovery if not connected to a server, update
mServerUrlList
- send heartbeats to a server if connected to one
- update UI in CameraActivity accordingly
Noteworthy code:
mHandler: Handler
- a handler object that manages all runnables based on the
msg: Message
it receives:END_ALL_THREADS
โ kills all active runnablesSTART_DISCOVER
โ kills heartbeatRunnable, starts discoverRunnableSTART_HEARTBEAT
โ kills discoverRunnable, starts heartbeatRunnable
startHeartbeatThread()
- sends a
msg: Message
to the mHandler Handler that starts the heartbeatRunnable
startDiscoverThread()
- sends a
msg: Message
to the mHandler Handler that starts the discoverRunnable
discoverRunnable: Runnable
- a runnable that runs the server discovery (โ discoverServersOnNetwork) every 5s
- callback: onServerURLsGet()
onServerURLsGet(servers: List<Pair<String, String>>)
- if the returned list is not empty:
- updates
serverUrlList
and checks ifmServerURL
is in the list - if it is: change connection to connected (โ onConnectionChanged(true))
- updates
onConnectionChanged(isConnected: Boolean)
isConnected = true
: startHeartbeatThreadisConnected = false
: startDiscoverThread- calls updateConnectionStatus() in CameraActivity
heartbeatRunnable: Runnable
- a runnable that sends a heartbeat request (โ sendHeartbeatRequest()) to the connected server every 5s
- if heartbeat fails โ onHeartbeatFail
onHeartbeatFail()
- notifies the user via Toast that the server disconnected
- calls onConnectionChanged(false)
setServerUrl(hostname: String)
- sets
mServerURL
to an url that belongs to the hostname provided in the parameters viahostname: String
NetworkDiscovery
Tasks
- provide functions for discovering servers in the local network
Noteworthy code:
discoverServersOnNetwork()
- todo: @TF
Http
Tasks
- works as a helper class for all HTTP related tasks:
- send bitmap to server โ sendBitmap()
- send heartbeats โ sendHeartbeatRequest()
- send feedback to server โ (from FeedbackFragment)
- request permission from server to match screenshots โ requestPermission()
- send logs to logging server โ sendLog()
Noteworthy code:
sendBitmap(bitmap, serverURL, activity, matchingOptions, permissionToken)
- encodes
bitmap
(= camera image) to a base64 string and appends it to JSON - appends
matchingOptions
,deviceName
,deviceID
to JSON - adds
permissionToken
, if not empty, to JSON - sends request, waits for these possible responses:
- error โ call onPermissionDenied
- response with url param โerrorโ = โpermissionRequiredโ โ requestPermission()
- response with url param โhasResultโ = true โ decode base64 to bitmap call onMatchResult()
- result with url param โhasResultโ = false โ start ErrorFragment
sendHeartbeatRequest(serverURL, activity)
- makes GET request to the servers heartbeat route
- if it returns a response, do nothing
- if it returns an error, call onHeartbeatFail
requestPermission(bitmap, serverURL, activity, matchingOptions)
- @TF
๐ Views
CameraInstance
Tasks
- provide a helper class for the CameraActivity that works abstracts the Camera 2 API:
- initializing the TextureView
- choosing a fitting preview size
- setting up camera outputs
- listen to camera state callbacks
Noteworthy code:
initializeTextureView()
- initializes the TextureView found in the CameraActivity layout
- sets callback object that listens to changes of the TextureView
onSurfaceTextureAvailable
= the starting point of the application code
openCamera(width: Int, height: Int)
- setupCameraOutputs() โ obtained
mPreviewSize
- checks for CAMERA permission
- opens camera using CameraManager, mCameraDeviceStateCallback as callback
setupCameraOutputs (width: Int, height: Int)
- helper function for determining the optimal camera output size (= preview size)
- checks each output resolution for each camera of the device and picks the biggest one with a 4:3 ratio โ
mPreviewSize
mCameraDeviceStateCallback
= a callback object for listening to camera device state changes:
- onOpened() โ createCameraCaptureSession
- onDisconnected() โ closes cameraDevice
- onError() โ finishes CameraActivity
createCameraCaptureSession()
- creates a Surface from
mTextureView
- chooses this Surface as target for displaying the camera preview
- creates a CaptureRequest, mCameraDeviceStateCallback as callback object
mCameraCaptureSessionStateCallback
= a callback object for listening to CaptureSession callbacks
- onConfigured()
- โ sets a repeating request on the CameraSession
- โ preview gets displayed on the Surface of mTextureView
- onConfigureFailed() โ throws RuntimeException
GridBaseAdapter
Tasks
- provide all Views (= screenshot thumbnails) for the GalleryFragment
Noteworthy code:
getView(position: Int, convertView: View, parent: ViewGroup)
- returns a View based on the provided
position
- sets an onClickListener on each returned View โ onClick:
openGalleryPreviewFragment(firstImageFile, secondImageFile)
of CameraActivityFragmentHandler
ListRowHolder
- private helper class for storing the information about which images are stored in a returned View
๐ชต Logger
Logger
- @TF