/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.android_scripting; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; import com.googlecode.android_scripting.exception.Sl4aException; import com.googlecode.android_scripting.interpreter.InterpreterConstants; import com.googlecode.android_scripting.interpreter.InterpreterDescriptor; import com.googlecode.android_scripting.interpreter.InterpreterUtils; import java.io.File; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * AsyncTask for installing interpreters. * */ public abstract class InterpreterInstaller extends AsyncTask<Void, Void, Boolean> { protected final InterpreterDescriptor mDescriptor; protected final AsyncTaskListener<Boolean> mTaskListener; protected final Queue<RequestCode> mTaskQueue; protected final Context mContext; protected final Handler mainThreadHandler; protected Handler mBackgroundHandler; protected volatile AsyncTask<Void, Integer, Long> mTaskHolder; protected final String mInterpreterRoot; protected static enum RequestCode { DOWNLOAD_INTERPRETER, DOWNLOAD_INTERPRETER_EXTRAS, DOWNLOAD_SCRIPTS, EXTRACT_INTERPRETER, EXTRACT_INTERPRETER_EXTRAS, EXTRACT_SCRIPTS } // Executed in the UI thread. private final Runnable mTaskStarter = new Runnable() { @Override public void run() { RequestCode task = mTaskQueue.peek(); try { AsyncTask<Void, Integer, Long> newTask = null; switch (task) { case DOWNLOAD_INTERPRETER: newTask = downloadInterpreter(); break; case DOWNLOAD_INTERPRETER_EXTRAS: newTask = downloadInterpreterExtras(); break; case DOWNLOAD_SCRIPTS: newTask = downloadScripts(); break; case EXTRACT_INTERPRETER: newTask = extractInterpreter(); break; case EXTRACT_INTERPRETER_EXTRAS: newTask = extractInterpreterExtras(); break; case EXTRACT_SCRIPTS: newTask = extractScripts(); break; } mTaskHolder = newTask.execute(); } catch (Exception e) { Log.v(e.getMessage(), e); } if (mBackgroundHandler != null) { mBackgroundHandler.post(mTaskWorker); } } }; // Executed in the background. private final Runnable mTaskWorker = new Runnable() { @Override public void run() { RequestCode request = mTaskQueue.peek(); try { if (mTaskHolder != null && mTaskHolder.get() != null) { mTaskQueue.remove(); mTaskHolder = null; // Post processing. if (request == RequestCode.EXTRACT_INTERPRETER && !chmodIntepreter()) { // Chmod returned false. Looper.myLooper().quit(); } else if (mTaskQueue.size() == 0) { // We're done here. Looper.myLooper().quit(); return; } else if (mainThreadHandler != null) { // There's still some work to do. mainThreadHandler.post(mTaskStarter); return; } } } catch (Exception e) { Log.e(e); } // Something went wrong... switch (request) { case DOWNLOAD_INTERPRETER: Log.e("Downloading interpreter failed."); break; case DOWNLOAD_INTERPRETER_EXTRAS: Log.e("Downloading interpreter extras failed."); break; case DOWNLOAD_SCRIPTS: Log.e("Downloading scripts failed."); break; case EXTRACT_INTERPRETER: Log.e("Extracting interpreter failed."); break; case EXTRACT_INTERPRETER_EXTRAS: Log.e("Extracting interpreter extras failed."); break; case EXTRACT_SCRIPTS: Log.e("Extracting scripts failed."); break; } Looper.myLooper().quit(); } }; // TODO(Alexey): Add Javadoc. public InterpreterInstaller(InterpreterDescriptor descriptor, Context context, AsyncTaskListener<Boolean> taskListener) throws Sl4aException { super(); mDescriptor = descriptor; mContext = context; mTaskListener = taskListener; mainThreadHandler = new Handler(); mTaskQueue = new LinkedList<RequestCode>(); String packageName = mDescriptor.getClass().getPackage().getName(); if (packageName.length() == 0) { throw new Sl4aException("Interpreter package name is empty."); } mInterpreterRoot = InterpreterConstants.SDCARD_ROOT + packageName; if (mDescriptor == null) { throw new Sl4aException("Interpreter description not provided."); } if (mDescriptor.getName() == null) { throw new Sl4aException("Interpreter not specified."); } if (isInstalled()) { throw new Sl4aException("Interpreter is installed."); } if (mDescriptor.hasInterpreterArchive()) { mTaskQueue.offer(RequestCode.DOWNLOAD_INTERPRETER); mTaskQueue.offer(RequestCode.EXTRACT_INTERPRETER); } if (mDescriptor.hasExtrasArchive()) { mTaskQueue.offer(RequestCode.DOWNLOAD_INTERPRETER_EXTRAS); mTaskQueue.offer(RequestCode.EXTRACT_INTERPRETER_EXTRAS); } if (mDescriptor.hasScriptsArchive()) { mTaskQueue.offer(RequestCode.DOWNLOAD_SCRIPTS); mTaskQueue.offer(RequestCode.EXTRACT_SCRIPTS); } } @Override protected Boolean doInBackground(Void... params) { new Thread(new Runnable() { @Override public void run() { executeInBackground(); final boolean result = (mTaskQueue.size() == 0); mainThreadHandler.post(new Runnable() { @Override public void run() { finish(result); } }); } }).start(); return true; } private boolean executeInBackground() { File root = new File(mInterpreterRoot); if (root.exists()) { FileUtils.delete(root); } if (!root.mkdirs()) { Log.e("Failed to make directories: " + root.getAbsolutePath()); return false; } if (Looper.myLooper() == null) { Looper.prepare(); } mBackgroundHandler = new Handler(Looper.myLooper()); mainThreadHandler.post(mTaskStarter); Looper.loop(); // Have we executed all the tasks? return (mTaskQueue.size() == 0); } protected void finish(boolean result) { if (result && setup()) { mTaskListener.onTaskFinished(true, "Installation successful."); } else { if (mTaskHolder != null) { mTaskHolder.cancel(true); } cleanup(); mTaskListener.onTaskFinished(false, "Installation failed."); } } protected AsyncTask<Void, Integer, Long> download(String in) throws MalformedURLException { String out = mInterpreterRoot; return new UrlDownloaderTask(in, out, mContext); } protected AsyncTask<Void, Integer, Long> downloadInterpreter() throws MalformedURLException { return download(mDescriptor.getInterpreterArchiveUrl()); } protected AsyncTask<Void, Integer, Long> downloadInterpreterExtras() throws MalformedURLException { return download(mDescriptor.getExtrasArchiveUrl()); } protected AsyncTask<Void, Integer, Long> downloadScripts() throws MalformedURLException { return download(mDescriptor.getScriptsArchiveUrl()); } protected AsyncTask<Void, Integer, Long> extract(String in, String out, boolean replaceAll) throws Sl4aException { return new ZipExtractorTask(in, out, mContext, replaceAll); } protected AsyncTask<Void, Integer, Long> extractInterpreter() throws Sl4aException { String in = new File(mInterpreterRoot, mDescriptor.getInterpreterArchiveName()).getAbsolutePath(); String out = InterpreterUtils.getInterpreterRoot(mContext).getAbsolutePath(); return extract(in, out, true); } protected AsyncTask<Void, Integer, Long> extractInterpreterExtras() throws Sl4aException { String in = new File(mInterpreterRoot, mDescriptor.getExtrasArchiveName()).getAbsolutePath(); String out = mInterpreterRoot + InterpreterConstants.INTERPRETER_EXTRAS_ROOT; return extract(in, out, true); } protected AsyncTask<Void, Integer, Long> extractScripts() throws Sl4aException { String in = new File(mInterpreterRoot, mDescriptor.getScriptsArchiveName()).getAbsolutePath(); String out = InterpreterConstants.SCRIPTS_ROOT; return extract(in, out, false); } protected boolean chmodIntepreter() { int dataChmodErrno; boolean interpreterChmodSuccess; try { dataChmodErrno = FileUtils.chmod(InterpreterUtils.getInterpreterRoot(mContext), 0755); interpreterChmodSuccess = FileUtils.recursiveChmod(InterpreterUtils.getInterpreterRoot(mContext, mDescriptor .getName()), 0755); } catch (Exception e) { Log.e(e); return false; } return dataChmodErrno == 0 && interpreterChmodSuccess; } protected boolean isInstalled() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); return preferences.getBoolean(InterpreterConstants.INSTALLED_PREFERENCE_KEY, false); } private void cleanup() { List<File> directories = new ArrayList<File>(); directories.add(new File(mInterpreterRoot)); if (mDescriptor.hasInterpreterArchive()) { if (!mTaskQueue.contains(RequestCode.EXTRACT_INTERPRETER)) { directories.add(InterpreterUtils.getInterpreterRoot(mContext, mDescriptor.getName())); } } for (File directory : directories) { FileUtils.delete(directory); } } protected abstract boolean setup(); }