diff options
author | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com> | 2013-02-13 09:08:37 +0100 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com> | 2013-02-14 09:29:47 +0100 |
commit | 27438946427ea006a2dfb6369fdb742abcf9ea05 (patch) | |
tree | fa3ad8f3b0618cc14b437413452906de273d017f /Ministro/src/org | |
parent | 2ef46bbfada155ae65e999f633f7edfda751438a (diff) | |
parent | f71e67d47f681b55a8b68d051e607e8478958638 (diff) |
Change-Id: I4b5bf0cbceebc4823c444617d19cd6dd9d6b2e02
Diffstat (limited to 'Ministro/src/org')
7 files changed, 3359 insertions, 0 deletions
diff --git a/Ministro/src/org/kde/necessitas/ministro/ExtractStyle.java b/Ministro/src/org/kde/necessitas/ministro/ExtractStyle.java new file mode 100644 index 0000000..7d02707 --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/ExtractStyle.java @@ -0,0 +1,1291 @@ +/* + 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.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.ArrayList; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xmlpull.v1.XmlPullParser; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.GradientDrawable.Orientation; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.NinePatchDrawable; +import android.graphics.drawable.StateListDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Xml; +import android.view.inputmethod.EditorInfo; + + +public class ExtractStyle { + + native static int[] extractChunkInfo(byte[] chunkData); + native static int[] extract9PatchInfo(Object ninePatchDrawable); + native static Object getClipStateDrawableObject(Object clipStateObject); + + Class<?> styleableClass = getStylableClass(); + final int[] EMPTY_STATE_SET = {}; + final int[] ENABLED_STATE_SET = {android.R.attr.state_enabled}; + final int[] FOCUSED_STATE_SET = {android.R.attr.state_focused}; + final int[] SELECTED_STATE_SET = {android.R.attr.state_selected}; + final int[] PRESSED_STATE_SET = {android.R.attr.state_pressed}; + final int[] WINDOW_FOCUSED_STATE_SET = {android.R.attr.state_window_focused}; + final int[] ENABLED_FOCUSED_STATE_SET = stateSetUnion(ENABLED_STATE_SET, FOCUSED_STATE_SET); + final int[] ENABLED_SELECTED_STATE_SET = stateSetUnion(ENABLED_STATE_SET, SELECTED_STATE_SET); + final int[] ENABLED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] FOCUSED_SELECTED_STATE_SET = stateSetUnion(FOCUSED_STATE_SET, SELECTED_STATE_SET); + final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] ENABLED_FOCUSED_SELECTED_STATE_SET = stateSetUnion(ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + final int[] ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(ENABLED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_SELECTED_STATE_SET = stateSetUnion(PRESSED_STATE_SET, SELECTED_STATE_SET); + final int[] PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_FOCUSED_STATE_SET = stateSetUnion(PRESSED_STATE_SET, FOCUSED_STATE_SET); + final int[] PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_FOCUSED_SELECTED_STATE_SET = stateSetUnion(PRESSED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + final int[] PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_ENABLED_STATE_SET = stateSetUnion(PRESSED_STATE_SET, ENABLED_STATE_SET); + final int[] PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_ENABLED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_ENABLED_SELECTED_STATE_SET = stateSetUnion(PRESSED_ENABLED_STATE_SET, SELECTED_STATE_SET); + final int[] PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_ENABLED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_ENABLED_FOCUSED_STATE_SET = stateSetUnion(PRESSED_ENABLED_STATE_SET, FOCUSED_STATE_SET); + final int[] PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + final int[] PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = stateSetUnion(PRESSED_ENABLED_FOCUSED_STATE_SET, SELECTED_STATE_SET); + final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = stateSetUnion(PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, WINDOW_FOCUSED_STATE_SET); + + + final int View_background = getField(styleableClass,"View_background"); + final int View_padding = getField(styleableClass,"View_padding"); + final int View_paddingLeft = getField(styleableClass,"View_paddingLeft"); + final int View_paddingTop = getField(styleableClass,"View_paddingTop"); + final int View_paddingRight = getField(styleableClass,"View_paddingRight"); + final int View_paddingBottom = getField(styleableClass,"View_paddingBottom"); + final int View_scrollX = getField(styleableClass,"View_scrollX"); + final int View_scrollY = getField(styleableClass,"View_scrollY"); + final int View_id = getField(styleableClass,"View_id"); + final int View_tag = getField(styleableClass,"View_tag"); + final int View_fitsSystemWindows = getField(styleableClass,"View_fitsSystemWindows"); + final int View_focusable = getField(styleableClass,"View_focusable"); + final int View_focusableInTouchMode = getField(styleableClass,"View_focusableInTouchMode"); + final int View_clickable = getField(styleableClass,"View_clickable"); + final int View_longClickable = getField(styleableClass,"View_longClickable"); + final int View_saveEnabled = getField(styleableClass,"View_saveEnabled"); + final int View_duplicateParentState = getField(styleableClass,"View_duplicateParentState"); + final int View_visibility = getField(styleableClass,"View_visibility"); + final int View_drawingCacheQuality = getField(styleableClass,"View_drawingCacheQuality"); + final int View_contentDescription = getField(styleableClass,"View_contentDescription"); + final int View_soundEffectsEnabled = getField(styleableClass,"View_soundEffectsEnabled"); + final int View_hapticFeedbackEnabled = getField(styleableClass,"View_hapticFeedbackEnabled"); + final int View_scrollbars = getField(styleableClass,"View_scrollbars"); + final int View_fadingEdge = getField(styleableClass,"View_fadingEdge"); + final int View_scrollbarStyle = getField(styleableClass,"View_scrollbarStyle"); + final int View_isScrollContainer = getField(styleableClass,"View_isScrollContainer"); + final int View_keepScreenOn = getField(styleableClass,"View_keepScreenOn"); + final int View_filterTouchesWhenObscured = getField(styleableClass,"View_filterTouchesWhenObscured"); + final int View_nextFocusLeft = getField(styleableClass,"View_nextFocusLeft"); + final int View_nextFocusRight = getField(styleableClass,"View_nextFocusRight"); + final int View_nextFocusUp = getField(styleableClass,"View_nextFocusUp"); + final int View_nextFocusDown = getField(styleableClass,"View_nextFocusDown"); + final int View_minWidth = getField(styleableClass,"View_minWidth"); + final int View_minHeight = getField(styleableClass,"View_minHeight"); + final int View_onClick = getField(styleableClass,"View_onClick"); + final int View_overScrollMode = getField(styleableClass,"View_overScrollMode"); + + + final int TextAppearance_textColorHighlight = getField(styleableClass,"TextAppearance_textColorHighlight"); + final int TextAppearance_textColor = getField(styleableClass,"TextAppearance_textColor"); + final int TextAppearance_textColorHint = getField(styleableClass,"TextAppearance_textColorHint"); + final int TextAppearance_textColorLink = getField(styleableClass,"TextAppearance_textColorLink"); + final int TextAppearance_textSize = getField(styleableClass,"TextAppearance_textSize"); + final int TextAppearance_typeface = getField(styleableClass,"TextAppearance_typeface"); + final int TextAppearance_textStyle = getField(styleableClass,"TextAppearance_textStyle"); + final int TextAppearance_textAllCaps = getField(styleableClass,"TextAppearance_textAllCaps"); + final int TextView_editable = getField(styleableClass,"TextView_editable"); + final int TextView_inputMethod = getField(styleableClass,"TextView_inputMethod"); + final int TextView_numeric = getField(styleableClass,"TextView_numeric"); + final int TextView_digits = getField(styleableClass,"TextView_digits"); + final int TextView_phoneNumber = getField(styleableClass,"TextView_phoneNumber"); + final int TextView_autoText = getField(styleableClass,"TextView_autoText"); + final int TextView_capitalize = getField(styleableClass,"TextView_capitalize"); + final int TextView_bufferType = getField(styleableClass,"TextView_bufferType"); + final int TextView_selectAllOnFocus = getField(styleableClass,"TextView_selectAllOnFocus"); + final int TextView_autoLink = getField(styleableClass,"TextView_autoLink"); + final int TextView_linksClickable = getField(styleableClass,"TextView_linksClickable"); + final int TextView_drawableLeft = getField(styleableClass,"TextView_drawableLeft"); + final int TextView_drawableTop = getField(styleableClass,"TextView_drawableTop"); + final int TextView_drawableRight = getField(styleableClass,"TextView_drawableRight"); + final int TextView_drawableBottom = getField(styleableClass,"TextView_drawableBottom"); + final int TextView_drawableStart = getField(styleableClass,"TextView_drawableStart"); + final int TextView_drawableEnd = getField(styleableClass,"TextView_drawableEnd"); + final int TextView_drawablePadding = getField(styleableClass,"TextView_drawablePadding"); + final int TextView_textCursorDrawable = getField(styleableClass,"TextView_textCursorDrawable"); + final int TextView_maxLines = getField(styleableClass,"TextView_maxLines"); + final int TextView_maxHeight = getField(styleableClass,"TextView_maxHeight"); + final int TextView_lines = getField(styleableClass,"TextView_lines"); + final int TextView_height = getField(styleableClass,"TextView_height"); + final int TextView_minLines = getField(styleableClass,"TextView_minLines"); + final int TextView_minHeight = getField(styleableClass,"TextView_minHeight"); + final int TextView_maxEms = getField(styleableClass,"TextView_maxEms"); + final int TextView_maxWidth = getField(styleableClass,"TextView_maxWidth"); + final int TextView_ems = getField(styleableClass,"TextView_ems"); + final int TextView_width = getField(styleableClass,"TextView_width"); + final int TextView_minEms = getField(styleableClass,"TextView_minEms"); + final int TextView_minWidth = getField(styleableClass,"TextView_minWidth"); + final int TextView_gravity = getField(styleableClass,"TextView_gravity"); + final int TextView_hint = getField(styleableClass,"TextView_hint"); + final int TextView_text = getField(styleableClass,"TextView_text"); + final int TextView_scrollHorizontally = getField(styleableClass,"TextView_scrollHorizontally"); + final int TextView_singleLine = getField(styleableClass,"TextView_singleLine"); + final int TextView_ellipsize = getField(styleableClass,"TextView_ellipsize"); + final int TextView_marqueeRepeatLimit = getField(styleableClass,"TextView_marqueeRepeatLimit"); + final int TextView_includeFontPadding = getField(styleableClass,"TextView_includeFontPadding"); + final int TextView_cursorVisible = getField(styleableClass,"TextView_cursorVisible"); + final int TextView_maxLength = getField(styleableClass,"TextView_maxLength"); + final int TextView_textScaleX = getField(styleableClass,"TextView_textScaleX"); + final int TextView_freezesText = getField(styleableClass,"TextView_freezesText"); + final int TextView_shadowColor = getField(styleableClass,"TextView_shadowColor"); + final int TextView_shadowDx = getField(styleableClass,"TextView_shadowDx"); + final int TextView_shadowDy = getField(styleableClass,"TextView_shadowDy"); + final int TextView_shadowRadius = getField(styleableClass,"TextView_shadowRadius"); + final int TextView_enabled = getField(styleableClass,"TextView_enabled"); + final int TextView_textColorHighlight = getField(styleableClass,"TextView_textColorHighlight"); + final int TextView_textColor = getField(styleableClass,"TextView_textColor"); + final int TextView_textColorHint = getField(styleableClass,"TextView_textColorHint"); + final int TextView_textColorLink = getField(styleableClass,"TextView_textColorLink"); + final int TextView_textSize = getField(styleableClass,"TextView_textSize"); + final int TextView_typeface = getField(styleableClass,"TextView_typeface"); + final int TextView_textStyle = getField(styleableClass,"TextView_textStyle"); + final int TextView_password = getField(styleableClass,"TextView_password"); + final int TextView_lineSpacingExtra = getField(styleableClass,"TextView_lineSpacingExtra"); + final int TextView_lineSpacingMultiplier = getField(styleableClass,"TextView_lineSpacingMultiplier"); + final int TextView_inputType = getField(styleableClass,"TextView_inputType"); + final int TextView_imeOptions = getField(styleableClass,"TextView_imeOptions"); + final int TextView_imeActionLabel = getField(styleableClass,"TextView_imeActionLabel"); + final int TextView_imeActionId = getField(styleableClass,"TextView_imeActionId"); + final int TextView_privateImeOptions = getField(styleableClass,"TextView_privateImeOptions"); + final int TextView_textSelectHandleLeft = getField(styleableClass,"TextView_textSelectHandleLeft"); + final int TextView_textSelectHandleRight = getField(styleableClass,"TextView_textSelectHandleRight"); + final int TextView_textSelectHandle = getField(styleableClass,"TextView_textSelectHandle"); + final int TextView_textIsSelectable = getField(styleableClass,"TextView_textIsSelectable"); + final int TextView_textAllCaps = getField(styleableClass,"TextView_textAllCaps"); + + final int ImageView_src = getField(styleableClass,"ImageView_src"); + final int ImageView_baselineAlignBottom = getField(styleableClass,"ImageView_baselineAlignBottom"); + final int ImageView_adjustViewBounds = getField(styleableClass,"ImageView_adjustViewBounds"); + final int ImageView_maxWidth = getField(styleableClass,"ImageView_maxWidth"); + final int ImageView_maxHeight = getField(styleableClass,"ImageView_maxHeight"); + final int ImageView_scaleType = getField(styleableClass,"ImageView_scaleType"); + final int ImageView_tint = getField(styleableClass,"ImageView_tint"); + final int ImageView_cropToPadding = getField(styleableClass,"ImageView_cropToPadding"); + + final Resources.Theme m_theme; + final String m_extractPath; + Context m_context; + + + class FakeCanvas extends Canvas { + int[] chunkData= null; + class Size { + public int s,e; + Size(int start, int end) + { + s=start; + e=end; + } + } + public boolean isHardwareAccelerated() { + return true; + } + public void drawPatch(Bitmap bmp, byte[] chunks, RectF dst, Paint paint) { + chunkData=extractChunkInfo(chunks); + } + } + + + + private int[] stateSetUnion(final int[] stateSet1, final int[] stateSet2) + { + try + { + final int stateSet1Length = stateSet1.length; + final int stateSet2Length = stateSet2.length; + final int[] newSet = new int[stateSet1Length + stateSet2Length]; + int k = 0; + int i = 0; + int j = 0; + // This is a merge of the two input state sets and assumes that the + // input sets are sorted by the order imposed by ViewDrawableStates. + int[] viewDrawableStatesState=(int[]) styleableClass.getDeclaredField("ViewDrawableStates").get(null); + for (int viewState : viewDrawableStatesState) + { + if (i < stateSet1Length && stateSet1[i] == viewState) + { + newSet[k++] = viewState; + i++; + } else if (j < stateSet2Length && stateSet2[j] == viewState) { + newSet[k++] = viewState; + j++; + } + if (k > 1) { + assert(newSet[k - 1] > newSet[k - 2]); + } + } + return newSet; + } + catch(Exception e) + { + e.printStackTrace(); + } + return null; + } + + private Class<?> getStylableClass() { + try { + return Class.forName("android.R$styleable"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } + + int getField(Class<?> clazz, String fieldName) + { + try { + return clazz.getDeclaredField(fieldName).getInt(null); + } catch (Exception e) { + e.printStackTrace(); + } + return -1; + } + + JSONObject getColorStateList(ColorStateList colorList) + { + JSONObject json = new JSONObject(); + try + { + json.put("EMPTY_STATE_SET", colorList.getColorForState(EMPTY_STATE_SET, 0)); + json.put("WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(WINDOW_FOCUSED_STATE_SET, 0)); + json.put("SELECTED_STATE_SET", colorList.getColorForState(SELECTED_STATE_SET, 0)); + json.put("SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("FOCUSED_STATE_SET", colorList.getColorForState(FOCUSED_STATE_SET, 0)); + json.put("FOCUSED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(FOCUSED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("FOCUSED_SELECTED_STATE_SET", colorList.getColorForState(FOCUSED_SELECTED_STATE_SET, 0)); + json.put("FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("ENABLED_STATE_SET", colorList.getColorForState(ENABLED_STATE_SET, 0)); + json.put("ENABLED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(ENABLED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("ENABLED_SELECTED_STATE_SET", colorList.getColorForState(ENABLED_SELECTED_STATE_SET, 0)); + json.put("ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("ENABLED_FOCUSED_STATE_SET", colorList.getColorForState(ENABLED_FOCUSED_STATE_SET, 0)); + json.put("ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("ENABLED_FOCUSED_SELECTED_STATE_SET", colorList.getColorForState(ENABLED_FOCUSED_SELECTED_STATE_SET, 0)); + json.put("ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_STATE_SET", colorList.getColorForState(PRESSED_STATE_SET, 0)); + json.put("PRESSED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_SELECTED_STATE_SET", colorList.getColorForState(PRESSED_SELECTED_STATE_SET, 0)); + json.put("PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_FOCUSED_SELECTED_STATE_SET", colorList.getColorForState(PRESSED_FOCUSED_SELECTED_STATE_SET, 0)); + json.put("PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_SELECTED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_SELECTED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET, 0)); + json.put("PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET", colorList.getColorForState(PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET, 0)); + } catch (JSONException e) { + e.printStackTrace(); + } + + return json; + } + + final int [] DrawableStates ={android.R.attr.state_active, android.R.attr.state_checked + , android.R.attr.state_enabled, android.R.attr.state_focused + , android.R.attr.state_pressed, android.R.attr.state_selected + , android.R.attr.state_window_focused, 16908288}; + final String[] DrawableStatesLabels = {"active", "checked", "enabled", "focused", "pressed", "selected", "window_focused", "backdroud"}; + final String[] DisableDrawableStatesLabels = {"inactive", "unchecked", "disabled", "not_focused", "no_pressed", "unselected", "window_not_focused", "backdroud"}; + + String getFileName(String file, String[] states) + { + for (String state: states) + file+="__"+state; + return file; + } + + String getStatesName(String[] states) + { + String statesName=""; + for (String state: states) + { + if (statesName.length()>0) + statesName+="__"; + statesName += state; + } + return statesName; + } + + void addDrawableItemIfNotExists(JSONObject json, ArrayList<Integer> list, Drawable item, String[] states, String filename) + { + for(Integer it : list) + { + if (it.equals(item.hashCode())) + return; + } + list.add(item.hashCode()); + try { + json.put(getStatesName(states), getDrawable(item, getFileName(filename, states))); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + void addSolution(String filename, JSONObject json, int c, Drawable drawable, ArrayList<Integer> drawableList, int u) + { + int pos=0; + int states[] = new int[c]; + String [] statesText = new String[c]; + + for(int n= 0;u > 0;++n, u>>= 1) + if((u & 1) > 0) + { + statesText[pos]=DrawableStatesLabels[n]; + states[pos++]=DrawableStates[n]; + } + drawable.setState(states); + addDrawableItemIfNotExists(json, drawableList, drawable.getCurrent(), statesText, filename); + } + + int bitCount(int u) + { + int n; + for(n= 0;u > 0;++n, u&= (u - 1)); + return n; + } + + Object getStateListDrawable_old(Drawable drawable, String filename) + { + JSONObject json = new JSONObject(); + ArrayList<Integer> drawableList= new ArrayList<Integer>(); + drawable.setState(EMPTY_STATE_SET); + try { + json.put("empty", getDrawable(drawable.getCurrent(), filename)); + drawableList.add(drawable.getCurrent().hashCode()); + } catch (JSONException e) { + e.printStackTrace(); + } + for (int c = 1;c<=DrawableStates.length;c++) + for(int u= 0;u < 1 << DrawableStates.length;u++) + if(bitCount(u) == c) + addSolution(filename, json, c, drawable, drawableList, u); + return json; + } + + JSONObject getStatesList(int [] states) throws JSONException + { + JSONObject json = new JSONObject(); + for (int s : states) + { + boolean found=false; + for (int d = 0;d<DrawableStates.length;d++) + { + if (s==DrawableStates[d]) + { + json.put(DrawableStatesLabels[d], true); + found=true; + break; + } + else if (s==-DrawableStates[d]) + { + json.put(DrawableStatesLabels[d], false); + + found=true; + break; + } + } + if (!found) + { + json.put("unhandled_state_"+s,s>0); + } + } + return json; + } + + String getStatesName(int [] states) + { + String statesName=""; + for (int s : states) + { + boolean found=false; + for (int d = 0;d<DrawableStates.length;d++) + { + if (s==DrawableStates[d]) + { + if (statesName.length()>0) + statesName+="__"; + statesName+=DrawableStatesLabels[d]; + found=true; + break; + } + else if (s==-DrawableStates[d]) + { + if (statesName.length()>0) + statesName+="__"; + statesName+=DisableDrawableStatesLabels[d]; + found=true; + break; + } + } + if (!found) + { + if (statesName.length()>0) + statesName+=";"; + statesName+=s; + } + } + if (statesName.length()>0) + return statesName; + return "empty"; + } + + private JSONObject getLayerDrawable(Object drawable, String filename) + { + JSONObject json = new JSONObject(); + LayerDrawable layers = (LayerDrawable) drawable; + final int nr=layers.getNumberOfLayers(); + try { + JSONArray array =new JSONArray(); + for (int i = 0; i < nr; i++) + { + JSONObject layerJsonObject=getDrawable(layers.getDrawable(i), filename+"__"+layers.getId(i)); + layerJsonObject.put("id", layers.getId(i)); + array.put(layerJsonObject); + } + json.put("type", "layer"); + Rect padding = new Rect(); + if (layers.getPadding(padding)) + json.put("padding", getJsonRect(padding)); + json.put("layers", array); + } catch (JSONException e) { + e.printStackTrace(); + } + return json; + } + + private JSONObject getStateListDrawable(Object drawable, String filename) + { + JSONObject json = new JSONObject(); + try { + StateListDrawable stateList = (StateListDrawable) drawable; + final int numStates = (Integer) StateListDrawable.class.getMethod("getStateCount").invoke(stateList); + JSONArray array =new JSONArray(); + for (int i = 0; i < numStates; i++) + { + JSONObject stateJson = new JSONObject(); + final Drawable d = (Drawable) StateListDrawable.class.getMethod("getStateDrawable", Integer.TYPE).invoke(stateList, i); + final int [] states = (int[]) StateListDrawable.class.getMethod("getStateSet", Integer.TYPE).invoke(stateList, i); + stateJson.put("states", getStatesList(states)); + stateJson.put("drawable", getDrawable(d, filename+"__"+getStatesName(states))); + array.put(stateJson); + } + json.put("type", "stateslist"); + Rect padding = new Rect(); + if (stateList.getPadding(padding)) + json.put("padding", getJsonRect(padding)); + json.put("stateslist", array); + } catch (Exception e) { + e.printStackTrace(); + } + return json; + } + + private JSONObject getGradientDrawable(GradientDrawable drawable) { + JSONObject json = new JSONObject(); + try { + json.put("type", "gradient"); + Object obj=drawable.getConstantState(); + Class<?> gradientStateClass=obj.getClass(); + json.put("shape",gradientStateClass.getField("mShape").getInt(obj)); + json.put("gradient",gradientStateClass.getField("mGradient").getInt(obj)); + GradientDrawable.Orientation orientation=(Orientation) gradientStateClass.getField("mOrientation").get(obj); + json.put("orientation",orientation.name()); + int [] intArray=(int[]) gradientStateClass.getField("mColors").get(obj); + json.put("colors",getJsonArray(intArray, 0, intArray.length)); + json.put("positions",getJsonArray((float[]) gradientStateClass.getField("mPositions").get(obj))); + json.put("solidColor",gradientStateClass.getField("mSolidColor").getInt(obj)); + json.put("strokeWidth",gradientStateClass.getField("mStrokeWidth").getInt(obj)); + json.put("strokeColor",gradientStateClass.getField("mStrokeColor").getInt(obj)); + json.put("strokeDashWidth",gradientStateClass.getField("mStrokeDashWidth").getFloat(obj)); + json.put("strokeDashGap",gradientStateClass.getField("mStrokeDashGap").getFloat(obj)); + json.put("radius",gradientStateClass.getField("mRadius").getFloat(obj)); + float [] floatArray=(float[]) gradientStateClass.getField("mRadiusArray").get(obj); + if (floatArray!=null) + json.put("radiusArray",getJsonArray(floatArray)); + Rect rc= (Rect) gradientStateClass.getField("mPadding").get(obj); + if (rc!=null) + json.put("padding",getJsonRect(rc)); + json.put("width",gradientStateClass.getField("mWidth").getInt(obj)); + json.put("height",gradientStateClass.getField("mHeight").getInt(obj)); + json.put("innerRadiusRatio",gradientStateClass.getField("mInnerRadiusRatio").getFloat(obj)); + json.put("thicknessRatio",gradientStateClass.getField("mThicknessRatio").getFloat(obj)); + json.put("innerRadius",gradientStateClass.getField("mInnerRadius").getInt(obj)); + json.put("thickness",gradientStateClass.getField("mThickness").getInt(obj)); + } catch (Exception e) { + e.printStackTrace(); + } + return json; + } + + private JSONObject getJsonRect(Rect rect) throws JSONException + { + JSONObject jsonRect = new JSONObject(); + jsonRect.put("left", rect.left); + jsonRect.put("top", rect.top); + jsonRect.put("right", rect.right); + jsonRect.put("bottom", rect.bottom); + return jsonRect; + + } + + private JSONArray getJsonArray(int[] array, int pos, int len) + { + JSONArray a = new JSONArray(); + final int l = pos+len; + for (int i=pos; i<l;i++) + a.put(array[i]); + return a; + } + + private JSONArray getJsonArray(float[] array) throws JSONException + { + JSONArray a = new JSONArray(); + if (array != null) + for (float val: array) + a.put(val); + return a; + } + + private JSONObject getJsonChunkInfo(int[] chunkData) throws JSONException + { + JSONObject jsonRect = new JSONObject(); + jsonRect.put("xdivs", getJsonArray(chunkData, 3, chunkData[0])); + jsonRect.put("ydivs", getJsonArray(chunkData, 3+chunkData[0], chunkData[1])); + jsonRect.put("colors", getJsonArray(chunkData, 3+chunkData[0]+chunkData[1], chunkData[2])); + return jsonRect; + } + + private JSONObject findPatchesMarings(Drawable d) throws JSONException + { + if (Build.VERSION.SDK_INT>=11) + { + FakeCanvas fc = new FakeCanvas(); + d.draw(fc); + return getJsonChunkInfo(fc.chunkData); + } + return getJsonChunkInfo(extract9PatchInfo(d)); + } + + public JSONObject getDrawable(Object drawable, String filename) + { + JSONObject json = new JSONObject(); + Bitmap bmp = null; + if (drawable instanceof Bitmap) + bmp = (Bitmap) drawable; + else + { + if (drawable instanceof BitmapDrawable) + bmp = ((BitmapDrawable)drawable).getBitmap(); + else + { + if (drawable instanceof LayerDrawable) + { + return getLayerDrawable(drawable, filename); + } + if (drawable instanceof StateListDrawable) + { + return getStateListDrawable(drawable, filename); + } + if (drawable instanceof GradientDrawable) + { + return getGradientDrawable((GradientDrawable) drawable); + } + if (drawable instanceof ClipDrawable) + { + try { + json.put("type", "clipDrawable"); + json.put("drawable", getDrawable(getClipStateDrawableObject(((ClipDrawable)drawable).getConstantState()), filename)); + Rect padding = new Rect(); + if (((Drawable) drawable).getPadding(padding)) + json.put("padding", getJsonRect(padding)); + } catch (Exception e) { + e.printStackTrace(); + } + return json; + } + if (drawable instanceof ColorDrawable) + { + bmp = Bitmap.createBitmap(1, 1, Config.ARGB_8888); + Drawable d = (Drawable) drawable; + d.setBounds(0, 0, 1, 1); + d.draw(new Canvas(bmp)); + Rect padding = new Rect(); + try { + json.put("type", "color"); + json.put("color", bmp.getPixel(0, 0)); + if (d.getPadding(padding)) + json.put("padding", getJsonRect(padding)); + } catch (JSONException e) { + e.printStackTrace(); + } + return json; } + else + { + Drawable d = (Drawable) drawable; + int w=d.getIntrinsicWidth(); + int h=d.getIntrinsicHeight(); + d.setLevel(10000); + if (w<1 || h< 1) + { + w=100; + h=100; + } + bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888); + d.setBounds(0, 0, w, h); + d.draw(new Canvas(bmp)); + if (drawable instanceof NinePatchDrawable) + { + NinePatchDrawable npd = (NinePatchDrawable) drawable; + try { + json.put("type", "9patch"); + json.put("drawable", getDrawable(bmp, filename)); + Rect padding = new Rect(); + if (npd.getPadding(padding)) + json.put("padding", getJsonRect(padding)); + json.put("chunkInfo", findPatchesMarings(d)); + return json; + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + } + } + FileOutputStream out; + try { + filename = m_extractPath+filename+".png"; + out = new FileOutputStream(filename); + bmp.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + json.put("type", "image"); + json.put("path", filename); + json.put("width", bmp.getWidth()); + json.put("height", bmp.getHeight()); + MinistroActivity.nativeChmode(filename, 0644); + } catch (JSONException e) { + e.printStackTrace(); + } + return json; + } + + public void extractViewInformations(String styleName, int styleId, JSONObject json, String qtClassName, AttributeSet attribSet) + { + try { + int[] viewAttrs; + viewAttrs = (int[]) styleableClass.getDeclaredField("View").get(null); + TypedArray a =m_theme.obtainStyledAttributes(attribSet, viewAttrs, styleId, 0); + + if (null != qtClassName) + json.put("qtClass", qtClassName); + + final int N = a.getIndexCount(); + for (int i = 0; i < N; i++) { + int attr = a.getIndex(i); + if (attr == View_background) + json.put("View_background", getDrawable(a.getDrawable(attr), styleName + "_View_background")); + else if (attr == View_padding) + json.put("View_padding", a.getDimensionPixelSize(attr, -1)); + else if (attr == View_paddingLeft) + json.put("View_paddingLeft", a.getDimensionPixelSize(attr, -1)); + else if (attr == View_paddingTop) + json.put("View_paddingTop", a.getDimensionPixelSize(attr, -1)); + else if (attr == View_paddingRight) + json.put("View_paddingRight", a.getDimensionPixelSize(attr, -1)); + else if (attr == View_paddingBottom) + json.put("View_paddingBottom", a.getDimensionPixelSize(attr, -1)); + else if (attr == View_scrollX) + json.put("View_paddingBottom", a.getDimensionPixelOffset(attr, 0)); + else if (attr == View_scrollY) + json.put("View_scrollY", a.getDimensionPixelOffset(attr, 0)); + else if (attr == View_id) + json.put("View_id", a.getResourceId(attr, -1)); + else if (attr == View_tag) + json.put("View_tag", a.getText(attr)); + else if (attr == View_fitsSystemWindows) + json.put("View_fitsSystemWindows", a.getBoolean(attr, false)); + else if (attr == View_focusable) + json.put("View_focusable", a.getBoolean(attr, false)); + else if (attr == View_focusableInTouchMode) + json.put("View_focusableInTouchMode", a.getBoolean(attr, false)); + else if (attr == View_clickable) + json.put("View_clickable", a.getBoolean(attr, false)); + else if (attr == View_longClickable) + json.put("View_longClickable", a.getBoolean(attr, false)); + else if (attr == View_saveEnabled) + json.put("View_saveEnabled", a.getBoolean(attr, true)); + else if (attr == View_duplicateParentState) + json.put("View_duplicateParentState", a.getBoolean(attr, false)); + else if (attr == View_visibility) + json.put("View_visibility", a.getInt(attr, 0)); + else if (attr == View_drawingCacheQuality) + json.put("View_drawingCacheQuality", a.getInt(attr, 0)); + else if (attr == View_drawingCacheQuality) + json.put("View_contentDescription", a.getString(attr)); + else if (attr == View_soundEffectsEnabled) + json.put("View_soundEffectsEnabled", a.getBoolean(attr, true)); + else if (attr == View_hapticFeedbackEnabled) + json.put("View_hapticFeedbackEnabled", a.getBoolean(attr, true)); + else if (attr == View_scrollbars) + json.put("View_scrollbars", a.getInt(attr, 0)); + else if (attr == View_fadingEdge) + json.put("View_fadingEdge", a.getInt(attr, 0)); + else if (attr == View_scrollbarStyle) + json.put("View_scrollbarStyle", a.getInt(attr, 0)); + else if (attr == View_isScrollContainer) + json.put("View_isScrollContainer", a.getBoolean(attr, false)); + else if (attr == View_keepScreenOn) + json.put("View_keepScreenOn", a.getBoolean(attr, false)); + else if (attr == View_filterTouchesWhenObscured) + json.put("View_filterTouchesWhenObscured", a.getBoolean(attr, false)); + else if (attr == View_nextFocusLeft) + json.put("View_nextFocusLeft", a.getResourceId(attr, -1)); + else if (attr == View_nextFocusRight) + json.put("View_nextFocusRight", a.getResourceId(attr, -1)); + else if (attr == View_nextFocusUp) + json.put("View_nextFocusUp", a.getResourceId(attr, -1)); + else if (attr == View_nextFocusDown) + json.put("View_nextFocusDown", a.getResourceId(attr, -1)); + else if (attr == View_minWidth) + json.put("View_minWidth", a.getDimensionPixelSize(attr, 0)); + else if (attr == View_minHeight) + json.put("View_minHeight", a.getDimensionPixelSize(attr, 0)); + else if (attr == View_onClick) + json.put("View_onClick", a.getString(attr)); + else if (attr == View_overScrollMode) + json.put("View_overScrollMode", a.getInt(attr, 1)); + } + a.recycle(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public JSONObject extractTextAppearanceInformations(String styleName, String qtClass, AttributeSet attribSet, int textAppearance ) + { + JSONObject json = new JSONObject(); + try + { + int textColorHighlight = 0; // + ColorStateList textColor = null; // + ColorStateList textColorHint = null; // + ColorStateList textColorLink = null; // + int textSize = 15; // + int typefaceIndex = -1; // + int styleIndex = -1; + boolean allCaps = false; + + Class<?> attrClass= Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField(styleName).getInt(null); + + extractViewInformations(styleName, styleId, json, qtClass, attribSet); + + int[] textViewAttrs=(int[]) styleableClass.getDeclaredField("TextView").get(null); + TypedArray a =m_theme.obtainStyledAttributes(null, textViewAttrs, styleId, 0); + + TypedArray appearance = null; + if (-1==textAppearance) + textAppearance = a.getResourceId(styleableClass.getDeclaredField("TextView_textAppearance").getInt(null), -1); + + if (textAppearance != -1) + appearance = m_theme.obtainStyledAttributes(textAppearance, (int[]) styleableClass.getDeclaredField("TextAppearance").get(null)); + + if (appearance != null) + { + int n = appearance.getIndexCount(); + for (int i = 0; i < n; i++) + { + int attr = appearance.getIndex(i); + if (attr == TextAppearance_textColorHighlight) + textColorHighlight = appearance.getColor(attr, textColorHighlight); + else if (attr == TextAppearance_textColor) + textColor = appearance.getColorStateList(attr); + else if (attr == TextAppearance_textColorHint) + textColorHint = appearance.getColorStateList(attr); + else if (attr == TextAppearance_textColorLink) + textColorLink = appearance.getColorStateList(attr); + else if (attr == TextAppearance_textSize) + textSize = appearance.getDimensionPixelSize(attr, textSize); + else if (attr == TextAppearance_typeface) + typefaceIndex = appearance.getInt(attr, -1); + else if (attr == TextAppearance_textStyle) + styleIndex = appearance.getInt(attr, -1); + else if (attr == TextAppearance_textAllCaps) + allCaps = appearance.getBoolean(attr, false); + } + appearance.recycle(); + } + + int n = a.getIndexCount(); + + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + if (attr == TextView_editable) + json.put("TextView_editable", a.getBoolean(attr, false)); + else if (attr == TextView_inputMethod) + json.put("TextView_inputMethod", a.getText(attr)); + else if (attr == TextView_numeric) + json.put("TextView_numeric", a.getInt(attr, 0)); + else if (attr == TextView_digits) + json.put("TextView_digits", a.getText(attr)); + else if (attr == TextView_phoneNumber) + json.put("TextView_phoneNumber", a.getBoolean(attr, false)); + else if (attr == TextView_autoText) + json.put("TextView_autoText", a.getBoolean(attr, false)); + else if (attr == TextView_capitalize) + json.put("TextView_capitalize", a.getInt(attr, -1)); + else if (attr == TextView_bufferType) + json.put("TextView_bufferType", a.getInt(attr, 0)); + else if (attr == TextView_selectAllOnFocus) + json.put("TextView_selectAllOnFocus", a.getBoolean(attr, false)); + else if (attr == TextView_autoLink) + json.put("TextView_autoLink", a.getInt(attr, 0)); + else if (attr == TextView_linksClickable) + json.put("TextView_linksClickable", a.getBoolean(attr, true)); + else if (attr == TextView_linksClickable) + json.put("TextView_linksClickable", a.getBoolean(attr, true)); + else if (attr == TextView_drawableLeft) + json.put("TextView_drawableLeft", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableLeft")); + else if (attr == TextView_drawableTop) + json.put("TextView_drawableTop", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableTop")); + else if (attr == TextView_drawableRight) + json.put("TextView_drawableRight", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableRight")); + else if (attr == TextView_drawableBottom) + json.put("TextView_drawableBottom", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableBottom")); + else if (attr == TextView_drawableStart) + json.put("TextView_drawableStart", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableStart")); + else if (attr == TextView_drawableEnd) + json.put("TextView_drawableEnd", getDrawable(a.getDrawable(attr), styleName + "_TextView_drawableEnd")); + else if (attr == TextView_drawablePadding) + json.put("TextView_drawablePadding", a.getDimensionPixelSize(attr, 0)); + else if (attr == TextView_textCursorDrawable) + json.put("TextView_textCursorDrawable", getDrawable(m_context.getResources().getDrawable(a.getResourceId(attr, 0)), styleName + "_TextView_textCursorDrawable")); + else if (attr == TextView_maxLines) + json.put("TextView_maxLines", a.getInt(attr, -1)); + else if (attr == TextView_maxHeight) + json.put("TextView_maxHeight", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_lines) + json.put("TextView_lines", a.getInt(attr, -1)); + else if (attr == TextView_height) + json.put("TextView_height", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_minLines) + json.put("TextView_minLines", a.getInt(attr, -1)); + else if (attr == TextView_minHeight) + json.put("TextView_minHeight", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_maxEms) + json.put("TextView_maxEms", a.getInt(attr, -1)); + else if (attr == TextView_maxWidth) + json.put("TextView_maxWidth", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_ems) + json.put("TextView_ems", a.getInt(attr, -1)); + else if (attr == TextView_width) + json.put("TextView_width", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_minEms) + json.put("TextView_minEms", a.getInt(attr, -1)); + else if (attr == TextView_minWidth) + json.put("TextView_minWidth", a.getDimensionPixelSize(attr, -1)); + else if (attr == TextView_gravity) + json.put("TextView_gravity", a.getInt(attr, -1)); + else if (attr == TextView_hint) + json.put("TextView_hint", a.getText(attr)); + else if (attr == TextView_text) + json.put("TextView_text", a.getText(attr)); + else if (attr == TextView_scrollHorizontally) + json.put("TextView_scrollHorizontally", a.getBoolean(attr, false)); + else if (attr == TextView_singleLine) + json.put("TextView_singleLine", a.getBoolean(attr, false)); + else if (attr == TextView_ellipsize) + json.put("TextView_ellipsize", a.getInt(attr, -1)); + else if (attr == TextView_marqueeRepeatLimit) + json.put("TextView_marqueeRepeatLimit", a.getInt(attr, 3)); + else if (attr == TextView_includeFontPadding) + json.put("TextView_includeFontPadding", a.getBoolean(attr, true)); + else if (attr == TextView_cursorVisible) + json.put("TextView_cursorVisible", a.getBoolean(attr, true)); + else if (attr == TextView_maxLength) + json.put("TextView_maxLength", a.getInt(attr, -1)); + else if (attr == TextView_textScaleX) + json.put("TextView_textScaleX", a.getFloat(attr, 1.0f)); + else if (attr == TextView_freezesText) + json.put("TextView_freezesText", a.getBoolean(attr, false)); + else if (attr == TextView_shadowColor) + json.put("TextView_shadowColor", a.getInt(attr, 0)); + else if (attr == TextView_shadowDx) + json.put("TextView_shadowDx", a.getFloat(attr, 0)); + else if (attr == TextView_shadowDy) + json.put("TextView_shadowDy", a.getFloat(attr, 0)); + else if (attr == TextView_shadowRadius) + json.put("TextView_shadowRadius", a.getFloat(attr, 0)); + else if (attr == TextView_enabled) + json.put("TextView_enabled", a.getBoolean(attr,true)); + else if (attr == TextView_textColorHighlight) + textColorHighlight = a.getColor(attr, textColorHighlight); + else if (attr == TextView_textColor) + textColor = a.getColorStateList(attr); + else if (attr == TextView_textColorHint) + textColorHint = a.getColorStateList(attr); + else if (attr == TextView_textColorLink) + textColorLink = a.getColorStateList(attr); + else if (attr == TextView_textSize) + textSize = a.getDimensionPixelSize(attr, textSize); + else if (attr == TextView_typeface) + typefaceIndex = a.getInt(attr, typefaceIndex); + else if (attr == TextView_textStyle) + styleIndex = a.getInt(attr, styleIndex); + else if (attr == TextView_password) + json.put("TextView_password", a.getBoolean(attr,false)); + else if (attr == TextView_lineSpacingExtra) + json.put("TextView_lineSpacingExtra", a.getDimensionPixelSize(attr, 0)); + else if (attr == TextView_lineSpacingMultiplier) + json.put("TextView_lineSpacingMultiplier", a.getFloat(attr, 1.0f)); + else if (attr == TextView_inputType) + json.put("TextView_inputType", a.getInt(attr, EditorInfo.TYPE_NULL)); + else if (attr == TextView_imeOptions) + json.put("TextView_imeOptions", a.getInt(attr, EditorInfo.IME_NULL)); + else if (attr == TextView_imeActionLabel) + json.put("TextView_imeActionLabel", a.getText(attr)); + else if (attr == TextView_imeActionId) + json.put("TextView_imeActionId", a.getInt(attr,0)); + else if (attr == TextView_privateImeOptions) + json.put("TextView_privateImeOptions", a.getString(attr)); + else if (attr == TextView_textSelectHandleLeft && styleName.equals("textViewStyle")) + json.put("TextView_textSelectHandleLeft", getDrawable(m_context.getResources().getDrawable(a.getResourceId(attr, 0)), styleName + "_TextView_textSelectHandleLeft")); + else if (attr == TextView_textSelectHandleRight && styleName.equals("textViewStyle")) + json.put("TextView_textSelectHandleRight", getDrawable(m_context.getResources().getDrawable(a.getResourceId(attr, 0)), styleName + "_TextView_textSelectHandleRight")); + else if (attr == TextView_textSelectHandle && styleName.equals("textViewStyle")) + json.put("TextView_textSelectHandle", getDrawable(m_context.getResources().getDrawable(a.getResourceId(attr, 0)), styleName + "_TextView_textSelectHandle")); + else if (attr == TextView_textIsSelectable) + json.put("TextView_textIsSelectable", a.getBoolean(attr, false)); + else if (attr == TextView_textAllCaps) + allCaps = a.getBoolean(attr, false); + } + a.recycle(); + + json.put("TextAppearance_textColorHighlight",textColorHighlight); + json.put("TextAppearance_textColor", getColorStateList(textColor)); + json.put("TextAppearance_textColorHint", getColorStateList(textColorHint)); + json.put("TextAppearance_textColorLink", getColorStateList(textColorLink)); + json.put("TextAppearance_textSize",textSize); + json.put("TextAppearance_typeface",typefaceIndex); + json.put("TextAppearance_textStyle",styleIndex); + json.put("TextAppearance_textAllCaps",allCaps); + } + catch(Exception e) + { + e.printStackTrace(); + } + return json; + } + + final String[] sScaleTypeArray = { + "MATRIX", + "FIT_XY", + "FIT_START", + "FIT_CENTER", + "FIT_END", + "CENTER", + "CENTER_CROP", + "CENTER_INSIDE" + }; + + public JSONObject extractImageViewInformations(String styleName, String qtClassName ) + { + JSONObject json = new JSONObject(); + try + { + Class<?> attrClass= Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField(styleName).getInt(null); + + extractViewInformations(styleName, styleId, json, qtClassName, null); + + int[] imageViewAttrs=(int[]) styleableClass.getDeclaredField("ImageView").get(null); + TypedArray a =m_theme.obtainStyledAttributes(null, imageViewAttrs, styleId, 0); + Drawable d = a.getDrawable(ImageView_src); + if (d != null) + json.put("ImageView_src", getDrawable(d, styleName + "_ImageView_src")); + + json.put("ImageView_baselineAlignBottom", a.getBoolean(ImageView_baselineAlignBottom, false)); + json.put("ImageView_adjustViewBounds", a.getBoolean(ImageView_adjustViewBounds, false)); + json.put("ImageView_maxWidth", a.getDimensionPixelSize(ImageView_maxWidth, Integer.MAX_VALUE)); + json.put("ImageView_maxHeight", a.getDimensionPixelSize(ImageView_maxHeight, Integer.MAX_VALUE)); + int index = a.getInt(ImageView_scaleType, -1); + if (index >= 0) + json.put("ImageView_scaleType", sScaleTypeArray[index]); + + int tint = a.getInt(ImageView_tint, 0); + if (tint != 0) + json.put("ImageView_tint", tint); + + + json.put("ImageView_cropToPadding",a.getBoolean(ImageView_cropToPadding, false)); + a.recycle(); + } + catch(Exception e) + { + e.printStackTrace(); + } + return json; + + } + + void extractCompoundButton(JSONObject parentObject, String styleName, String qtClass) + { + JSONObject json = extractTextAppearanceInformations(styleName, qtClass, null, -1); + Class<?> attrClass; + try { + attrClass = Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField(styleName).getInt(null); + int[] compoundButtonAttrs=(int[]) styleableClass.getDeclaredField("CompoundButton").get(null); + + TypedArray a = m_theme.obtainStyledAttributes( + null, compoundButtonAttrs, styleId, 0); + + Drawable d = a.getDrawable(getField(styleableClass,"CompoundButton_button")); + if (d != null) + json.put("CompoundButton_button", getDrawable(d, styleName + "_CompoundButton_button")); + + a.recycle(); + parentObject.put(styleName, json); + } catch (Exception e) { + e.printStackTrace(); + } + } + + void extractProgressBarInfo(JSONObject json, String styleName) + { + Class<?> attrClass; + try { + attrClass = Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField(styleName).getInt(null); + int[] progressBarAttrs=(int[]) styleableClass.getDeclaredField("ProgressBar").get(null); + + TypedArray a = m_theme.obtainStyledAttributes(null, progressBarAttrs, styleId, 0); + int mMinWidth = 24; + int mMaxWidth = 48; + int mMinHeight = 24; + int mMaxHeight = 48; + mMinWidth = a.getDimensionPixelSize(getField(styleableClass, "ProgressBar_minWidth"), mMinWidth); + mMaxWidth = a.getDimensionPixelSize(getField(styleableClass, "ProgressBar_maxWidth"), mMaxWidth); + mMinHeight = a.getDimensionPixelSize(getField(styleableClass, "ProgressBar_minHeight"), mMinHeight); + mMaxHeight = a.getDimensionPixelSize(getField(styleableClass, "ProgressBar_maxHeight"), mMaxHeight); + + json.put("ProgressBar_indeterminateDuration", a.getInt(getField(styleableClass, "ProgressBar_indeterminateDuration"), 4000)); + json.put("ProgressBar_minWidth", mMinWidth); + json.put("ProgressBar_maxWidth", mMaxWidth); + json.put("ProgressBar_minHeight", mMinHeight); + json.put("ProgressBar_maxHeight", mMaxHeight); + json.put("ProgressBar_progress_id", android.R.id.progress); + json.put("ProgressBar_secondaryProgress_id", android.R.id.secondaryProgress); + + Drawable d = a.getDrawable(getField(styleableClass,"ProgressBar_progressDrawable")); + if (d != null) + json.put("ProgressBar_progressDrawable", getDrawable(d, styleName + "_ProgressBar_progressDrawable")); + + d = a.getDrawable(getField(styleableClass,"ProgressBar_indeterminateDrawable")); + if (d != null) + json.put("ProgressBar_indeterminateDrawable", getDrawable(d, styleName + "_ProgressBar_indeterminateDrawable")); + + a.recycle(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + void extractProgressBar(JSONObject parentObject, String styleName, String qtClass) + { + JSONObject json = extractTextAppearanceInformations(styleName, qtClass, null, -1); + try { + extractProgressBarInfo(json, styleName); + parentObject.put(styleName, json); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + void extractAbsSeekBar(JSONObject parentObject, String styleName, String qtClass) + { + JSONObject json = extractTextAppearanceInformations(styleName, qtClass, null, -1); + extractProgressBarInfo(json, styleName); + Class<?> attrClass; + try { + attrClass = Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField(styleName).getInt(null); + int[] compoundButtonAttrs=(int[]) styleableClass.getDeclaredField("SeekBar").get(null); + + TypedArray a = m_theme.obtainStyledAttributes( + null, compoundButtonAttrs, styleId, 0); + + Drawable d = a.getDrawable(getField(styleableClass,"SeekBar_thumb")); + if (d != null) + json.put("SeekBar_thumb", getDrawable(d, styleName + "_SeekBar_thumb")); + + a.recycle(); + parentObject.put(styleName, json); + } catch (Exception e) { + e.printStackTrace(); + } + } + + JSONObject extractCheckedTextView(AttributeSet attribSet, String itemName) + { + JSONObject json = extractTextAppearanceInformations("textViewStyle", itemName, attribSet, -1); + try { + Class<?> attrClass= Class.forName("android.R$attr"); + int styleId = attrClass.getDeclaredField("textViewStyle").getInt(null); + int[] compoundButtonAttrs=(int[]) styleableClass.getDeclaredField("CheckedTextView").get(null); + + TypedArray a = m_theme.obtainStyledAttributes(attribSet, compoundButtonAttrs, styleId, 0); + + Drawable d = a.getDrawable(getField(styleableClass,"CheckedTextView_checkMark")); + if (d != null) + json.put("CheckedTextView_checkMark", getDrawable(d, itemName+"_CheckedTextView_checkMark")); + + a.recycle(); + } catch (Exception e) { + e.printStackTrace(); + } + return json; + } + + private JSONObject extractItemStyle(int resourceId, String itemName, int textAppearance) { + try + { + XmlResourceParser parser = m_context.getResources().getLayout(resourceId); + int type; + while ((type = parser.next()) != XmlPullParser.START_TAG && + type != XmlPullParser.END_DOCUMENT) { + // Empty + } + + if (type != XmlPullParser.START_TAG) { + return null; + } + + AttributeSet attributes = Xml.asAttributeSet(parser); + String name = parser.getName(); + if (name.equals("TextView")) + return extractTextAppearanceInformations("textViewStyle", itemName, attributes, textAppearance); + if (name.equals("CheckedTextView")) + return extractCheckedTextView(attributes, itemName); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private void extractItemsStyle(JSONObject json) { + try + { + json.put("simple_list_item",extractItemStyle(android.R.layout.simple_list_item_1, "simple_list_item", android.R.style.TextAppearance_Large)); + json.put("simple_list_item_checked",extractItemStyle(android.R.layout.simple_list_item_checked, "simple_list_item_checked", android.R.style.TextAppearance_Large)); + json.put("simple_list_item_multiple_choice",extractItemStyle(android.R.layout.simple_list_item_multiple_choice, "simple_list_item_multiple_choice", android.R.style.TextAppearance_Large)); + json.put("simple_list_item_single_choice",extractItemStyle(android.R.layout.simple_list_item_single_choice, "simple_list_item_single_choice", android.R.style.TextAppearance_Large)); + json.put("simple_spinner_item",extractItemStyle(android.R.layout.simple_spinner_item, "simple_spinner_item", -1)); + json.put("simple_spinner_dropdown_item",extractItemStyle(android.R.layout.simple_spinner_dropdown_item, "simple_spinner_dropdown_item",android.R.style.TextAppearance_Large)); + json.put("simple_dropdown_item_1line",extractItemStyle(android.R.layout.simple_dropdown_item_1line, "simple_dropdown_item_1line",android.R.style.TextAppearance_Large)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public ExtractStyle(Context context, String extractPath) + { + m_extractPath = extractPath; + new File(m_extractPath).mkdirs(); + MinistroActivity.nativeChmode(m_extractPath, 0755); + m_context = context; + m_theme = context.getTheme(); + JSONObject json = new JSONObject(); + try { + json.put("buttonStyle", extractTextAppearanceInformations("buttonStyle", "QPushButton", null, -1)); + json.put("spinnerStyle", extractTextAppearanceInformations("spinnerStyle", "QComboBox", null, -1)); + extractProgressBar(json, "progressBarStyleHorizontal", "QProgressBar"); + extractAbsSeekBar(json, "seekBarStyle", "QSlider"); + extractCompoundButton(json, "checkboxStyle", "QCheckBox"); + json.put("editTextStyle", extractTextAppearanceInformations("editTextStyle", "QLineEdit", null, -1)); + extractCompoundButton(json, "radioButtonStyle", "QRadioButton"); + json.put("textViewStyle", extractTextAppearanceInformations("textViewStyle", "QWidget", null, -1)); + extractItemsStyle(json); + //extractCompoundButton(json, "buttonStyleToggle", null); + //extractCompoundButton(json, "switchStyle", null); + //json.put("imageButtonStyle", extractImageViewInformations("imageButtonStyle", null)); + OutputStreamWriter jsonWriter; + jsonWriter = new OutputStreamWriter(new FileOutputStream(m_extractPath+"style.json")); + jsonWriter.write(json.toString(1)); + jsonWriter.close(); + MinistroActivity.nativeChmode(m_extractPath+"style.json", 0644); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Ministro/src/org/kde/necessitas/ministro/IMinistro.aidl b/Ministro/src/org/kde/necessitas/ministro/IMinistro.aidl new file mode 100644 index 0000000..c1101da --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/IMinistro.aidl @@ -0,0 +1,47 @@ +/* + Copyright (c) 2011, BogDan Vatra <bog_dan_ro@yahoo.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +package org.kde.necessitas.ministro; + +import org.kde.necessitas.ministro.IMinistroCallback; + +interface IMinistro +{ +/** +* Check/download required libs to run the application +* +* param callback - interface used by Minsitro service to notify the client when the loader is ready +* param parameters +* parameters fields: +* * Key Name Key type Explanations +* "required.modules" StringArray Required modules by your application +* "application.title" String Application name, used to show more informations to user +* "qt.provider" String Qt libs provider, currently only "necessitas" is supported. +* "minimum.ministro.api" Integer Minimum Ministro API level, used to check if Ministro service compatible with your application. Current API Level is 1 ! +* "minimum.qt.version" Integer Minimim Qt version (e.g. 0x040800, which means Qt 4.8.0, check http://doc.trolltech.com/4.8/qtglobal.html#QT_VERSION)! +*/ + void requestLoader(in IMinistroCallback callback, in Bundle parameters); +} diff --git a/Ministro/src/org/kde/necessitas/ministro/IMinistroCallback.aidl b/Ministro/src/org/kde/necessitas/ministro/IMinistroCallback.aidl new file mode 100644 index 0000000..dfd3ea9 --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/IMinistroCallback.aidl @@ -0,0 +1,52 @@ +/* + Copyright (c) 2011, BogDan Vatra <bog_dan_ro@yahoo.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.kde.necessitas.ministro; + +oneway interface IMinistroCallback { +/** +* This method is called by the Ministro service back into the application which +* implements this interface. +* +* param in - loaderParams +* loaderParams fields: +* * Key Name Key type Explanations +* * "error.code" Integer See below +* * "error.message" String Missing if no error, otherwise will contain the error message translated into phone language where available. +* * "dex.path" String The list of jar/apk files containing classes and resources, needed to be passed to application DexClassLoader +* * "lib.path" String The list of directories containing native libraries; may be missing, needed to be passed to application DexClassLoader +* * "loader.class.name" String Loader class name. +* +* "error.code" field possible errors: +* - 0 no error. +* - 1 incompatible Ministro version. Ministro needs to be upgraded. +* - 2 not all modules could be satisfy. +* - 3 invalid parameters +* +* This parameter will contain additional fields which are used by the loader to start your application, so it must be passed to loader. +*/ + + void loaderReady(in Bundle loaderParams); +} diff --git a/Ministro/src/org/kde/necessitas/ministro/Library.java b/Ministro/src/org/kde/necessitas/ministro/Library.java new file mode 100644 index 0000000..7e1a9fe --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/Library.java @@ -0,0 +1,259 @@ +/* + 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.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import android.os.Bundle; + + +class Library +{ + public String name = null; + public String filePath = null; + public String[] depends = null; + public String[] replaces = null; + public NeedsStruct[] needs = null; + public int level=0; + public long size = 0; + public String sha1 = null; + public String url; + + public static String[] getLibNames(Element libNode) + { + if (libNode == null) + return null; + NodeList list=libNode.getElementsByTagName("lib"); + ArrayList<String> libs = new ArrayList<String>(); + for (int i=0;i<list.getLength();i++) + { + if (list.item(i).getNodeType() != Node.ELEMENT_NODE) + continue; + Element lib=(Element)list.item(i); + if (lib!=null) + libs.add(lib.getAttribute("name")); + } + String[] strings = new String[libs.size()]; + return libs.toArray(strings); + } + + public static NeedsStruct[] getNeeds(Element libNode) + { + if (libNode == null) + return null; + NodeList list=libNode.getElementsByTagName("item"); + ArrayList<NeedsStruct> needs = new ArrayList<NeedsStruct>(); + + for (int i=0;i<list.getLength();i++) + { + if (list.item(i).getNodeType() != Node.ELEMENT_NODE) + continue; + Element lib=(Element)list.item(i); + if (lib!=null) + { + NeedsStruct need=new NeedsStruct(); + need.name=lib.getAttribute("name"); + need.filePath=lib.getAttribute("file"); + need.url=lib.getAttribute("url"); + need.sha1=lib.getAttribute("sha1"); + need.size=Long.valueOf(lib.getAttribute("size")); + if ( lib.hasAttribute("type") ) + need.type=lib.getAttribute("type"); + needs.add(need); + } + } + NeedsStruct[] _needs = new NeedsStruct[needs.size()]; + return needs.toArray(_needs); + } + + public static Library getLibrary(Element libNode, boolean includeNeed) + { + Library lib= new Library(); + lib.name=libNode.getAttribute("name"); + lib.sha1=libNode.getAttribute("sha1").toUpperCase(); + lib.filePath=libNode.getAttribute("file"); + lib.url=libNode.getAttribute("url"); + try + { + lib.level=Integer.parseInt(libNode.getAttribute("level")); + } catch (Exception e) { + e.printStackTrace(); + } + + try + { + lib.size=Long.parseLong(libNode.getAttribute("size")); + } catch (Exception e) { + e.printStackTrace(); + } + NodeList list=libNode.getElementsByTagName("depends"); + for (int i=0;i<list.getLength();i++) + { + if (list.item(i).getNodeType() != Node.ELEMENT_NODE) + continue; + lib.depends=getLibNames((Element) list.item(i)); + break; + } + list=libNode.getElementsByTagName("replaces"); + for (int i=0;i<list.getLength();i++) + { + if (list.item(i).getNodeType() != Node.ELEMENT_NODE) + continue; + lib.replaces=getLibNames((Element) list.item(i)); + break; + } + + if (!includeNeed) // don't waste time. + return lib; + + list=libNode.getElementsByTagName("needs"); + for (int i=0;i<list.getLength();i++) + { + if (list.item(i).getNodeType() != Node.ELEMENT_NODE) + continue; + lib.needs=getNeeds((Element) list.item(i)); + break; + } + return lib; + } + + public static String convertToHex(byte[] data) + { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < data.length; i++) + { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do + { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append((char) ('0' + halfbyte)); + else + buf.append((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } while(two_halfs++ < 1); + } + return buf.toString(); + } + + public static boolean checkCRC(String fileName, String sha1) throws IOException + { + try + { + byte[] tmp = new byte[2048]; + MessageDigest digester = MessageDigest.getInstance("SHA-1"); + int downloaded; + FileInputStream inFile = new FileInputStream(new File(fileName)); + while ((downloaded = inFile.read(tmp)) != -1) + { + digester.update(tmp, 0, downloaded); + } + inFile.close(); + return sha1.equalsIgnoreCase(convertToHex(digester.digest())); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return false; + } + + public static String mkdirParents(String rootPath, String filePath, int skip) + { + String[] paths=filePath.split("/"); + String path = ""; + for (int pit=0;pit<paths.length-skip;pit++) + { + if (paths[pit].length()==0) + continue; + path+="/"+paths[pit]; + File dir=new File(rootPath+path); + dir.mkdir(); + MinistroActivity.nativeChmode(rootPath+path, 0755); + } + return rootPath+path; + } + + public static void removeAllFiles(String path) + { + File f = new File(path); + if (!f.exists()) + return; + String files[]=f.list(); + if (!path.endsWith("/")) + path+="/"; + for (int i=0;i<files.length;i++) + { + try + { + new File(path+files[i]).delete(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + + public static String join(Collection<String> s, String delimiter) + { + if (s == null || s.isEmpty()) return ""; + Iterator<String> iter = s.iterator(); + StringBuilder builder = new StringBuilder(iter.next()); + while( iter.hasNext() ) + { + builder.append(delimiter).append(iter.next()); + } + return builder.toString(); + } + + public static void mergeBundleParameters(Bundle out, String outKey, Bundle in, String inKey) + { + if (!in.containsKey(inKey)) + return; + + String value = null; + if (out.containsKey(outKey)) + value=out.getString(outKey); + + if (value!=null && value.length()>0 && value.charAt(value.length()-1)!='\t') + value=value+"\t"; + + value=value+in.getString(inKey); + out.putString(outKey, value); + } +}; + +class NeedsStruct +{ + public String name = null; + public String filePath = null; + public String sha1 = null; + public String url = null; + public String type = null; + public long size = 0; +};
\ No newline at end of file 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..c9d6bd7 --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/MinistroActivity.java @@ -0,0 +1,860 @@ +/* + 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.BufferedReader; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +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 java.util.concurrent.Semaphore; + +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.Build; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.os.StatFs; +import android.provider.Settings; +import android.util.Log; + +public class MinistroActivity extends Activity +{ + private static final int CONNECTION_TIMEOUT = 20000; // 20 seconds for connection timeout + private static final int READ_TIMEOUT = 10000; // 10 seconds for read timeout + + public native static int nativeChmode(String filepath, int mode); + private static final String DOMAIN_NAME="https://files.kde.org/necessitas/ministro/android/necessitas/"; + + private String[] m_modules; + private int m_id=-1; + private String m_qtLibsRootPath; + private WakeLock m_wakeLock; + + 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.settings_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() { + public void onCancel(DialogInterface dialog) + { + finishMe(); + } + }); + getApplication().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) + { + if (isOnline(MinistroActivity.this)) + { + try + { + getApplication().unregisterReceiver(this); + } + catch(Exception e) + { + e.printStackTrace(); + } + runOnUiThread(new Runnable() { + public void run() + { + m_dialog.dismiss(); + new CheckLibraries().execute(update); + } + }); + } + } + }, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + try { + startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)); + } catch(Exception e) { + e.printStackTrace(); + try { + startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)); + } catch(Exception e1) { + + e1.printStackTrace(); + } + } + 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() { + public void onCancel(DialogInterface dialog) + { + finishMe(); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + } + private AlertDialog m_distSpaceDialog=null; + private final int freeSpaceCode=0xf3ee500; + private Semaphore m_diskSpaceSemaphore = new Semaphore(0); + + private boolean checkFreeSpace(final long size) throws InterruptedException + { + final StatFs stat = new StatFs(m_qtLibsRootPath); + if (stat.getBlockSize() * stat.getAvailableBlocks() < size) + { + runOnUiThread(new Runnable() { + public void run() { + + AlertDialog.Builder builder = new AlertDialog.Builder(MinistroActivity.this); + builder.setMessage(getResources().getString(R.string.ministro_disk_space_msg, + (size-(stat.getBlockSize() * stat.getAvailableBlocks()))/1024+"Kb")); + builder.setCancelable(true); + builder.setNeutralButton(getResources().getString(R.string.settings_msg), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + try { + startActivityForResult(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS), freeSpaceCode); + } catch(Exception e) { + e.printStackTrace(); + try { + startActivityForResult(new Intent(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS), freeSpaceCode); + } catch(Exception e1) { + + e1.printStackTrace(); + } + } + } + }); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) + { + dialog.dismiss(); + m_diskSpaceSemaphore.release(); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) + { + dialog.dismiss(); + m_diskSpaceSemaphore.release(); + } + }); + m_distSpaceDialog = builder.create(); + m_distSpaceDialog.show(); + } + }); + m_diskSpaceSemaphore.acquire(); + } + else + return true; + + return stat.getBlockSize() * stat.getAvailableBlocks()>size; + } + + protected void onActivityResult (int requestCode, int resultCode, Intent data) + { + if (requestCode == freeSpaceCode) + { + m_diskSpaceSemaphore.release(); + try + { + if (m_distSpaceDialog != null) + { + m_distSpaceDialog.dismiss(); + m_distSpaceDialog = null; + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + private ServiceConnection m_ministroConnection=new ServiceConnection() + { + public void onServiceConnected(ComponentName name, IBinder service) + { + if (getIntent().hasExtra("id")) + m_id=getIntent().getExtras().getInt("id"); + + if (getIntent().hasExtra("modules")) + { + 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(); + try + { + alert.show(); + } + catch (Exception e) + { + e.printStackTrace(); + checkNetworkAndDownload(false); + } + } + else + checkNetworkAndDownload(true); + } + + public void onServiceDisconnected(ComponentName name) + { + m_ministroConnection = null; + } + }; + + void finishMe() + { + if (-1 != m_id && null != MinistroService.instance()) + MinistroService.instance().retrievalFinished(m_id); + 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.os.Build.CPU_ABI+"/android-"+android.os.Build.VERSION.SDK_INT+"/versions.xml"); + } + + private static URL getLibsXmlUrl(Context c, String version) throws MalformedURLException + { + return new URL(DOMAIN_NAME+MinistroService.getRepository(c)+"/"+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; + } + + private static String deviceSupportedFeatures(String supportedFeatures) + { + if (null==supportedFeatures) + return ""; + String [] serverFeaturesList=supportedFeatures.trim().split(" "); + String [] deviceFeaturesList=null; + try { + FileInputStream fstream = new FileInputStream("/proc/cpuinfo"); + DataInputStream in = new DataInputStream(fstream); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + String strLine; + while ((strLine = br.readLine()) != null) + { + if (strLine.startsWith("Features")) + { + deviceFeaturesList=strLine.substring(strLine.indexOf(":")+1).trim().split(" "); + break; + } + } + br.close(); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + + String features=""; + for(String sfeature: serverFeaturesList) + for (String dfeature: deviceFeaturesList) + if (sfeature.equals(dfeature)) + features+="_"+dfeature; + + return features; + } + + + 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(); + connection.setConnectTimeout(CONNECTION_TIMEOUT); + connection.setReadTimeout(READ_TIMEOUT); + 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; + String supportedFeatures=null; + if (root.hasAttribute("features")) + supportedFeatures=root.getAttribute("features"); + connection = getLibsXmlUrl(c, version+deviceSupportedFeatures(supportedFeatures)).openConnection(); + 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.setCanceledOnTouchOutside(false); + m_dialog.setOnCancelListener(new DialogInterface.OnCancelListener(){ + public void onCancel(DialogInterface dialog) + { + DownloadManager.this.cancel(false); + finishMe(); + } + }); + try + { + m_dialog.show(); + } + catch(Exception e) + { + e.printStackTrace(); + m_dialog = null; + } + super.onPreExecute(); + } + + private boolean DownloadItem(String url, String file, long size, String fileSha1) throws NoSuchAlgorithmException, MalformedURLException, IOException + { + for (int i=0;i<2;i++) + { + MessageDigest digester = MessageDigest.getInstance("SHA-1"); + URLConnection connection = new URL(url).openConnection(); + 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); + return true; + } + else + Log.e("Ministro", "sha1 mismatch, the file:"+file+" will be removed, expected sha1:"+fileSha1+" got sha1:"+sha1+" file was downloaded from "+url); + 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; + } + + void removeFile(String file) + { + File f = new File(m_qtLibsRootPath+file); + f.delete(); + } + + @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); + if (!checkFreeSpace(m_totalSize)) + return null; + 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)) + break; + + 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)) + { + for (int k=0;k<j;k++) // remove previous neede files + removeFile(params[i].needs[k].filePath); + removeFile(params[i].filePath); // remove the parent + break; + } + } + } + } 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) + { + try + { + if (m_dialog != null) + { + synchronized (m_status) + { + m_dialog.setMessage(m_status+values[0]+"%"); + m_dialog.setProgress(values[1]); + } + } + } + catch(Exception e) + { + e.printStackTrace(); + } + super.onProgressUpdate(values); + } + + @Override + protected void onPostExecute(Long result) + { + super.onPostExecute(result); + if (m_dialog != null) + { + m_dialog.dismiss(); + m_dialog = null; + } + MinistroService.instance().refreshLibraries(false); + finishMe(); + } + } + + private class CheckLibraries extends AsyncTask<Boolean, String, Double> + { + private ProgressDialog m_dialog = null; + private final ArrayList<Library> newLibs = new ArrayList<Library>(); + private String m_message; + @Override + protected void onPreExecute() + { + try + { + m_dialog = ProgressDialog.show(MinistroActivity.this, null, + getResources().getString(R.string.checking_libraries_msg), true, true); + } + catch (Exception e) + { + e.printStackTrace(); + m_dialog = null; + } + 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(); + + SharedPreferences preferences=getSharedPreferences("Ministro", MODE_PRIVATE); + // extract device look&feel + 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) || + !preferences.getString("MINISTRO_VERSION", "").equals(getPackageManager().getPackageInfo(getPackageName(), 0).versionName) || + !(new File(m_qtLibsRootPath+"style").exists())) + { + m_message = getResources().getString(R.string.extracting_look_n_feel_msg); + publishProgress(m_message); + new ExtractStyle(MinistroActivity.this, m_qtLibsRootPath+"style/"); + SharedPreferences.Editor editor= preferences.edit(); + editor.putString("MINISTRO_VERSION",getPackageManager().getPackageInfo(getPackageName(), 0).versionName); + editor.commit(); + } + + 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 + 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(m_message); + 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 = null; + if (Build.VERSION.SDK_INT>13) + { + ks = KeyStore.getInstance("AndroidCAStore"); + ks.load(null, null); + } + else + { + ks= KeyStore.getInstance(KeyStore.getDefaultType()); + String cacertsPath=System.getProperty("javax.net.ssl.trustStore"); + if (null == cacertsPath) + cacertsPath="/system/etc/security/cacerts.bks"; + FileInputStream instream = new FileInputStream(new File(cacertsPath)); + 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(String... messages) + { + try + { + if (null != m_dialog) + m_dialog.setMessage(messages[0]); + } + catch (Exception e) + { + e.printStackTrace(); + } + super.onProgressUpdate(messages); + } + + @Override + protected void onPostExecute(Double result) + { + try + { + if (null != m_dialog) + { + m_dialog.dismiss(); + m_dialog = null; + } + } + catch(Exception e) + { + e.printStackTrace(); + } + 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.IMinistro"), m_ministroConnection, Context.BIND_AUTO_CREATE); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + m_wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "Ministro"); + m_wakeLock.acquire(); + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + if (null != m_wakeLock) + { + m_wakeLock.release(); + m_wakeLock = null; + } + unbindService(m_ministroConnection); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) + { + //Avoid activity from being destroyed/created + super.onConfigurationChanged(newConfig); + } + + static + { + System.loadLibrary("ministro"); + } +} diff --git a/Ministro/src/org/kde/necessitas/ministro/MinistroConfigActivity.java b/Ministro/src/org/kde/necessitas/ministro/MinistroConfigActivity.java new file mode 100644 index 0000000..91b5d06 --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/MinistroConfigActivity.java @@ -0,0 +1,102 @@ +/* + 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 android.app.Activity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.Toast; + +public class MinistroConfigActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + setContentView(R.layout.repoconfig); + Spinner repositoriesSpinner = (Spinner) findViewById(R.id.repositories); + ArrayAdapter<CharSequence> repositories = ArrayAdapter.createFromResource( + this, R.array.repositories, android.R.layout.simple_spinner_item); + repositories.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + repositoriesSpinner.setAdapter(repositories); + repositoriesSpinner.setSelection(repositories.getPosition(MinistroService.getRepository(this))); + repositoriesSpinner.setOnItemSelectedListener(new OnItemSelectedListener(){ + public void onItemSelected(AdapterView<?> parent, View view, int pos, + long id) { + Toast.makeText(parent.getContext() + , getResources().getString(R.string.ministro_repository_msg + , parent.getItemAtPosition(pos).toString()), Toast.LENGTH_SHORT).show(); + MinistroService.setRepository(MinistroConfigActivity.this, parent.getItemAtPosition(pos).toString()); + } + public void onNothingSelected(AdapterView<?> arg0) { + } + }); + + Spinner checkFrequencySpinner = (Spinner) findViewById(R.id.check_frequency); + ArrayAdapter<CharSequence> checkFrequency = ArrayAdapter.createFromResource( + this, R.array.check_frequency, android.R.layout.simple_spinner_item); + checkFrequency.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + checkFrequencySpinner.setAdapter(checkFrequency); + checkFrequencySpinner.setSelection(checkFrequency.getPosition(MinistroService.getCheckFrequency(this).toString())); + checkFrequencySpinner.setOnItemSelectedListener(new OnItemSelectedListener(){ + public void onItemSelected(AdapterView<?> parent, View view, int pos, + long id) { + MinistroService.setCheckFrequency(MinistroConfigActivity.this, Long.parseLong(parent.getItemAtPosition(pos).toString())); + } + + public void onNothingSelected(AdapterView<?> arg0) { + } + }); + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + int icon = R.drawable.icon; + CharSequence tickerText = getResources().getString(R.string.ministro_repository_changed_msg); // ticker-text + long when = System.currentTimeMillis(); // notification time + Context context = getApplicationContext(); // application Context + CharSequence contentTitle = getResources().getString(R.string.ministro_update_msg); // expanded message title + CharSequence contentText = getResources().getString(R.string.ministro_repository_changed_tap_msg); // expanded message text + + Intent notificationIntent = new Intent(this, MinistroActivity.class); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); + + // the next two lines initialize the Notification, using the configurations above + Notification notification = new Notification(icon, tickerText, when); + notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); + notification.defaults |= Notification.DEFAULT_SOUND; + notification.defaults |= Notification.DEFAULT_VIBRATE; + notification.defaults |= Notification.DEFAULT_LIGHTS; + try{ + nm.notify(1, notification); + }catch(Exception e) + { + e.printStackTrace(); + } + super.onDestroy(); + } +} diff --git a/Ministro/src/org/kde/necessitas/ministro/MinistroService.java b/Ministro/src/org/kde/necessitas/ministro/MinistroService.java new file mode 100644 index 0000000..eb75801 --- /dev/null +++ b/Ministro/src/org/kde/necessitas/ministro/MinistroService.java @@ -0,0 +1,748 @@ +/* + 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.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +public class MinistroService extends Service +{ + private static final String TAG = "MinistroService"; + + private static final String MINISTRO_CHECK_UPDATES_KEY="LASTCHECK"; + private static final String MINISTRO_CHECK_FREQUENCY_KEY="CHECKFREQUENCY"; + private static final String MINISTRO_REPOSITORY_KEY="REPOSITORY"; + private static final String MINISTRO_DEFAULT_REPOSITORY="stable"; + + /// Ministro server parameter keys + private static final String REQUIRED_MODULES_KEY="required.modules"; + private static final String APPLICATION_TITLE_KEY="application.title"; + private static final String QT_PROVIDER_KEY="qt.provider"; + private static final String MINIMUM_MINISTRO_API_KEY="minimum.ministro.api"; + private static final String MINIMUM_QT_VERSION_KEY="minimum.qt.version"; + /// Ministro server parameter keys + + /// loader parameter keys + private static final String ERROR_CODE_KEY="error.code"; + private static final String ERROR_MESSAGE_KEY="error.message"; + private static final String DEX_PATH_KEY="dex.path"; + private static final String LIB_PATH_KEY="lib.path"; + private static final String LOADER_CLASS_NAME_KEY="loader.class.name"; + + private static final String NATIVE_LIBRARIES_KEY="native.libraries"; + private static final String ENVIRONMENT_VARIABLES_KEY="environment.variables"; + private static final String APPLICATION_PARAMETERS_KEY="application.parameters"; + private static final String QT_VERSION_PARAMETER_KEY="qt.version.parameter"; + /// loader parameter keys + + /// loader error codes + private static final int EC_NO_ERROR=0; + private static final int EC_INCOMPATIBLE=1; + private static final int EC_NOT_FOUND=2; + private static final int EC_INVALID_PARAMETERS=3; + private static final int EC_INVALID_QT_VERSION=3; + /// loader error codes + + + public static String getRepository(Context c) + { + SharedPreferences preferences=c.getSharedPreferences("Ministro", MODE_PRIVATE); + return preferences.getString(MINISTRO_REPOSITORY_KEY,MINISTRO_DEFAULT_REPOSITORY); + } + + public static void setRepository(Context c, String value) + { + SharedPreferences preferences=c.getSharedPreferences("Ministro", MODE_PRIVATE); + SharedPreferences.Editor editor= preferences.edit(); + editor.putString(MINISTRO_REPOSITORY_KEY,value); + editor.putLong(MINISTRO_CHECK_UPDATES_KEY,0); + editor.commit(); + } + + public static Long getCheckFrequency(Context c) + { + SharedPreferences preferences=c.getSharedPreferences("Ministro", MODE_PRIVATE); + return preferences.getLong(MINISTRO_CHECK_FREQUENCY_KEY, 7l*24*3600*1000)/(24l*3600*1000); + } + + public static void setCheckFrequency(Context c, long value) + { + SharedPreferences preferences=c.getSharedPreferences("Ministro", MODE_PRIVATE); + SharedPreferences.Editor editor= preferences.edit(); + editor.putLong(MINISTRO_CHECK_FREQUENCY_KEY, value*24*3600*1000); + editor.putLong(MINISTRO_CHECK_UPDATES_KEY,0); + editor.commit(); + } + + // used to check Ministro Service compatibility + private static final int MINISTRO_MIN_API_LEVEL=1; + private static final int MINISTRO_MAX_API_LEVEL=2; + + // MinistroService instance, its used by MinistroActivity to directly access services data (e.g. libraries) + private static MinistroService m_instance = null; + private String m_environmentVariables = null; + private String m_applicationParams = null; + private String m_loaderClassName = null; + private String m_pathSeparator = null; + public static MinistroService instance() + { + return m_instance; + } + + public MinistroService() + { + m_instance = this; + } + + private int m_actionId=0; // last actions id + private Handler m_handler = null; + + // current downloaded libraries + private final ArrayList<Library> m_downloadedLibraries = new ArrayList<Library>(); + + ArrayList<Library> getDownloadedLibraries() + { + synchronized (this) + { + return m_downloadedLibraries; + } + } + + // current available libraries + private final ArrayList<Library> m_availableLibraries = new ArrayList<Library>(); + ArrayList<Library> getAvailableLibraries() + { + synchronized (this) + { + return m_availableLibraries; + } + } + + class CheckForUpdates extends AsyncTask<Void, Void, Void> + { + @Override + protected void onPreExecute() + { + if (m_version<MinistroActivity.downloadVersionXmlFile(MinistroService.this, true)) + { + NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + int icon = R.drawable.icon; + CharSequence tickerText = getResources().getString(R.string.new_qt_libs_msg); // ticker-text + long when = System.currentTimeMillis(); // notification time + Context context = getApplicationContext(); // application Context + CharSequence contentTitle = getResources().getString(R.string.ministro_update_msg); // expanded message title + CharSequence contentText = getResources().getString(R.string.new_qt_libs_tap_msg); // expanded message text + + Intent notificationIntent = new Intent(MinistroService.this, MinistroActivity.class); + PendingIntent contentIntent = PendingIntent.getActivity(MinistroService.this, 0, notificationIntent, 0); + + // the next two lines initialize the Notification, using the configurations above + Notification notification = new Notification(icon, tickerText, when); + notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent); + notification.defaults |= Notification.DEFAULT_SOUND; + notification.defaults |= Notification.DEFAULT_LIGHTS; + try { + nm.notify(1, notification); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + @Override + protected Void doInBackground(Void... params) { + return null; + } + } + + + // this method reload all downloaded libraries + synchronized ArrayList<Library> refreshLibraries(boolean checkCrc) + { + synchronized (this) + { + try + { + m_downloadedLibraries.clear(); + m_availableLibraries.clear(); + if (! (new File(m_versionXmlFile)).exists()) + return m_downloadedLibraries; + DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder(); + Document dom = documentBuilder.parse(new FileInputStream(m_versionXmlFile)); + Element root = dom.getDocumentElement(); + m_version = Double.valueOf(root.getAttribute("version")); + m_loaderClassName=root.getAttribute("loaderClassName"); + m_applicationParams=root.getAttribute("applicationParameters"); + m_applicationParams=m_applicationParams.replaceAll("MINISTRO_PATH", getFilesDir().getAbsolutePath()); + m_environmentVariables=root.getAttribute("environmentVariables"); + m_environmentVariables=m_environmentVariables.replaceAll("MINISTRO_PATH", getFilesDir().getAbsolutePath()); + m_environmentVariables="MINISTRO_ANDROID_STYLE_PATH="+m_qtLibsRootPath+"style/\t"+m_environmentVariables; + if (root.hasAttribute("qtVersion")) + m_qtVersion = Integer.valueOf(root.getAttribute("qtVersion")); + root.normalize(); + Node node = root.getFirstChild(); + while(node != null) + { + if (node.getNodeType() == Node.ELEMENT_NODE) + { + Library lib= Library.getLibrary((Element)node, true); + File file=new File(m_qtLibsRootPath + lib.filePath); + if (file.exists()) + { + if (checkCrc && !Library.checkCRC(file.getAbsolutePath(), lib.sha1)) + file.delete(); + else + { + + boolean allOk = true; + if (lib.needs != null) + { + for(NeedsStruct needed: lib.needs)// check if its needed files are available + if (needed.type != null && needed.type.equals("jar")) + { + File f=new File(m_qtLibsRootPath + needed.filePath); + if (!f.exists()) + { + allOk = false; + break; + } + } + if (!allOk) + { + for(NeedsStruct needed: lib.needs)// remove all needed files + if (needed.type != null && needed.type.equals("jar")) + { + try { + File f=new File(m_qtLibsRootPath + needed.filePath); + if (f.exists()) + f.delete(); + } catch (Exception e) + { + e.printStackTrace(); + } + } + file.delete(); // delete the parent + } + } + if (allOk) + m_downloadedLibraries.add(lib); + } + } + m_availableLibraries.add(lib); + } + // Workaround for an unbelievable bug !!! + try { + node = node.getNextSibling(); + } catch (Exception e) { + e.printStackTrace(); + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return m_downloadedLibraries; + } + + // version xml file + private String m_versionXmlFile; + public String getVersionXmlFile() + { + return m_versionXmlFile; + } + + private String m_qtLibsRootPath; + public String getQtLibsRootPath() + { + return m_qtLibsRootPath; + } + + private double m_version = -1; + public double getVersion() + { + return m_version; + } + + private double m_qtVersion = 0x040800; + public double getQtVersion() + { + return m_qtVersion; + } + + // class used to fire an action, this class is used + // to start an activity when user needs more libraries to start its application + class ActionStruct + { + ActionStruct(IMinistroCallback cb, String[] m, ArrayList<String> notFoundMoules, String appName, Bundle p) + { + id=++m_actionId; + callback = cb; + modules = m; + parameters = p; + } + public int id; + public IMinistroCallback callback; + public String[] modules; + public Bundle parameters; + } + + // we can have more then one action + ArrayList<ActionStruct> m_actions = new ArrayList<ActionStruct>(); + + @Override + public void onCreate() + { + m_handler = new Handler(); + m_versionXmlFile = getFilesDir().getAbsolutePath()+"/version.xml"; + m_qtLibsRootPath = getFilesDir().getAbsolutePath()+"/qt/"; + m_pathSeparator = System.getProperty("path.separator", ":"); + SharedPreferences preferences=getSharedPreferences("Ministro", MODE_PRIVATE); + long lastCheck = preferences.getLong(MINISTRO_CHECK_UPDATES_KEY,0); + long checkFrequency = preferences.getLong(MINISTRO_CHECK_FREQUENCY_KEY,7l*24*3600*1000); // check once per week by default + if (MinistroActivity.isOnline(this) && System.currentTimeMillis()-lastCheck>checkFrequency) + { + refreshLibraries(true); + SharedPreferences.Editor editor= preferences.edit(); + editor.putLong(MINISTRO_CHECK_UPDATES_KEY,System.currentTimeMillis()); + editor.commit(); + new CheckForUpdates().execute((Void[])null); + } + else + refreshLibraries(false); + super.onCreate(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) + { + return new IMinistro.Stub() + { + public void requestLoader(IMinistroCallback callback, Bundle parameters) throws RemoteException + { + + checkModulesImpl(callback, parameters); + } + }; + } + + /** + * Implements the {@link IMinistro.Stub#checkModules(IMinistroCallback, String[], String, int, int)} + * service method. + * + * @param callback + * @param modules + * @param appName + * @param ministroApiLevel + * @param necessitasApiLevel + * @throws RemoteException + */ + final void checkModulesImpl(IMinistroCallback callback, Bundle parameters) throws RemoteException + { + if (!parameters.containsKey(REQUIRED_MODULES_KEY) + || !parameters.containsKey(APPLICATION_TITLE_KEY) + || !parameters.containsKey(MINIMUM_MINISTRO_API_KEY) + || !parameters.containsKey(MINIMUM_QT_VERSION_KEY)) + { + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, EC_INVALID_PARAMETERS); + loaderParams.putString(ERROR_MESSAGE_KEY, getResources().getString(R.string.invalid_parameters)); + try + { + callback.loaderReady(loaderParams); + } + catch (Exception e) { + e.printStackTrace(); + } + Log.e(TAG, "Invalid parameters: " + parameters.toString()); + return; + } + int ministroApiLevel = parameters.getInt(MINIMUM_MINISTRO_API_KEY); + String[] modules = parameters.getStringArray(REQUIRED_MODULES_KEY); + String appName = parameters.getString(APPLICATION_TITLE_KEY); + + int qtApiLevel = parameters.getInt(MINIMUM_QT_VERSION_KEY); + if (qtApiLevel > m_qtVersion) // the application needs a newer qt version + { + if (parameters.getBoolean(QT_VERSION_PARAMETER_KEY, false)) + { + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, EC_INVALID_QT_VERSION); + loaderParams.putString(ERROR_MESSAGE_KEY, getResources().getString(R.string.invalid_qt_version)); + try + { + callback.loaderReady(loaderParams); + } + catch (Exception e) { + e.printStackTrace(); + } + Log.e(TAG, "Invalid qt verson"); + return; + } + startRetrieval(callback, null, null, appName, parameters); + parameters.putBoolean(QT_VERSION_PARAMETER_KEY, true); + return; + } + + @SuppressWarnings("unused") + String qtProvider="necessitas"; + if (parameters.containsKey(QT_PROVIDER_KEY)) + qtProvider=parameters.getString(QT_PROVIDER_KEY); // TODO add the possibility to have more than one provider + + if (ministroApiLevel<MINISTRO_MIN_API_LEVEL || ministroApiLevel>MINISTRO_MAX_API_LEVEL) + { + // panic !!! Ministro service is not compatible, user should upgrade Ministro package + Bundle loaderParams = new Bundle(); + loaderParams.putInt(ERROR_CODE_KEY, EC_INCOMPATIBLE); + loaderParams.putString(ERROR_MESSAGE_KEY, getResources().getString(R.string.incompatible_ministo_api)); + try + { + callback.loaderReady(loaderParams); + } + catch (Exception e) { + e.printStackTrace(); + } + Log.e(TAG, "Ministro cannot satisfy API version: " + ministroApiLevel); + return; + } + + // check necessitasApiLevel !!! I'm pretty sure some people will completely ignore my warning + // and they will deploying apps to Android Market, so let's try to give them a chance. + + // this method is called by the activity client who needs modules. + ArrayList<String> notFoundModules = new ArrayList<String>(); + Bundle loaderParams = checkModules(modules, notFoundModules); + if (loaderParams.containsKey(ERROR_CODE_KEY) && EC_NO_ERROR == loaderParams.getInt(ERROR_CODE_KEY)) + { + try + { + Library.mergeBundleParameters(loaderParams, ENVIRONMENT_VARIABLES_KEY, parameters, ENVIRONMENT_VARIABLES_KEY); + Library.mergeBundleParameters(loaderParams, APPLICATION_PARAMETERS_KEY, parameters, APPLICATION_PARAMETERS_KEY); + callback.loaderReady(loaderParams); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + else + { + // Starts a retrieval of the modules which are not readily accessible. + startRetrieval(callback, modules, notFoundModules, appName, parameters); + } + } + + /** + * Creates and sets up a {@link MinistroActivity} to retrieve the modules specified in the + * <code>notFoundModules</code> argument. + * + * @param callback + * @param modules + * @param notFoundModules + * @param appName + * @throws RemoteException + */ + private void startRetrieval(IMinistroCallback callback, String[] modules + , ArrayList<String> notFoundModules, String appName, Bundle parameters) throws RemoteException + { + ActionStruct as = new ActionStruct(callback, modules, notFoundModules, appName, parameters); + m_actions.add(as); // if not, lets start an activity to do it. + + final Intent intent = new Intent(MinistroService.this, MinistroActivity.class); + intent.putExtra("id", as.id); + intent.putExtra("name", appName); + if (null != notFoundModules) + { + String[] libs = notFoundModules.toArray(new String[notFoundModules.size()]); + intent.putExtra("modules", libs); + } + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + boolean failed = false; + try + { + m_handler.postDelayed(new Runnable() { + public void run() { + MinistroService.this.startActivity(intent); + } + }, 100); + } + catch(Exception e) + { + failed = true; + throw (RemoteException) new RemoteException().initCause(e); + } + finally + { + // Removes the dead Activity from our list as it will never finish by itself. + if (failed) + m_actions.remove(as); + } + } + + /** + * Called by a finished {@link MinistroActivity} in order to let + * the service notify the application which caused the activity about + * the result of the retrieval. + * + * @param id + */ + void retrievalFinished(int id) + { + for (int i=0;i<m_actions.size();i++) + { + ActionStruct action=m_actions.get(i); + if (action.id==id) + { + postRetrieval(action); + m_actions.remove(i); + break; + } + } + if (m_actions.size() == 0) + m_actionId = 0; + } + + /** + * Helper method for the last step of the retrieval process. + * + * <p>Checks the availability of the requested modules and informs + * the requesting application about it via the {@link IMinistroCallback} + * instance.</p> + * + * @param callback + * @param modules + */ + private void postRetrieval(ActionStruct action) + { + // Does a final check whether the libraries are accessible (without caring for + // the non-accessible ones). + try + { + if (null != action.modules) + { + Bundle loaderParams = checkModules(action.modules, null); + Library.mergeBundleParameters(loaderParams, ENVIRONMENT_VARIABLES_KEY, action.parameters, ENVIRONMENT_VARIABLES_KEY); + Library.mergeBundleParameters(loaderParams, APPLICATION_PARAMETERS_KEY, action.parameters, APPLICATION_PARAMETERS_KEY); + action.callback.loaderReady(loaderParams); + } + else + checkModulesImpl(action.callback, action.parameters); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * Checks whether a given list of libraries are readily accessible (e.g. usable by a program). + * + * <p>If the <code>notFoundModules</code> argument is given, the method fills the list with + * libraries that need to be retrieved first.</p> + * + * @param libs + * @param notFoundModules + * @return true if all modules are available + */ + Bundle checkModules(String[] modules, ArrayList<String> notFoundModules) + { + Bundle params = new Bundle(); + boolean res=true; + ArrayList<Module> libs= new ArrayList<Module>(); + Set<String> jars= new HashSet<String>(); + for (String module: modules) + res = res & addModules(module, libs, notFoundModules, jars); // don't stop on first error + + ArrayList<String> librariesArray = new ArrayList<String>(); + // sort all libraries + Collections.sort(libs, new ModuleCompare()); + for (Module lib: libs) + librariesArray.add(m_qtLibsRootPath+lib.path); + params.putStringArrayList(NATIVE_LIBRARIES_KEY, librariesArray); + + ArrayList<String> jarsArray = new ArrayList<String>(); + for (String jar: jars) + jarsArray.add(m_qtLibsRootPath+jar); + params.putString(DEX_PATH_KEY, Library.join(jarsArray, m_pathSeparator)); + + params.putString(LOADER_CLASS_NAME_KEY, m_loaderClassName); + params.putString(LIB_PATH_KEY, m_qtLibsRootPath); + params.putString(ENVIRONMENT_VARIABLES_KEY, m_environmentVariables); + params.putString(APPLICATION_PARAMETERS_KEY, m_applicationParams); + params.putInt(ERROR_CODE_KEY, res?EC_NO_ERROR:EC_NOT_FOUND); + if (!res) + params.putString(ERROR_MESSAGE_KEY, getResources().getString(R.string.dependencies_error)); + return params; + } + +/** + * Helper method for the module resolution mechanism. It deals with an individual module's + * resolution request. + * + * <p>The method checks whether a given <em>single</em> <code>module</code> is already + * accessible or needs to be retrieved first. In the latter case the method returns + * <code>false</code>.</p> + * + * <p>The method traverses a <code>module<code>'s dependencies automatically.</p> + * + * <p>In order to find out whether a <code>module</code> is accessible the method consults + * the list of downloaded libraries. If found, an entry to the <code>modules</code> list is + * added.</p> + * + * <p>In case the <code>module</code> is not immediately accessible and the <code>notFoundModules</code> + * argument exists, a list of available libraries is consulted to fill a list of modules which + * yet need to be retrieved.</p> + * + * @param module + * @param modules + * @param notFoundModules + * @param jars + * @return <code>true</code> if the given module and all its dependencies are readily available. + */ + private boolean addModules(String module, ArrayList<Module> modules + , ArrayList<String> notFoundModules, Set<String> jars) + { + // Module argument is not supposed to be null at this point. + if (modules == null) + return false; // we are in deep shit if this happens + + // Short-cut: If the module is already in our list of previously found modules then we do not + // need to consult the list of downloaded modules. + for (int i=0;i<modules.size();i++) + { + if (modules.get(i).name.equals(module)) + return true; + } + + // Consult the list of downloaded modules. If a matching entry is found, it is added to the + // list of readily accessible modules and its dependencies are checked via a recursive call. + for (Library library:m_downloadedLibraries) + { + if (library.name.equals(module)) + { + Module m = new Module(); + m.name=library.name; + m.path=library.filePath; + m.level=library.level; + if (library.needs != null) + for(NeedsStruct needed: library.needs) + if (needed.type != null && needed.type.equals("jar")) + jars.add(needed.filePath); + modules.add(m); + + boolean res = true; + if (library.depends != null) + for (String depend: library.depends) + res &= addModules(depend, modules, notFoundModules, jars); + + if (library.replaces != null) + for (String replaceLibrary: library.replaces) + for (int mIt=0; mIt<modules.size();mIt++) + if (replaceLibrary.equals(modules.get(mIt).name)) + modules.remove(mIt--); + + return res; + } + } + + // Requested module is not readily accessible. + if (notFoundModules != null) + { + // Checks list of modules which are known to not be readily accessible and returns early to + // prevent double entries. + for (int i=0;i<notFoundModules.size();i++) + { + if (notFoundModules.get(i).equals(module)) + return false; + } + + // Deal with not yet readily accessible module's dependencies. + notFoundModules.add(module); + for (int i = 0; i< m_availableLibraries.size(); i++) + { + if (m_availableLibraries.get(i).name.equals(module)) + { + if (m_availableLibraries.get(i).depends != null) + for (int depIt=0;depIt<m_availableLibraries.get(i).depends.length;depIt++) + addModules(m_availableLibraries.get(i).depends[depIt], modules, notFoundModules, jars); + break; + } + } + } + return false; + } + + /** Sorter for libraries. + * + * Hence the order in which the libraries have to be loaded is important, it is neccessary + * to sort them. + */ + static private class ModuleCompare implements Comparator<Module> + { + public int compare(Module a, Module b) + { + return a.level-b.level; + } + } + + /** Helper class which allows manipulating libraries. + * + * It is similar to the {@link Library} class but has fewer fields. + */ + static private class Module + { + String path; + String name; + int level; + } +} |