1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.googlecode.android_scripting;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.os.AsyncTask;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.preference.PreferenceManager;
25 
26 import com.googlecode.android_scripting.exception.Sl4aException;
27 import com.googlecode.android_scripting.interpreter.InterpreterConstants;
28 import com.googlecode.android_scripting.interpreter.InterpreterDescriptor;
29 import com.googlecode.android_scripting.interpreter.InterpreterUtils;
30 
31 import java.io.File;
32 import java.net.MalformedURLException;
33 import java.util.ArrayList;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Queue;
37 
38 /**
39  * AsyncTask for installing interpreters.
40  *
41  */
42 public abstract class InterpreterInstaller extends AsyncTask<Void, Void, Boolean> {
43 
44   protected final InterpreterDescriptor mDescriptor;
45   protected final AsyncTaskListener<Boolean> mTaskListener;
46   protected final Queue<RequestCode> mTaskQueue;
47   protected final Context mContext;
48 
49   protected final Handler mainThreadHandler;
50   protected Handler mBackgroundHandler;
51 
52   protected volatile AsyncTask<Void, Integer, Long> mTaskHolder;
53 
54   protected final String mInterpreterRoot;
55 
56   protected static enum RequestCode {
57     DOWNLOAD_INTERPRETER, DOWNLOAD_INTERPRETER_EXTRAS, DOWNLOAD_SCRIPTS, EXTRACT_INTERPRETER,
58     EXTRACT_INTERPRETER_EXTRAS, EXTRACT_SCRIPTS
59   }
60 
61   // Executed in the UI thread.
62   private final Runnable mTaskStarter = new Runnable() {
63     @Override
64     public void run() {
65       RequestCode task = mTaskQueue.peek();
66       try {
67         AsyncTask<Void, Integer, Long> newTask = null;
68         switch (task) {
69         case DOWNLOAD_INTERPRETER:
70           newTask = downloadInterpreter();
71           break;
72         case DOWNLOAD_INTERPRETER_EXTRAS:
73           newTask = downloadInterpreterExtras();
74           break;
75         case DOWNLOAD_SCRIPTS:
76           newTask = downloadScripts();
77           break;
78         case EXTRACT_INTERPRETER:
79           newTask = extractInterpreter();
80           break;
81         case EXTRACT_INTERPRETER_EXTRAS:
82           newTask = extractInterpreterExtras();
83           break;
84         case EXTRACT_SCRIPTS:
85           newTask = extractScripts();
86           break;
87         }
88         mTaskHolder = newTask.execute();
89       } catch (Exception e) {
90         Log.v(e.getMessage(), e);
91       }
92 
93       if (mBackgroundHandler != null) {
94         mBackgroundHandler.post(mTaskWorker);
95       }
96     }
97   };
98 
99   // Executed in the background.
100   private final Runnable mTaskWorker = new Runnable() {
101     @Override
102     public void run() {
103       RequestCode request = mTaskQueue.peek();
104       try {
105         if (mTaskHolder != null && mTaskHolder.get() != null) {
106           mTaskQueue.remove();
107           mTaskHolder = null;
108           // Post processing.
109           if (request == RequestCode.EXTRACT_INTERPRETER && !chmodIntepreter()) {
110             // Chmod returned false.
111             Looper.myLooper().quit();
112           } else if (mTaskQueue.size() == 0) {
113             // We're done here.
114             Looper.myLooper().quit();
115             return;
116           } else if (mainThreadHandler != null) {
117             // There's still some work to do.
118             mainThreadHandler.post(mTaskStarter);
119             return;
120           }
121         }
122       } catch (Exception e) {
123         Log.e(e);
124       }
125       // Something went wrong...
126       switch (request) {
127       case DOWNLOAD_INTERPRETER:
128         Log.e("Downloading interpreter failed.");
129         break;
130       case DOWNLOAD_INTERPRETER_EXTRAS:
131         Log.e("Downloading interpreter extras failed.");
132         break;
133       case DOWNLOAD_SCRIPTS:
134         Log.e("Downloading scripts failed.");
135         break;
136       case EXTRACT_INTERPRETER:
137         Log.e("Extracting interpreter failed.");
138         break;
139       case EXTRACT_INTERPRETER_EXTRAS:
140         Log.e("Extracting interpreter extras failed.");
141         break;
142       case EXTRACT_SCRIPTS:
143         Log.e("Extracting scripts failed.");
144         break;
145       }
146       Looper.myLooper().quit();
147     }
148   };
149 
150   // TODO(Alexey): Add Javadoc.
InterpreterInstaller(InterpreterDescriptor descriptor, Context context, AsyncTaskListener<Boolean> taskListener)151   public InterpreterInstaller(InterpreterDescriptor descriptor, Context context,
152       AsyncTaskListener<Boolean> taskListener) throws Sl4aException {
153     super();
154     mDescriptor = descriptor;
155     mContext = context;
156     mTaskListener = taskListener;
157     mainThreadHandler = new Handler();
158     mTaskQueue = new LinkedList<RequestCode>();
159 
160     String packageName = mDescriptor.getClass().getPackage().getName();
161 
162     if (packageName.length() == 0) {
163       throw new Sl4aException("Interpreter package name is empty.");
164     }
165 
166     mInterpreterRoot = InterpreterConstants.SDCARD_ROOT + packageName;
167 
168     if (mDescriptor == null) {
169       throw new Sl4aException("Interpreter description not provided.");
170     }
171     if (mDescriptor.getName() == null) {
172       throw new Sl4aException("Interpreter not specified.");
173     }
174     if (isInstalled()) {
175       throw new Sl4aException("Interpreter is installed.");
176     }
177 
178     if (mDescriptor.hasInterpreterArchive()) {
179       mTaskQueue.offer(RequestCode.DOWNLOAD_INTERPRETER);
180       mTaskQueue.offer(RequestCode.EXTRACT_INTERPRETER);
181     }
182     if (mDescriptor.hasExtrasArchive()) {
183       mTaskQueue.offer(RequestCode.DOWNLOAD_INTERPRETER_EXTRAS);
184       mTaskQueue.offer(RequestCode.EXTRACT_INTERPRETER_EXTRAS);
185     }
186     if (mDescriptor.hasScriptsArchive()) {
187       mTaskQueue.offer(RequestCode.DOWNLOAD_SCRIPTS);
188       mTaskQueue.offer(RequestCode.EXTRACT_SCRIPTS);
189     }
190   }
191 
192   @Override
doInBackground(Void... params)193   protected Boolean doInBackground(Void... params) {
194     new Thread(new Runnable() {
195       @Override
196       public void run() {
197         executeInBackground();
198         final boolean result = (mTaskQueue.size() == 0);
199         mainThreadHandler.post(new Runnable() {
200           @Override
201           public void run() {
202             finish(result);
203           }
204         });
205       }
206     }).start();
207     return true;
208   }
209 
executeInBackground()210   private boolean executeInBackground() {
211 
212     File root = new File(mInterpreterRoot);
213     if (root.exists()) {
214       FileUtils.delete(root);
215     }
216     if (!root.mkdirs()) {
217       Log.e("Failed to make directories: " + root.getAbsolutePath());
218       return false;
219     }
220 
221     if (Looper.myLooper() == null) {
222       Looper.prepare();
223     }
224     mBackgroundHandler = new Handler(Looper.myLooper());
225     mainThreadHandler.post(mTaskStarter);
226     Looper.loop();
227     // Have we executed all the tasks?
228     return (mTaskQueue.size() == 0);
229   }
230 
finish(boolean result)231   protected void finish(boolean result) {
232     if (result && setup()) {
233       mTaskListener.onTaskFinished(true, "Installation successful.");
234     } else {
235       if (mTaskHolder != null) {
236         mTaskHolder.cancel(true);
237       }
238       cleanup();
239       mTaskListener.onTaskFinished(false, "Installation failed.");
240     }
241   }
242 
download(String in)243   protected AsyncTask<Void, Integer, Long> download(String in) throws MalformedURLException {
244     String out = mInterpreterRoot;
245     return new UrlDownloaderTask(in, out, mContext);
246   }
247 
downloadInterpreter()248   protected AsyncTask<Void, Integer, Long> downloadInterpreter() throws MalformedURLException {
249     return download(mDescriptor.getInterpreterArchiveUrl());
250   }
251 
downloadInterpreterExtras()252   protected AsyncTask<Void, Integer, Long> downloadInterpreterExtras() throws MalformedURLException {
253     return download(mDescriptor.getExtrasArchiveUrl());
254   }
255 
downloadScripts()256   protected AsyncTask<Void, Integer, Long> downloadScripts() throws MalformedURLException {
257     return download(mDescriptor.getScriptsArchiveUrl());
258   }
259 
extract(String in, String out, boolean replaceAll)260   protected AsyncTask<Void, Integer, Long> extract(String in, String out, boolean replaceAll)
261       throws Sl4aException {
262     return new ZipExtractorTask(in, out, mContext, replaceAll);
263   }
264 
extractInterpreter()265   protected AsyncTask<Void, Integer, Long> extractInterpreter() throws Sl4aException {
266     String in =
267         new File(mInterpreterRoot, mDescriptor.getInterpreterArchiveName()).getAbsolutePath();
268     String out = InterpreterUtils.getInterpreterRoot(mContext).getAbsolutePath();
269     return extract(in, out, true);
270   }
271 
extractInterpreterExtras()272   protected AsyncTask<Void, Integer, Long> extractInterpreterExtras() throws Sl4aException {
273     String in = new File(mInterpreterRoot, mDescriptor.getExtrasArchiveName()).getAbsolutePath();
274     String out = mInterpreterRoot + InterpreterConstants.INTERPRETER_EXTRAS_ROOT;
275     return extract(in, out, true);
276   }
277 
extractScripts()278   protected AsyncTask<Void, Integer, Long> extractScripts() throws Sl4aException {
279     String in = new File(mInterpreterRoot, mDescriptor.getScriptsArchiveName()).getAbsolutePath();
280     String out = InterpreterConstants.SCRIPTS_ROOT;
281     return extract(in, out, false);
282   }
283 
chmodIntepreter()284   protected boolean chmodIntepreter() {
285     int dataChmodErrno;
286     boolean interpreterChmodSuccess;
287     try {
288       dataChmodErrno = FileUtils.chmod(InterpreterUtils.getInterpreterRoot(mContext), 0755);
289       interpreterChmodSuccess =
290           FileUtils.recursiveChmod(InterpreterUtils.getInterpreterRoot(mContext, mDescriptor
291               .getName()), 0755);
292     } catch (Exception e) {
293       Log.e(e);
294       return false;
295     }
296     return dataChmodErrno == 0 && interpreterChmodSuccess;
297   }
298 
isInstalled()299   protected boolean isInstalled() {
300     SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
301     return preferences.getBoolean(InterpreterConstants.INSTALLED_PREFERENCE_KEY, false);
302   }
303 
cleanup()304   private void cleanup() {
305     List<File> directories = new ArrayList<File>();
306 
307     directories.add(new File(mInterpreterRoot));
308 
309     if (mDescriptor.hasInterpreterArchive()) {
310       if (!mTaskQueue.contains(RequestCode.EXTRACT_INTERPRETER)) {
311         directories.add(InterpreterUtils.getInterpreterRoot(mContext, mDescriptor.getName()));
312       }
313     }
314 
315     for (File directory : directories) {
316       FileUtils.delete(directory);
317     }
318   }
319 
setup()320   protected abstract boolean setup();
321 }
322