Java tutorial
/* * EasyAsPi: A phone-based interface for the Raspberry Pi. * Copyright (C) 2017 vtcakavsmoace * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Raspberry Pi is a trademark of the Raspberry Pi Foundation. */ package io.github.trulyfree.easyaspi; import android.animation.Animator; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.BottomNavigationView; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.text.InputType; import android.view.MenuItem; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import android.widget.ViewSwitcher; import com.google.gson.JsonParseException; import java.io.IOException; import java.net.MalformedURLException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import io.github.trulyfree.easyaspi.lib.EAPActivity; import io.github.trulyfree.easyaspi.lib.callback.StagedCallback; import io.github.trulyfree.easyaspi.lib.disp.EAPDisplay; import io.github.trulyfree.easyaspi.lib.disp.EAPDisplayableModule; import io.github.trulyfree.easyaspi.lib.dl.DownloadHandler; import io.github.trulyfree.easyaspi.lib.io.FileHandler; import io.github.trulyfree.easyaspi.lib.module.ModuleHandler; import io.github.trulyfree.easyaspi.lib.module.conf.Config; import io.github.trulyfree.easyaspi.lib.module.conf.ModuleConfig; import static android.widget.LinearLayout.LayoutParams; /** * MainActivity class serves as the front end introductory interface for EasyAsPi. It has two main * functions: download modules and launch modules. * * @author vtcakavsmoace * @since v0.0.1-alpha */ public final class MainActivity extends AppCompatActivity implements EAPActivity { /** * Animation duration of all animations of EasyAsPi's introductory interface, in milliseconds. */ private static final int ANIMATION_DURATION = 500; /** * DownloadHandler of this EAPActivity implementation. */ private DownloadHandler downloadHandler; /** * FileHandler of this EAPActivity implementation. */ private FileHandler fileHandler; /** * ModuleHandler of this EAPActivity implementation. */ private ModuleHandler moduleHandler; /** * ExecutorService of this EAPActivity implementation. */ private ExecutorService executorService; /** * Current ID of the navigation bar. */ private int currentID = R.id.navigation_home; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setup(); } @Override public boolean setup() { downloadHandler = new DownloadHandler(this); fileHandler = new FileHandler(this); moduleHandler = new ModuleHandler(this); executorService = Executors.newCachedThreadPool(); setContentView(R.layout.activity_main); BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation); navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { ViewSwitcher viewGroup = (ViewSwitcher) findViewById(R.id.content); int id = item.getItemId(); if ((id == R.id.navigation_home || id == R.id.navigation_modules) && id != currentID) { currentID = item.getItemId(); viewGroup.showNext(); return true; } return false; } }); ViewSwitcher viewSwitcher = (ViewSwitcher) findViewById(R.id.content); Animation in = AnimationUtils.loadAnimation(this, android.R.anim.slide_in_left); Animation out = AnimationUtils.loadAnimation(this, android.R.anim.slide_out_right); viewSwitcher.setInAnimation(in); viewSwitcher.setOutAnimation(out); resetConfigReturned(); Button getNewModule = (Button) findViewById(R.id.new_module_config_confirm); getNewModule.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { EditText editText = (EditText) findViewById(R.id.new_module_config_configurl); final String url = editText.getText().toString(); Toast.makeText(MainActivity.this, "Requested config from: " + url, Toast.LENGTH_SHORT).show(); executorService.submit(new Callable<Boolean>() { @Override public Boolean call() { ModuleConfig config; try { config = moduleHandler.getModuleConfig(url); } catch (MalformedURLException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "Invalid URL. :(", Toast.LENGTH_LONG).show(); } }); return false; } catch (IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "Failed to get module config. :(", Toast.LENGTH_LONG).show(); } }); return false; } catch (JsonParseException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "Config loaded was invalid. :(", Toast.LENGTH_LONG).show(); } }); return false; } final ModuleConfig finalConfig = config; final ImageView configResponseBlock = (ImageView) findViewById(R.id.block_module_returned); final int colorFrom = ContextCompat.getColor(MainActivity.this, R.color.colorFillingTint); final int colorTo = ContextCompat.getColor(MainActivity.this, R.color.colorClear); final ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); colorAnimation.setDuration(ANIMATION_DURATION); colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { configResponseBlock.setBackgroundColor((Integer) animator.getAnimatedValue()); } }); colorAnimation.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { LinearLayout layout = (LinearLayout) findViewById(R.id.module_returned_config); EditText moduleName = (EditText) findViewById(R.id.module_returned_configname); EditText moduleVersion = (EditText) findViewById( R.id.module_returned_configversion); EditText moduleConfigUrl = (EditText) findViewById(R.id.module_returned_configurl); EditText moduleJarUrl = (EditText) findViewById(R.id.module_returned_jarurl); LinearLayout moduleDependencies = (LinearLayout) findViewById( R.id.module_returned_dependencies); moduleDependencies.removeAllViewsInLayout(); try { configResponseBlock.setVisibility(View.GONE); moduleName.setText(finalConfig.getName()); moduleVersion.setText(finalConfig.getVersion()); moduleConfigUrl.setText(finalConfig.getConfUrl()); moduleJarUrl.setText(finalConfig.getJarUrl()); Config[] dependencies = finalConfig.getDependencies(); for (Config dependency : dependencies) { LinearLayout dependencyLayout = new LinearLayout(MainActivity.this); dependencyLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); dependencyLayout.setOrientation(LinearLayout.HORIZONTAL); EditText name = new EditText(MainActivity.this); EditText jarUrl = new EditText(MainActivity.this); EditText[] loopThrough = { name, jarUrl }; LayoutParams params = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1.0f); for (EditText item : loopThrough) { item.setLayoutParams(params); item.setClickable(false); item.setInputType(InputType.TYPE_NULL); item.setCursorVisible(false); item.setFocusable(false); item.setFocusableInTouchMode(false); } name.setText(dependency.getName()); jarUrl.setText(dependency.getJarUrl()); dependencyLayout.addView(name); dependencyLayout.addView(jarUrl); moduleDependencies.addView(dependencyLayout); } layout.setClickable(true); Button validate = (Button) findViewById(R.id.module_returned_validate); Button cancel = (Button) findViewById(R.id.module_returned_cancel); validate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Requesting jars...", Toast.LENGTH_SHORT).show(); executorService.submit(new Callable<Boolean>() { @Override public Boolean call() { try { boolean success = true, refreshAllModification = true; Button refreshAll = null, getNewModule = (Button) findViewById( R.id.new_module_config_confirm); try { refreshAll = (Button) findViewById(R.id.refresh_all); refreshAll.setClickable(false); } catch (Throwable e) { refreshAllModification = false; } getNewModule.setClickable(false); final TextView stager = (TextView) findViewById( R.id.new_module_config_downloadstage); final ProgressBar progressBar = (ProgressBar) findViewById( R.id.new_module_config_downloadprogress); try { runOnUiThread(new Runnable() { @Override public void run() { resetConfigReturned(); } }); moduleHandler.getNewModule( makeModuleCallback(stager, progressBar), finalConfig, null, true); } catch (IOException e) { e.printStackTrace(); runOnUiThread(new Runnable() { @Override public void run() { stager.setText(""); progressBar.setProgress(0); } }); success = false; } getNewModule.setClickable(true); if (refreshAllModification) { refreshAll.setClickable(true); } return success; } catch (Throwable throwable) { throwable.printStackTrace(); return false; } } }); } }); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Cancelling request...", Toast.LENGTH_SHORT).show(); resetConfigReturned(); } }); } catch (Exception e) { e.printStackTrace(); Toast.makeText(MainActivity.this, "Module config invalid. :(", Toast.LENGTH_LONG).show(); layout.setClickable(false); final int colorFrom = ContextCompat.getColor(MainActivity.this, R.color.colorClear); final int colorTo = ContextCompat.getColor(MainActivity.this, R.color.colorFillingTint); ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); colorAnimation.setDuration(ANIMATION_DURATION); colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { configResponseBlock .setBackgroundColor((Integer) animator.getAnimatedValue()); } }); } } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); runOnUiThread(new Runnable() { @Override public void run() { colorAnimation.start(); } }); return true; } }); } }); moduleHandler.setup(); refreshFilling(); return true; } @Override public boolean isReady() { return false; } @Override public boolean destroy() { return false; } /** * Helper method which refreshes the module layout between module downloads/deletions. */ private void refreshFilling() { LinearLayout dashboard = (LinearLayout) findViewById(R.id.dashboard); dashboard.removeAllViewsInLayout(); if (moduleHandler.getConfigs().length != 0) { final LinearLayout moduleList = (LinearLayout) ((LinearLayout) getLayoutInflater() .inflate(R.layout.modulelist, dashboard)).getChildAt(0); final Button refreshAll = (Button) findViewById(R.id.refresh_all); refreshAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(MainActivity.this, "Refreshing jars...", Toast.LENGTH_SHORT).show(); executorService.submit(new Callable<Boolean>() { @Override public Boolean call() { boolean success = true; refreshAll.setClickable(false); final TextView stager = (TextView) findViewById(R.id.refresh_download_stage); final ProgressBar progressBar = (ProgressBar) findViewById(R.id.refresh_bar); try { moduleHandler.refreshAll(makeModuleCallback(stager, progressBar)); } catch (IOException e) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "Refresh failed. :(", Toast.LENGTH_SHORT) .show(); stager.setText(""); progressBar.setProgress(0); } }); success = false; } refreshAll.setClickable(true); return success; } }); } }); LinearLayout scrolledModuleList = (LinearLayout) ((ScrollView) moduleList.getChildAt(3)).getChildAt(0); for (int i = 0; i < moduleHandler.getConfigs().length; i++) { final int intermediary = i; final LinearLayout layout = (LinearLayout) ((LinearLayout) getLayoutInflater() .inflate(R.layout.module, scrolledModuleList)).getChildAt(i); executorService.submit(new Callable<Boolean>() { @Override public Boolean call() { final Button launcher = (Button) layout.getChildAt(1); final Button delete = (Button) layout.getChildAt(2); launcher.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent myIntent = new Intent(MainActivity.this, EAPDisplay.class); myIntent.putExtra("targetModule", moduleHandler.toJson(moduleHandler.getConfigs()[intermediary])); MainActivity.this.startActivityForResult(myIntent, intermediary); } }); delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { ModuleConfig config = moduleHandler.getConfigs()[intermediary]; boolean success = false; try { success = moduleHandler.remove(null, config); } catch (IOException e) { e.printStackTrace(); } final String toast = "Deletion of " + config.getName() + " was " + ((success) ? "successful." : "unsuccessful."); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show(); refreshFilling(); } }); } }); launcher.setClickable(true); delete.setClickable(true); return true; } }); EditText moduleName = (EditText) layout.getChildAt(0); moduleName.setText(moduleHandler.getConfigs()[intermediary].getName()); } } else { getLayoutInflater().inflate(R.layout.no_module_modulelist, dashboard); } } /** * */ private void resetConfigReturned() { final ImageView configResponseBlock = (ImageView) findViewById(R.id.block_module_returned); LinearLayout layout = (LinearLayout) findViewById(R.id.module_returned_config); EditText moduleName = (EditText) findViewById(R.id.module_returned_configname); EditText moduleVersion = (EditText) findViewById(R.id.module_returned_configversion); EditText moduleConfigUrl = (EditText) findViewById(R.id.module_returned_configurl); EditText moduleJarUrl = (EditText) findViewById(R.id.module_returned_jarurl); LinearLayout moduleDependencies = (LinearLayout) findViewById(R.id.module_returned_dependencies); Button validate = (Button) findViewById(R.id.module_returned_validate); Button cancel = (Button) findViewById(R.id.module_returned_cancel); moduleName.setText(R.string.module_returned_configname); moduleVersion.setText(R.string.module_returned_configversion); moduleConfigUrl.setText(R.string.module_returned_configurl); moduleJarUrl.setText(R.string.module_returned_jarurl); moduleDependencies.removeAllViewsInLayout(); validate.setOnClickListener(null); cancel.setOnClickListener(null); final int colorFrom = ContextCompat.getColor(MainActivity.this, R.color.colorClear); final int colorTo = ContextCompat.getColor(MainActivity.this, R.color.colorFillingTint); ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); colorAnimation.setDuration(ANIMATION_DURATION); colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { configResponseBlock.setBackgroundColor((Integer) animator.getAnimatedValue()); } }); layout.setClickable(false); configResponseBlock.setVisibility(View.VISIBLE); colorAnimation.start(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_CANCELED && data != null) { Bundle bundle = data.getExtras(); Toast.makeText(this, moduleHandler.getConfigs()[requestCode].getName() + " crashed: " + bundle.getString("error"), Toast.LENGTH_LONG).show(); } else if (resultCode == RESULT_OK || data == null) { Toast.makeText(this, moduleHandler.getConfigs()[requestCode].getName() + " finished cleanly. :)", Toast.LENGTH_LONG).show(); } } /** * @param stageText * @param progressBar * @return */ private StagedCallback makeModuleCallback(final TextView stageText, final ProgressBar progressBar) { return new StagedCallback() { private String[] names; private int stage = 0; @Override public void setStages(String[] names) { this.names = names; } @Override public void onStart() { runOnUiThread(new Runnable() { @Override public void run() { stageText.setText(names[stage]); } }); } @Override public void onProgress(final int current) { runOnUiThread(new Runnable() { @Override public void run() { int numerator = stage * 100 + current; int denominator = names.length; final int progress = numerator / denominator; progressBar.setProgress(progress); } }); } @Override public void onFinish() { runOnUiThread(new Runnable() { @Override public void run() { stage++; if (stage == names.length) { stageText.setText(""); progressBar.setProgress(0); Toast.makeText(MainActivity.this, "Download(s) successful.", Toast.LENGTH_SHORT).show(); refreshFilling(); } } }); } }; } @Override public DownloadHandler getDownloadHandler() { return downloadHandler; } @Override public FileHandler getFileHandler() { return fileHandler; } @Override public ModuleHandler getModuleHandler() { return moduleHandler; } @Override public ExecutorService getExecutorService() { return executorService; } @Override public EAPDisplayableModule getDisplayableModule() { return null; } @Override public boolean setDisplayableModule(EAPDisplayableModule displayableModule) { return false; } @Override public void displayToUser(String text, int time) { Toast.makeText(this, text, time).show(); } }