summaryrefslogtreecommitdiffstats
path: root/Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java')
-rw-r--r--Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java647
1 files changed, 647 insertions, 0 deletions
diff --git a/Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java b/Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java
new file mode 100644
index 0000000..8956e8a
--- /dev/null
+++ b/Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java
@@ -0,0 +1,647 @@
+/*
+ Copyright (c) 2011, BogDan Vatra <bog_dan_ro@yahoo.com>
+
+ 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/>.
+*/
+
+package org.kde.necessitas.ministro;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.http.client.ClientProtocolException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.NotificationManager;
+import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings;
+
+public class MinistroActivity extends Activity
+{
+
+ public native static int nativeChmode(String filepath, int mode);
+ private static final String DOMAIN_NAME="http://files.kde.org/necessitas/ministro/";
+
+ private String[] m_modules;
+ private int m_id=-1;
+ private String m_qtLibsRootPath;
+
+ private void checkNetworkAndDownload(final boolean update)
+ {
+ if (isOnline(this))
+ new CheckLibraries().execute(update);
+ else
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(MinistroActivity.this);
+ builder.setMessage(getResources().getString(R.string.ministro_network_access_msg));
+ builder.setCancelable(true);
+ builder.setNeutralButton(getResources().getString(R.string.setting_msg), new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ final ProgressDialog m_dialog = ProgressDialog.show(MinistroActivity.this, null, getResources().getString(R.string.wait_for_network_connection_msg), true, true, new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ finishMe();
+ }
+ });
+ getApplication().registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ if (isOnline(MinistroActivity.this))
+ {
+ getApplication().unregisterReceiver(this);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run()
+ {
+ m_dialog.dismiss();
+ new CheckLibraries().execute(update);
+ }
+ });
+ }
+ }
+ }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+ startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
+ dialog.dismiss();
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id)
+ {
+ dialog.cancel();
+ }
+ });
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ finishMe();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ }
+
+ private ServiceConnection m_ministroConnection=new ServiceConnection()
+ {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service)
+ {
+ if (getIntent().hasExtra("id") && getIntent().hasExtra("modules"))
+ {
+ m_id=getIntent().getExtras().getInt("id");
+ m_modules=getIntent().getExtras().getStringArray("modules");
+ AlertDialog.Builder builder = new AlertDialog.Builder(MinistroActivity.this);
+ builder.setMessage(getResources().getString(R.string.download_app_libs_msg,
+ getIntent().getExtras().getString("name")))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ checkNetworkAndDownload(false);
+ }
+ })
+ .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ finishMe();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+ else
+ checkNetworkAndDownload(true);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name)
+ {
+ m_ministroConnection = null;
+ }
+ };
+
+ void finishMe()
+ {
+ if (-1 != m_id && null != MinistroService.instance())
+ MinistroService.instance().retrievalFinished(m_id);
+ else
+ {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancelAll();
+ }
+ finish();
+ }
+
+ private static URL getVersionUrl(Context c) throws MalformedURLException
+ {
+ return new URL(DOMAIN_NAME+MinistroService.getRepository(c)+"/android/"+android.os.Build.CPU_ABI+"/android-"+android.os.Build.VERSION.SDK_INT+"/versions.xml");
+ }
+
+ private static URL getLibsXmlUrl(Context c, double version) throws MalformedURLException
+ {
+ return new URL(DOMAIN_NAME+MinistroService.getRepository(c)+"/android/"+android.os.Build.CPU_ABI+"/android-"+android.os.Build.VERSION.SDK_INT+"/libs-"+version+".xml");
+ }
+
+ public static boolean isOnline(Context c)
+ {
+ ConnectivityManager cm = (ConnectivityManager) c.getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo netInfo = cm.getActiveNetworkInfo();
+ if (netInfo != null && netInfo.isConnectedOrConnecting())
+ return true;
+ return false;
+ }
+
+ public static double downloadVersionXmlFile(Context c, boolean checkOnly)
+ {
+ if (!isOnline(c))
+ return-1;
+ try
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document dom = null;
+ Element root = null;
+ URLConnection connection = getVersionUrl(c).openConnection();
+ dom = builder.parse(connection.getInputStream());
+ root = dom.getDocumentElement();
+ root.normalize();
+ double version = Double.valueOf(root.getAttribute("latest"));
+ if ( MinistroService.instance().getVersion() >= version )
+ return MinistroService.instance().getVersion();
+
+ if (checkOnly)
+ return version;
+
+ connection = getLibsXmlUrl(c, version).openConnection();
+ connection.setRequestProperty("Accept-Encoding", "gzip,deflate");
+ File file= new File(MinistroService.instance().getVersionXmlFile());
+ file.delete();
+ FileOutputStream outstream = new FileOutputStream(MinistroService.instance().getVersionXmlFile());
+ InputStream instream = connection.getInputStream();
+ byte[] tmp = new byte[2048];
+ int downloaded;
+ while ((downloaded = instream.read(tmp)) != -1)
+ outstream.write(tmp, 0, downloaded);
+
+ outstream.close();
+ MinistroService.instance().refreshLibraries(false);
+ return version;
+ } catch (ClientProtocolException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return -1;
+ }
+
+ private class DownloadManager extends AsyncTask<Library, Integer, Long>
+ {
+ private ProgressDialog m_dialog = null;
+ private String m_status = getResources().getString(R.string.start_downloading_msg);
+ private int m_totalSize=0, m_totalProgressSize=0;
+
+ @Override
+ protected void onPreExecute()
+ {
+ m_dialog = new ProgressDialog(MinistroActivity.this);
+ m_dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ m_dialog.setTitle(getResources().getString(R.string.downloading_qt_libraries_msg));
+ m_dialog.setMessage(m_status);
+ m_dialog.setCancelable(true);
+ m_dialog.setOnCancelListener(new DialogInterface.OnCancelListener(){
+ @Override
+ public void onCancel(DialogInterface dialog)
+ {
+ DownloadManager.this.cancel(false);
+ finishMe();
+ }
+ });
+ m_dialog.show();
+ super.onPreExecute();
+ }
+
+ private boolean DownloadItem(String url, String file, long size, String fileSha1) throws NoSuchAlgorithmException, MalformedURLException, IOException
+ {
+ MessageDigest digester = MessageDigest.getInstance("SHA-1");
+ URLConnection connection = new URL(url).openConnection();
+ connection.setRequestProperty("Accept-Encoding", "gzip,deflate");
+ Library.mkdirParents(m_qtLibsRootPath, file, 1);
+ String filePath=m_qtLibsRootPath+file;
+ int progressSize=0;
+ try
+ {
+ FileOutputStream outstream = new FileOutputStream(filePath);
+ InputStream instream = connection.getInputStream();
+ int downloaded;
+ byte[] tmp = new byte[2048];
+ int oldProgress=-1;
+ while ((downloaded = instream.read(tmp)) != -1)
+ {
+ if (isCancelled())
+ break;
+ progressSize+=downloaded;
+ m_totalProgressSize+=downloaded;
+ digester.update(tmp, 0, downloaded);
+ outstream.write(tmp, 0, downloaded);
+ int progress=(int)(progressSize*100/size);
+ if (progress!=oldProgress)
+ {
+ publishProgress(progress
+ , m_totalProgressSize);
+ oldProgress = progress;
+ }
+ }
+ String sha1 = Library.convertToHex(digester.digest());
+ if (sha1.equalsIgnoreCase(fileSha1))
+ {
+ outstream.close();
+ nativeChmode(filePath, 0644);
+ MinistroService.instance().refreshLibraries(false);
+ return true;
+ }
+ outstream.close();
+ File f = new File(filePath);
+ f.delete();
+ } catch (Exception e) {
+ e.printStackTrace();
+ File f = new File(filePath);
+ f.delete();
+ }
+ m_totalProgressSize-=progressSize;
+ return false;
+ }
+
+ @Override
+ protected Long doInBackground(Library... params)
+ {
+ try
+ {
+ for (int i=0;i<params.length;i++)
+ {
+ m_totalSize+=params[i].size;
+ if (null != params[i].needs)
+ for (int j=0;j<params[i].needs.length;j++)
+ m_totalSize+=params[i].needs[j].size;
+ }
+
+ m_dialog.setMax(m_totalSize);
+ int lastId=-1;
+ for (int i=0;i<params.length;i++)
+ {
+ if (isCancelled())
+ break;
+ synchronized (m_status)
+ {
+ m_status=params[i].name+" ";
+ }
+ publishProgress(0, m_totalProgressSize);
+ if (!DownloadItem(params[i].url, params[i].filePath, params[i].size, params[i].sha1))
+ {
+ // sometimes for some reasons which I don't understand, Ministro receives corrupt data, so let's give it another chance.
+ if (i == lastId)
+ break;
+ lastId=i;
+ --i;
+ continue;
+ }
+
+ lastId=-1;
+ if (null != params[i].needs)
+ for (int j=0;j<params[i].needs.length;j++)
+ {
+ synchronized (m_status)
+ {
+ m_status=params[i].needs[j].name+" ";
+ }
+ publishProgress(0, m_totalProgressSize);
+ if (!DownloadItem(params[i].needs[j].url, params[i].needs[j].filePath, params[i].needs[j].size, params[i].needs[j].sha1))
+ {
+ // sometimes for some reasons which I don't understand, Ministro receives corrupt data, so let's give it another chance.
+ if (j == lastId)
+ break;
+ lastId=j;
+ --j;
+ continue;
+ }
+ lastId=-1;
+ }
+ }
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values)
+ {
+ synchronized (m_status)
+ {
+ m_dialog.setMessage(m_status+values[0]+"%");
+ m_dialog.setProgress(values[1]);
+ }
+ super.onProgressUpdate(values);
+ }
+
+ @Override
+ protected void onPostExecute(Long result)
+ {
+ super.onPostExecute(result);
+ if (m_dialog != null)
+ {
+ m_dialog.dismiss();
+ m_dialog = null;
+ }
+ finishMe();
+ }
+ }
+
+ private class CheckLibraries extends AsyncTask<Boolean, Void, Double>
+ {
+ private ProgressDialog dialog = null;
+ private ArrayList<Library> newLibs = new ArrayList<Library>();
+ private String m_message;
+ @Override
+ protected void onPreExecute()
+ {
+ runOnUiThread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ dialog = ProgressDialog.show(MinistroActivity.this, null,
+ getResources().getString(R.string.checking_libraries_msg), true, true);
+ }
+ });
+ super.onPreExecute();
+ }
+
+ @Override
+ protected Double doInBackground(Boolean... update)
+ {
+ double version=0.0;
+ try
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document dom = null;
+ Element root = null;
+ double oldVersion=MinistroService.instance().getVersion();
+ if (update[0] || MinistroService.instance().getVersion()<0)
+ version = downloadVersionXmlFile(MinistroActivity.this, false);
+ else
+ version = MinistroService.instance().getVersion();
+
+ ArrayList<Library> libraries;
+ if (update[0])
+ {
+ if (oldVersion!=version)
+ libraries = MinistroService.instance().getDownloadedLibraries();
+ else
+ return version;
+ }
+ else
+ libraries = MinistroService.instance().getAvailableLibraries();
+
+ ArrayList<String> notFoundModules = new ArrayList<String>();
+ if (m_modules!=null)
+ MinistroService.instance().checkModules(m_modules, notFoundModules);
+
+ dom = builder.parse(new FileInputStream(MinistroService.instance().getVersionXmlFile()));
+
+ factory = DocumentBuilderFactory.newInstance();
+ builder = factory.newDocumentBuilder();
+ root = dom.getDocumentElement();
+ root.normalize();
+
+ // extract device root certificates
+ SharedPreferences preferences=getSharedPreferences("Ministro", MODE_PRIVATE);
+ if (!preferences.getString("CODENAME", "").equals(android.os.Build.VERSION.CODENAME) ||
+ !preferences.getString("INCREMENTAL", "").equals(android.os.Build.VERSION.INCREMENTAL) ||
+ !preferences.getString("RELEASE", "").equals(android.os.Build.VERSION.RELEASE))
+ {
+ m_message = getResources().getString(R.string.extracting_SSL_msg);
+ publishProgress((Void[])null);
+ String environmentVariables=root.getAttribute("environmentVariables");
+ environmentVariables=environmentVariables.replaceAll("MINISTRO_PATH", "");
+ String environmentVariablesList[]=environmentVariables.split("\t");
+ for (int i=0;i<environmentVariablesList.length;i++)
+ {
+ String environmentVariable[]=environmentVariablesList[i].split("=");
+ if (environmentVariable[0].equals("MINISTRO_SSL_CERTS_PATH"))
+ {
+ String path=Library.mkdirParents(getFilesDir().getAbsolutePath(),environmentVariable[1], 0);
+ Library.removeAllFiles(path);
+ try
+ {
+ KeyStore ks= KeyStore.getInstance(KeyStore.getDefaultType());
+ FileInputStream instream = new FileInputStream(new File("/system/etc/security/cacerts.bks"));
+ ks.load(instream, null);
+ for (Enumeration<String> aliases = ks.aliases(); aliases.hasMoreElements(); )
+ {
+ String aName = aliases.nextElement();
+ try
+ {
+ X509Certificate cert=(X509Certificate) ks.getCertificate(aName);
+ if (null==cert)
+ continue;
+ String filePath=path+"/"+cert.getType()+"_"+cert.hashCode()+".der";
+ FileOutputStream outstream = new FileOutputStream(new File(filePath));
+ byte buff[]=cert.getEncoded();
+ outstream.write(buff, 0, buff.length);
+ outstream.close();
+ nativeChmode(filePath, 0644);
+ } catch(KeyStoreException e) {
+ e.printStackTrace();
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ } catch (CertificateException e) {
+ e.printStackTrace();
+ }
+ SharedPreferences.Editor editor= preferences.edit();
+ editor.putString("CODENAME",android.os.Build.VERSION.CODENAME);
+ editor.putString("INCREMENTAL", android.os.Build.VERSION.INCREMENTAL);
+ editor.putString("RELEASE", android.os.Build.VERSION.RELEASE);
+ editor.commit();
+ break;
+ }
+ }
+ }
+
+ Node node = root.getFirstChild();
+ while(node != null)
+ {
+ if (node.getNodeType() == Node.ELEMENT_NODE)
+ {
+ Library lib= Library.getLibrary((Element)node, true);
+ if (update[0])
+ { // check for updates
+ for (int j=0;j<libraries.size();j++)
+ if (libraries.get(j).name.equals(lib.name))
+ {
+ newLibs.add(lib);
+ break;
+ }
+ }
+ else
+ {// download missing libraries
+ for(String module : notFoundModules)
+ if (module.equals(lib.name))
+ {
+ newLibs.add(lib);
+ break;
+ }
+ }
+ }
+
+ // Workaround for an unbelievable bug !!!
+ try {
+ node = node.getNextSibling();
+ } catch (Exception e) {
+ e.printStackTrace();
+ break;
+ }
+ }
+ return version;
+ } catch (ClientProtocolException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return -1.;
+ }
+
+ @Override
+ protected void onProgressUpdate(Void... nothing)
+ {
+ dialog.setMessage(m_message);
+ super.onProgressUpdate(nothing);
+ }
+
+ @Override
+ protected void onPostExecute(Double result)
+ {
+ if (null != dialog)
+ {
+ dialog.dismiss();
+ dialog = null;
+ }
+ if (newLibs.size()>0 && result>0)
+ {
+ Library[] libs = new Library[newLibs.size()];
+ libs = newLibs.toArray(libs);
+ new DownloadManager().execute(libs);
+ }
+ else
+ finishMe();
+ super.onPostExecute(result);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ m_qtLibsRootPath = getFilesDir().getAbsolutePath()+"/qt/";
+ File dir=new File(m_qtLibsRootPath);
+ dir.mkdirs();
+ nativeChmode(m_qtLibsRootPath, 0755);
+ bindService(new Intent("org.kde.necessitas.ministro"), m_ministroConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ unbindService(m_ministroConnection);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig)
+ {
+ //Avoid activity from being destroyed/created
+ super.onConfigurationChanged(newConfig);
+ }
+
+ static
+ {
+ System.loadLibrary("chmode");
+ }
+}