package mobi.monaca.framework.bootloader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import mobi.monaca.framework.util.MyLog; import mobi.monaca.utils.MyAsyncTask; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Handler; /** This class make Monaca application running on local file. */ public class LocalFileBootloader { protected static final String BOOTLOADER_PREFERENCE_NAME = "bootloader"; protected static final String BOOTLOADER_FILES_PREFERENCE_NAME = "bootloader_files"; private static final String TAG = LocalFileBootloader.class.getSimpleName(); protected Context context; protected Runnable success, fail; protected String dataDirPath; protected BootloaderPreferences bootloaderPreferences; public static boolean mShouldExtractAssets; protected LocalFileBootloader(Context context, Runnable success, Runnable fail) { this.context = context; this.success = success; this.fail = fail; this.bootloaderPreferences = new BootloaderPreferences(context); dataDirPath = context.getApplicationInfo().dataDir; } /** Get application's version string. */ protected String getAppliationVersionCode() { try { return "" + context.getPackageManager().getPackageInfo( context.getPackageName(), PackageManager.GET_META_DATA).versionCode; } catch (NameNotFoundException e) { return "0"; } } protected void execute() { MyLog.d(TAG, "using localFileBootloader"); new BootloaderTask().execute(); } public static void setup(Context context, Runnable runner, Runnable fail) { MyLog.d(TAG, "using LocalFileBootloader"); new LocalFileBootloader(context, runner, fail).execute(); } protected boolean isAppVersionUpdated() { boolean updated = !bootloaderPreferences.getAppVersionCode().equals(getAppliationVersionCode()); return updated; } protected boolean needsInitialization() { MyLog.d(TAG, "AppVersionUpdated , AppPackageUpdated : " + isAppVersionUpdated()+ ", " + bootloaderPreferences.isAppPackageUpdated()); return isAppVersionUpdated() || bootloaderPreferences.isAppPackageUpdated(); } protected List<String> getAssetsFileList() { ArrayList<String> result = new ArrayList<String>(); aggregateAssetsFileList("www", result); Collections.sort(result); return result; } public static boolean needToUseLocalFileBootloader() { return Build.VERSION.SDK_INT == 14 || Build.VERSION.SDK_INT == 15; } /** * returns new inputStream from assetPath. * if ver4.0.x, uses LocalFileBootloader. * else uses getAssets() * @param path this method removes file:///android_asset/ and file://android_asset/ * @param context * @return * @throws IOException */ public static InputStream openAsset(Context context, String path) throws IOException { String newPath = path.replaceFirst("(file:///android_asset/)|(file://android_asset/)", ""); if(mShouldExtractAssets){ return loadFileUsingBootloader(context, path); } if (needToUseLocalFileBootloader()) { return loadFileUsingBootloader(context, newPath); } else { return context.getAssets().open(newPath); } } private static InputStream loadFileUsingBootloader(Context context, String newPath) throws FileNotFoundException, IOException { String filePath = newPath.startsWith("file:///data/") ? newPath.substring(8) : context.getApplicationInfo().dataDir + "/" + newPath; File localAssetFile = new File(filePath); if (localAssetFile.exists()) { return new FileInputStream(localAssetFile); } else { return context.getAssets().open(newPath); } } public static String getFullPath(Context context, String path){ return context.getApplicationInfo().dataDir + "/" + path; } protected void aggregateAssetsFileList(String prefix, ArrayList<String> result) { try { for (String path : context.getAssets().list(prefix)) { MyLog.d(TAG, "pathCheck :" + prefix + "/" + path); if (existAsset(prefix + "/" + path)) { result.add(prefix + "/" + path); } else { // may be directory aggregateAssetsFileList(prefix + "/" + path, result); } } } catch (Exception e) { MyLog.e(getClass().getSimpleName(), e.getMessage()); throw new RuntimeException(e); } } protected List<String> getApplicationLocalFileList() { ArrayList<String> temp = new ArrayList<String>(); File dir = new File(context.getApplicationInfo().dataDir + "/www"); dir.mkdir(); LocalFileUtil.aggregateApplicationLocalFileList(new File( context.getApplicationInfo().dataDir + "/www"), temp); ArrayList<String> result = new ArrayList<String>(); int start = context.getApplicationInfo().dataDir.length() + 1; for (String path : temp) { result.add(path.substring(start)); } Collections.sort(result); return result; } protected static String join(List<String> list) { StringBuffer buffer = new StringBuffer(); for (String elt : list) { buffer.append(elt); buffer.append(":"); } String temp = buffer.toString(); if(temp.length() == 0){ MyLog.e(TAG, "Warning: temp.length=0"); return ""; } return temp.substring(0, temp.length() - 1); } protected boolean existAsset(String path) { try { InputStream stream = context.getAssets().open(path); stream.close(); } catch (Exception e) { MyLog.e(TAG, path + " not exist"); return false; } return true; } /** Copy assets to local data directory. */ protected void copyAssetToLocal(String path) { MyLog.d(TAG, "copyAssetToLocal()"); byte[] buffer = new byte[1024 * 4]; File file = new File(context.getApplicationInfo().dataDir + "/" + path); file.getParentFile().mkdirs(); try { OutputStream output = new FileOutputStream(file); InputStream input = context.getAssets().open(path); int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); } input.close(); output.close(); } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new AbortException(e); } } /** Clean local application files and the application preferences. */ protected void clean() { bootloaderPreferences.clear(); cleanFiles(context.getApplicationInfo().dataDir + "/www"); } protected void cleanFiles(String path) { File file = new File(path); if (file.isDirectory()) { for (File child : file.listFiles()) { cleanFiles(child.getAbsolutePath()); } file.delete(); } else { file.delete(); } } protected class BootloaderTask extends MyAsyncTask<Void, Void, Boolean> { protected Handler handler = new Handler(); protected ProgressDialog loadingDialog = null; @Override protected Boolean doInBackground(Void ...a) { boolean needInit = true; try { needInit = needsInitialization(); MyLog.v(TAG, "needInit = " + needInit); } catch (AbortException e) { MyLog.e(getClass().getSimpleName(), "bootloader task aborted." + e); return false; } catch (RuntimeException e) { MyLog.e(getClass().getSimpleName(), "bootloader task fail." + e); return false; } try { if (needInit) { showProgressDialog(); clean(); MyLog.v(TAG, "assetFiles size=" + getAssetsFileList().size()); for (String path : getAssetsFileList()) { copyAssetToLocal(path); } bootloaderPreferences .saveAppVersionCode(getAppliationVersionCode()); bootloaderPreferences.updateLastPackageUpdatedTime(); } } catch (AbortException e) { MyLog.e(getClass().getSimpleName(), "local file bootloader abort." + e.getMessage()); return false; } catch (RuntimeException e) { MyLog.e(getClass().getSimpleName(), "local file bootloader fail" + e.getMessage()); return false; } return true; } @Override protected void onPostExecute(Boolean isSuccess) { dismissProgressDialog(); if (isSuccess) { success.run(); } else { fail.run(); } } protected void showProgressDialog() { handler.post(new Runnable() { @Override public void run() { loadingDialog = new ProgressDialog(context); loadingDialog.setMessage("Loading..."); loadingDialog.show(); loadingDialog.setCancelable(false); } }); } protected void dismissProgressDialog() { handler.post(new Runnable() { @Override public void run() { if (loadingDialog != null) { loadingDialog.dismiss(); loadingDialog = null; } } }); } protected void showAbortAlert() { showAlert("インストールに失敗しました。ディスクの容量を増やして再度実行してください。"); } protected void showAlert(final String message) { handler.post(new Runnable() { @Override public void run() { new AlertDialog.Builder(context).setTitle("") .setMessage(message).setCancelable(true); } }); } } }