diff options
author | YuutaW <17158086+Trumeet@users.noreply.github.com> | 2019-02-24 11:59:17 -0800 |
---|---|---|
committer | YuutaW <17158086+Trumeet@users.noreply.github.com> | 2019-02-24 11:59:17 -0800 |
commit | a08328403be84d85c006f801169a3feed0d956a4 (patch) | |
tree | ceebece6443a3e6662a4937b911c58904bb5b1ff /app/src/main/java/moe/yuuta/workmode/MainActivity.kt | |
download | WorkMode-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.kt | 406 |
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 |