/*
* 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();
}