@@ -7,32 +7,45 @@ import android.app.Dialog
import android.app.DialogFragment
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.os.AsyncTask
import android.os.Bundle
import android.provider.MediaStore
import android.support.v4.content.FileProvider
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.net.toUri
import com.github.niqdev.mjpeg.DisplayMode
import com.github.niqdev.mjpeg.Mjpeg
import com.github.niqdev.mjpeg.MjpegSurfaceView
import com.microsoft.projectoxford.face.FaceServiceRestClient
import com.microsoft.projectoxford.face.rest.ClientException
import com.squareup.picasso.Picasso
import com.trakam.trakam.R
import com.trakam.trakam.activities.ImagePreviewActivity
import com.trakam.trakam.activities.SettingsActivity
import com.trakam.trakam.data.Log
import com.trakam.trakam.db.AppDatabase
import com.trakam.trakam.db.PeopleDao
import com.trakam.trakam.db.Person
import com.trakam.trakam.fragments.base.BaseFragment
import com.trakam.trakam.services.OnLogEventListener
import com.trakam.trakam.services.ServerPollingService
import com.trakam.trakam.util.*
import okhttp3.Request
import rx.Subscription
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import java.lang.ref.WeakReference
import java.text.DateFormat

class RecentActivityFragment : BaseFragment(), OnLogEventListener, View.OnClickListener {
@@ -195,14 +208,7 @@ class RecentActivityFragment : BaseFragment(), OnLogEventListener, View.OnClickL
R.id.recent_activity_list_root -> {
val pos = mLinearLayoutManager.getPosition(v)
if (pos != RecyclerView.NO_POSITION) {
val host = activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_HOST,
PrefKeys.Server.Default.SERVER_HOST)
val port = activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_PORT,
PrefKeys.Server.Default.SERVER_PORT)
val url = "http://$host:$port/%s.jpg".format(mRecyclerViewAdapter[pos].log.uuid)
EventPictureViewerDialogFragment.newInstance(url)
EventPictureViewerDialogFragment.newInstance(mRecyclerViewAdapter[pos].log)
.show(fragmentManager, EventPictureViewerDialogFragment.TAG)
}
}
@@ -250,13 +256,18 @@ private class MyRecyclerViewAdapter(private val context: Context,
.build()

private val mUrl: String
private val mRegularTextColor: Int
private val mBlackListTextColor: Int

init {
val host = context.getDefaultSharedPreferences().getString(PrefKeys.Server.KEY_SERVER_HOST,
PrefKeys.Server.Default.SERVER_HOST)
val port = context.getDefaultSharedPreferences().getString(PrefKeys.Server.KEY_SERVER_PORT,
PrefKeys.Server.Default.SERVER_PORT)
mUrl = "http://$host:$port/%s.jpg"

mRegularTextColor = context.getAttrColor(R.attr.primary_text_color)
mBlackListTextColor = Color.parseColor("#C2185B")
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
@@ -285,6 +296,13 @@ private class MyRecyclerViewAdapter(private val context: Context,
} else {
holder.name.text = listItem.log.firstName
}

if (listItem.log.blacklisted) {
holder.name.setTextColor(mBlackListTextColor)
} else {
holder.name.setTextColor(mRegularTextColor)
}

holder.timeStamp.text = DateFormat.getDateTimeInstance().format(listItem.log.timestamp)

Picasso.with(context)
@@ -309,32 +327,237 @@ private class MyRecyclerViewAdapter(private val context: Context,
internal class EventPictureViewerDialogFragment : DialogFragment() {
companion object {
val TAG = EventPictureViewerDialogFragment::class.qualifiedName
private val KEY_URL = TAG + "_key_url"

fun newInstance(url: String): EventPictureViewerDialogFragment {
val args = Bundle()
args.putString(KEY_URL, url)

fun newInstance(log: Log): EventPictureViewerDialogFragment {
val frag = EventPictureViewerDialogFragment()
frag.arguments = args
frag.mLog = log
return frag
}
}

private lateinit var mLog: Log

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = inflateLayout(R.layout.event_picture_viewer_dialog)
val imageView = view.findViewById<ImageView>(R.id.imageView)

val url = arguments!!.getString(KEY_URL)
val host = activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_HOST,
PrefKeys.Server.Default.SERVER_HOST)
val port = activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_PORT,
PrefKeys.Server.Default.SERVER_PORT)
val url = "http://$host:$port/%s.jpg".format(mLog.uuid)

Picasso.with(activity!!)
.load(url)
.fit()
.centerCrop()
.placeholder(R.drawable.ic_account_circle_grey_700_48dp)
.into(imageView)

val dialogBuilder = AlertDialog.Builder(activity!!)
.setView(view)

if (mLog.firstName == "Unknown person") {
dialogBuilder.setPositiveButton("Whitelist", { _, _ ->
GetNameDialogFragment.newInstance(mLog, false)
.show(fragmentManager, GetNameDialogFragment.TAG)
})

dialogBuilder.setNegativeButton("Blacklist", { _, _ ->
GetNameDialogFragment.newInstance(mLog, true)
.show(fragmentManager, GetNameDialogFragment.TAG)
})
}

return dialogBuilder.create()
}
}


class GetNameDialogFragment : DialogFragment() {
companion object {
val TAG = GetNameDialogFragment::class.qualifiedName

fun newInstance(log: Log, blackList: Boolean): GetNameDialogFragment {
val frag = GetNameDialogFragment()
frag.mLog = log
frag.mMarkAsBlacklist = blackList
return frag
}
}

private lateinit var mLog: Log
private var mMarkAsBlacklist = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = inflateLayout(R.layout.get_name_dialog)
val editText = view.findViewById<EditText>(R.id.editText)
isCancelable = false
return AlertDialog.Builder(activity!!)
.setTitle(getString(R.string.name))
.setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, { _, _ ->
val name = editText.text.toString()
if (name.isEmpty()) {
activity!!.showToast("Name is empty!")
} else {
SendToAzureProgressDialog.newInstance(mLog, name, mMarkAsBlacklist)
.show(fragmentManager, SendToAzureProgressDialog.TAG)
}
dismiss()
})
.setNegativeButton(android.R.string.cancel, { _, _ ->
dismiss()
})
.create()
}
}

internal class SendToAzureProgressDialog : DialogFragment() {
companion object {
val TAG = SendToAzureProgressDialog::class.qualifiedName

fun newInstance(log: Log, newName: String, blackList: Boolean): SendToAzureProgressDialog {
val frag = SendToAzureProgressDialog()
frag.mLog = log
frag.mMarkBlacklist = blackList
frag.mName = newName
return frag
}
}

private lateinit var mName: String
private lateinit var mLog: Log
private var mMarkBlacklist: Boolean = false
private var mSendTask: SendTask? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = inflateLayout(R.layout.progress_dialog)
val textView = view.findViewById<TextView>(R.id.message)
textView.text = "Processing..."

isCancelable = false
return AlertDialog.Builder(activity!!)
.setView(view)
.setCancelable(false)
.create()
}

override fun onStart() {
super.onStart()
if (mSendTask == null) {
mSendTask = SendTask(this, mLog, mName, mMarkBlacklist)
mSendTask?.execute()
}
}

class SendTask(frag: SendToAzureProgressDialog,
log: Log,
private val mNewName: String,
private val mMarkAsBlacklist: Boolean) : AsyncTask<Any, Any, Pair<Boolean, String>>() {

private val mUrl: String
private val mFragRef = WeakReference(frag)
private val mDao: PeopleDao

init {
val host = frag.activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_HOST,
PrefKeys.Server.Default.SERVER_HOST)
val port = frag.activity!!.getDefaultSharedPreferences()
.getString(PrefKeys.Server.KEY_SERVER_PORT,
PrefKeys.Server.Default.SERVER_PORT)
mUrl = "http://$host:$port/%s-face.jpg".format(log.uuid)

mDao = AppDatabase.getInstance(frag.activity!!).peopleDao()
}

override fun doInBackground(vararg params: Any): Pair<Boolean, String> {
// get the face
val trainResult = trainFace()
if (trainResult.first && mMarkAsBlacklist) {
val name = mNewName.trim()
if (name.contains(' ')) {
val firstNameLastName = StringSplitter.splitOnEmptySequence(name)
mDao.insert(Person(firstName = firstNameLastName[0], lastName = firstNameLastName[1]))
} else {
mDao.insert(Person(firstName = name, lastName = ""))
}
return Pair(true, "")
} else {
return trainResult
}
}

private fun trainFace(): Pair<Boolean, String> {
val req = Request.Builder()
.url(mUrl)
.build()

val getFaceResult = ServerUtil.makeRequest(req) {
val bytes = it.body()?.bytes()
it.body()?.close()
bytes
}
if (!getFaceResult.success || getFaceResult.data == null) {
return Pair(false, "")
}

try {
val faceServiceClient = FaceServiceRestClient(FaceAPI.SUB_KEY)
val result = faceServiceClient.createPerson(FaceAPI.PERSON_GROUP_ID,
mNewName, "")
val inputStream = ByteArrayInputStream(getFaceResult.data)

faceServiceClient.addPersonFace(FaceAPI.PERSON_GROUP_ID,
result.personId, inputStream,
"", null)
faceServiceClient.trainPersonGroup(FaceAPI.PERSON_GROUP_ID)
return Pair(true, "")
} catch (e: ClientException) {
return Pair(false, e.message ?: "")
} catch (e: IOException) {
return Pair(false, "")
}
}

override fun onPostExecute(result: Pair<Boolean, String>) {
val frag = mFragRef.get() ?: return

if (frag.activity == null) {
return
}

val (success, errMsg) = result
if (success) {
frag.activity!!.showToast("Sent!")
} else {
if (errMsg.isNotEmpty()) {
frag.activity!!.showToast(errMsg)
} else {
frag.activity!!.showToast("Send failed!")
}
}
frag.dismiss()
}
}

}
@@ -8,6 +8,8 @@ import android.os.Handler
import android.os.Looper
import android.support.annotation.UiThread
import com.trakam.trakam.data.Log
import com.trakam.trakam.db.AppDatabase
import com.trakam.trakam.db.PeopleDao
import com.trakam.trakam.util.*
import okhttp3.HttpUrl
import okhttp3.Request
@@ -41,8 +43,11 @@ class ServerPollingService : BaseService() {
private val mRequestLock = Any()
private lateinit var mRequest: Request

private lateinit var mDao: PeopleDao

override fun onCreate() {
super.onCreate()
mDao = AppDatabase.getInstance(this).peopleDao()
setupRequest()
}

@@ -51,8 +56,7 @@ class ServerPollingService : BaseService() {
PrefKeys.Server.Default.SERVER_HOST)
val port = getDefaultSharedPreferences().getString(PrefKeys.Server.KEY_SERVER_PORT,
PrefKeys.Server.Default.SERVER_PORT)
val url = HttpUrl.parse("http://$host:$port/logs")
?: throw RuntimeException("Failed to parse url")
val url = "http://$host:$port/logs"
synchronized(mRequestLock) {
mRequest = Request.Builder()
.url(url)
@@ -122,7 +126,13 @@ class ServerPollingService : BaseService() {

val time = tokens[2].trim().toLong()

val log = Log(id, firstName, lastName, Date(time))
val foundInBlacklist = mDao.getAll()
.find {
it.firstName == firstName && it.lastName == lastName
}

val log = Log(id, firstName, lastName,
foundInBlacklist != null, Date(time))
logs += log

MyLogger.logDebug(ServerPollingService::class, "log: $log")
@@ -0,0 +1,6 @@
package com.trakam.trakam.util

object FaceAPI {
const val SUB_KEY = "fb648f47875e4dba98ed1267aec784e7"
const val PERSON_GROUP_ID = "people"
}
@@ -8,6 +8,7 @@ import android.content.SharedPreferences
import android.graphics.Typeface
import android.os.Build
import android.preference.PreferenceManager
import android.support.annotation.AttrRes
import android.support.annotation.IdRes
import android.support.annotation.LayoutRes
import android.util.TypedValue
@@ -83,6 +84,15 @@ fun Context.dipToPix(value: Float): Float {
fun Context.getDefaultSharedPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this)

fun Context.getAttrColor(@AttrRes attr: Int): Int {
val typedValue = TypedValue()
return if (theme.resolveAttribute(attr, typedValue, true)) {
typedValue.data
} else {
throw IllegalStateException("Color attr not found")
}
}

//// END Context