aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/moe/yuuta/workmode/MainActivity.kt
diff options
context:
space:
mode:
authorYuutaW <17158086+Trumeet@users.noreply.github.com>2019-02-24 11:59:17 -0800
committerYuutaW <17158086+Trumeet@users.noreply.github.com>2019-02-24 11:59:17 -0800
commita08328403be84d85c006f801169a3feed0d956a4 (patch)
treeceebece6443a3e6662a4937b911c58904bb5b1ff /app/src/main/java/moe/yuuta/workmode/MainActivity.kt
downloadWorkMode-a08328403be84d85c006f801169a3feed0d956a4.tar
WorkMode-a08328403be84d85c006f801169a3feed0d956a4.tar.gz
WorkMode-a08328403be84d85c006f801169a3feed0d956a4.tar.bz2
WorkMode-a08328403be84d85c006f801169a3feed0d956a4.zip
First Commit
Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com>
Diffstat (limited to 'app/src/main/java/moe/yuuta/workmode/MainActivity.kt')
-rw-r--r--app/src/main/java/moe/yuuta/workmode/MainActivity.kt406
1 files changed, 406 insertions, 0 deletions
diff --git a/app/src/main/java/moe/yuuta/workmode/MainActivity.kt b/app/src/main/java/moe/yuuta/workmode/MainActivity.kt
new file mode 100644
index 0000000..b1d425b
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/MainActivity.kt
@@ -0,0 +1,406 @@
+package moe.yuuta.workmode
+
+import android.app.Activity
+import android.content.*
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Bundle
+import android.service.quicksettings.TileService
+import android.util.Log
+import android.util.LruCache
+import android.view.*
+import android.widget.*
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settings.widget.SwitchBar
+import com.elvishew.xlog.Logger
+import com.elvishew.xlog.XLog
+import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.snackbar.Snackbar
+import com.google.android.material.tabs.TabLayout
+import moe.yuuta.workmode.access.AccessorStarter
+import moe.yuuta.workmode.async.*
+import moe.yuuta.workmode.suspend.AsyncSuspender
+import moe.yuuta.workmode.suspend.SuspendTile
+import moe.yuuta.workmode.suspend.data.ListMode
+import moe.yuuta.workmode.suspend.data.Status
+import moe.yuuta.workmode.suspend.data.SuspendedStorage
+import moe.yuuta.workmode.update.Update
+import moe.yuuta.workmode.update.UpdateChecker
+import moe.yuuta.workmode.utils.Utils
+import java.util.stream.Collectors
+
+class MainActivity : AppCompatActivity(), SwitchBar.OnSwitchChangeListener, View.OnClickListener {
+ private val logger: Logger = XLog.tag("MainActivity").build()
+
+ companion object {
+ const val RC_PICK = 1
+ }
+
+ private lateinit var mAdapter: Adapter
+
+ private lateinit var switchBar: SwitchBar
+ private lateinit var progressBar: ProgressBar
+ private lateinit var tabLayout: TabLayout
+ private lateinit var welcomeTip: TextView
+
+ private val mStoppableGroup: StoppableGroup = StoppableGroup()
+ private var mSortDisplayStoppable: Stoppable? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ switchBar = findViewById(R.id.switch_bar)
+ switchBar.show()
+ welcomeTip = findViewById(R.id.welcome_tip)
+ progressBar = findViewById(R.id.progress_apply)
+ tabLayout = findViewById(R.id.tab)
+ tabLayout.addTab(tabLayout.newTab().setTag("blacklist").setText(R.string.blacklist))
+ tabLayout.addTab(tabLayout.newTab().setTag("whitelist").setText(R.string.whitelist))
+ val fab: FloatingActionButton = findViewById(R.id.fab_add)
+ fab.setOnClickListener(this)
+ val recyclerView: RecyclerView = findViewById(R.id.recycler_apps)
+ recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
+ mAdapter = Adapter()
+ recyclerView.adapter = mAdapter
+ displayUI()
+ val filter = IntentFilter(AccessorStarter.ACTION_UPDATE_UI_STATE)
+ filter.addAction(AccessorStarter.ACTION_UPDATE_UI_PROGRESS)
+ registerReceiver(mUIUpdateReceiver, filter)
+ scheduleUpdateChecking()
+ setProgressUI(false)
+ }
+
+ override fun onSwitchChanged(switchView: Switch?, isChecked: Boolean) {
+ SuspendedStorage(this).setStatus(if (isChecked) Status.ON else Status.OFF)
+ scheduleApply()
+ }
+
+ /**
+ * Apply settings which are stored in SuspendedStorage to OS
+ */
+ private fun scheduleApply() {
+ mStoppableGroup.add(AsyncSuspender(this).applyFromSettings(object : Callback<Unit> {
+ override fun onStart() {
+ setProgressUI(true)
+ }
+
+ override fun onStop(success: Boolean, result: Unit?, e: Throwable?) {
+ setProgressUI(false)
+ displayUI()
+ if (!success) {
+ logger.e("Unable scheduleApply settings", e)
+ Toast.makeText(this@MainActivity, R.string.error_apply, Toast.LENGTH_LONG).show()
+ }
+ }
+ }))
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ when (requestCode) {
+ RC_PICK -> {
+ if (resultCode == Activity.RESULT_OK && data != null && data.hasExtra(ApplicationPickerActivity.EXTRA_SELECTED_PACKAGE_NAME)) {
+ val newSet = data.getStringArrayExtra(ApplicationPickerActivity.EXTRA_SELECTED_PACKAGE_NAME).toSet()
+ logger.d("AR() $newSet")
+ SuspendedStorage(this).setList(newSet)
+ scheduleApply()
+ }
+ }
+ }
+ }
+
+ private fun setProgressUI(showProgress: Boolean) {
+ TileService.requestListeningState(this, ComponentName(this, SuspendTile::class.java))
+ Utils.setViewTreeEnable(findViewById(android.R.id.content), !showProgress)
+ Utils.makeTabLayoutDisable(tabLayout, !showProgress)
+ progressBar.visibility = if (showProgress) View.VISIBLE else View.GONE
+ }
+
+ override fun onDestroy() {
+ mStoppableGroup.stop()
+ mAdapter.destroy()
+ if (mSortDisplayStoppable != null) (mSortDisplayStoppable as Stoppable).stop()
+ unregisterReceiver(mUIUpdateReceiver)
+ super.onDestroy()
+ }
+
+ /**
+ * Display the data from SuspendedStorage to UI
+ */
+ private fun displayUI() {
+ switchBar.removeOnSwitchChangeListener(this)
+ switchBar.isChecked = SuspendedStorage(this).getStatus() == Status.ON
+ switchBar.addOnSwitchChangeListener(this)
+ tabLayout.removeOnTabSelectedListener(mSwitchListModeListener)
+ tabLayout.getTabAt(
+ when (SuspendedStorage(this).getListMode()) {
+ ListMode.BLACKLIST -> 0
+ ListMode.WHITELIST -> 1
+ }
+ )!!.select()
+ tabLayout.addOnTabSelectedListener(mSwitchListModeListener)
+ if (mSortDisplayStoppable != null) {
+ val stoppable = mSortDisplayStoppable as Stoppable
+ stoppable.stop()
+ mSortDisplayStoppable = null
+ }
+ mSortDisplayStoppable = Async.beginTask(object : Runnable<List<String>> {
+ override fun run(): List<String>? {
+ val sCollator = java.text.Collator.getInstance()
+ return SuspendedStorage(this@MainActivity).getList()
+ .stream()
+ .sorted { o1, o2 ->
+ return@sorted sCollator.compare(packageManager.getApplicationLabel(packageManager.getApplicationInfo(o1, 0)).toString()
+ , packageManager.getApplicationLabel(packageManager.getApplicationInfo(o2, 0)))
+ }
+ .collect(Collectors.toList())
+ }
+ }, object : Callback<List<String>> {
+ override fun onStop(success: Boolean, result: List<String>?, e: Throwable?) {
+ if (result != null) {
+ val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
+ override fun getOldListSize(): Int = mAdapter.itemCount
+
+ override fun getNewListSize(): Int = result.size
+
+ override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
+ mAdapter.data[oldItemPosition]::class.java.name ==
+ result[newItemPosition]::class.java.name
+
+ override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
+ mAdapter.data[oldItemPosition] ==
+ result[newItemPosition]
+ })
+ mAdapter.data = result
+ diff.dispatchUpdatesTo(mAdapter)
+ if (result.isEmpty()) {
+ welcomeTip.setText(when (SuspendedStorage(this@MainActivity).getListMode()) {
+ ListMode.BLACKLIST -> R.string.blacklist_welcome
+ ListMode.WHITELIST -> R.string.whitelist_welcome
+ })
+ welcomeTip.visibility = View.VISIBLE
+ } else {
+ welcomeTip.visibility = View.GONE
+ }
+ } else {
+ if (e == null) logger.e("Unable to sort data")
+ else logger.e("Unable to sort data with error", e)
+ }
+ }
+ })
+ }
+
+ private val mUIUpdateReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent == null) return
+ when (intent.action) {
+ AccessorStarter.ACTION_UPDATE_UI_STATE -> {
+ displayUI()
+ }
+ AccessorStarter.ACTION_UPDATE_UI_PROGRESS -> {
+ logger.d("Updating progress from receiver")
+ setProgressUI(intent.getBooleanExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, false))
+ }
+ }
+ }
+ }
+
+ private fun scheduleUpdateChecking() {
+ mStoppableGroup.add(Async.beginTask(UpdateChecker(), object : Callback<Update> {
+ override fun onStop(success: Boolean, result: Update?, e: Throwable?) {
+ if (result == null) return
+ if (result.version <= BuildConfig.VERSION_CODE) return
+ if (!shouldOpenGooglePlay() && !result.altUrlEnabled && !result.altUrlForce) return
+ Snackbar.make(findViewById(android.R.id.content),
+ getString(R.string.update_available,
+ result.name),
+ Snackbar.LENGTH_LONG)
+ .setAction(R.string.view) {
+ val url = if (shouldOpenGooglePlay() && !result.altUrlForce)
+ "https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}"
+ else result.altUrl
+ try {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
+ } catch (ignored: ActivityNotFoundException) {}
+ }
+ .show()
+ }
+
+ private fun shouldOpenGooglePlay(): Boolean =
+ "com.android.vending" == this@MainActivity.packageManager.getInstallerPackageName(BuildConfig.APPLICATION_ID)
+
+ }))
+ }
+
+ private val mSwitchListModeListener = object : TabLayout.OnTabSelectedListener {
+ override fun onTabReselected(tab: TabLayout.Tab?) {
+ }
+
+ override fun onTabUnselected(tab: TabLayout.Tab?) {
+ }
+
+ override fun onTabSelected(tab: TabLayout.Tab) {
+ when (tab.tag) {
+ "blacklist" -> {
+ AlertDialog.Builder(this@MainActivity)
+ .setTitle(R.string.blacklist_toggle_title)
+ .setMessage(R.string.blacklist_toggle_information)
+ .setCancelable(false)
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ tabLayout.removeOnTabSelectedListener(this)
+ tabLayout.getTabAt(1)?.select()
+ tabLayout.addOnTabSelectedListener(this)
+ }
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ SuspendedStorage(this@MainActivity).setListMode(ListMode.BLACKLIST)
+ scheduleApply()
+ }
+ .show()
+ }
+ "whitelist" -> {
+ AlertDialog.Builder(this@MainActivity)
+ .setTitle(R.string.whitelist_toggle_title)
+ .setMessage(R.string.whitelist_toggle_information)
+ .setCancelable(false)
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ tabLayout.removeOnTabSelectedListener(this)
+ tabLayout.getTabAt(0)?.select()
+ tabLayout.addOnTabSelectedListener(this)
+ }
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ SuspendedStorage(this@MainActivity).setListMode(ListMode.WHITELIST)
+ scheduleApply()
+ }
+ .show()
+ }
+ }
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_main, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_get_logs -> {
+ return try {
+ startActivity(Intent.createChooser(Setup.buildShareLogsIntent(this),
+ getString(R.string.get_logs)))
+ true
+ } catch (e: Exception) {
+ try {
+ logger.e("Share logs", e)
+ } catch (ignored: Exception) {}
+ System.err.println("Unable to share logs, ${Log.getStackTraceString(e)}")
+ true
+ }
+ }
+ R.id.action_feedback -> {
+ val intent = Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse("mailto:"))
+ intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.feedback_subject))
+ intent.putExtra(Intent.EXTRA_EMAIL, arrayOf("android-apps@yuuta.moe"))
+ startActivity(Intent.createChooser(intent, getString(R.string.feedback)))
+ return true
+ }
+ R.id.action_check_update -> {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}")))
+ return true
+ }
+ R.id.action_oss -> {
+ startActivity(Intent(this, OssLicensesMenuActivity::class.java))
+ return true
+ }
+ else -> return super.onOptionsItemSelected(item)
+ }
+ }
+
+ override fun onClick(v: View?) {
+ if (v == null) return
+ when (v.id) {
+ R.id.fab_add -> {
+ startActivityForResult(Intent(this, ApplicationPickerActivity::class.java)
+ .putExtra(ApplicationPickerActivity.EXTRA_SELECTED_PACKAGE_NAME,
+ SuspendedStorage(this).getList().toTypedArray()), RC_PICK)
+ }
+ }
+ }
+}
+
+private class Adapter : RecyclerView.Adapter<Adapter.VH>() {
+ private val mIconMemoryCaches: LruCache<String, Drawable> =
+ LruCache(Runtime.getRuntime().maxMemory().toInt() / 5)
+ private val mStoppableGroup: StoppableGroup = StoppableGroup()
+ internal var data: List<String> = listOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH =
+ VH(LayoutInflater.from(parent.context).inflate(R.layout.item_application, parent, false))
+
+ override fun getItemCount(): Int = data.size
+
+ override fun onBindViewHolder(holder: VH, position: Int) {
+ val context = holder.itemView.context
+ val packageName = data[position]
+ val icon = getIconFromMemoryCache(packageName)
+ if (icon != null) {
+ holder.icon.setImageDrawable(icon)
+ } else {
+ loadIcon(packageName, holder.itemView.context, holder.icon)
+ }
+ holder.title.text = context
+ .packageManager
+ .getApplicationLabel(
+ context.packageManager.getApplicationInfo(packageName, 0)
+ )
+ }
+
+ class VH(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ internal val icon: ImageView = itemView.findViewById(android.R.id.icon)
+ internal val title: TextView = itemView.findViewById(android.R.id.title)
+ }
+
+ private fun addDrawableToMemoryCache(pkg: String?, icon: Drawable) {
+ if (getIconFromMemoryCache(pkg) == null) {
+ mIconMemoryCaches.put(pkg ?: "", icon)
+ }
+ }
+
+ private fun getIconFromMemoryCache(pkg: String?): Drawable? {
+ return mIconMemoryCaches.get(pkg ?: "")
+ }
+
+ private fun loadIcon(pkg: String, context: Context, imageView: ImageView) {
+ mStoppableGroup.add(Async.beginTask(object : Runnable<Drawable> {
+ override fun run(): Drawable {
+ var icon: Drawable?
+ try {
+ icon = context.packageManager
+ .getApplicationIcon(pkg)
+ } catch (ignore: PackageManager.NameNotFoundException) {
+ icon = null
+ }
+ if (icon == null) {
+ icon = ContextCompat.getDrawable(context, android.R.mipmap.sym_def_app_icon)!!
+ }
+ addDrawableToMemoryCache(pkg, icon)
+ return icon
+ }
+ }, object : Callback<Drawable> {
+ override fun onStop(success: Boolean, result: Drawable?, e: Throwable?) {
+ if (success && result != null) imageView.setImageDrawable(result)
+ }
+ }))
+ }
+
+ fun destroy() {
+ mStoppableGroup.stop()
+ }
+} \ No newline at end of file