/tags/20110815/src/com/anysoftkeyboard/addons/AddOnsFactory.java

http://softkeyboard.googlecode.com/ · Java · 258 lines · 201 code · 42 blank · 15 comment · 51 complexity · deea4eaa488c7294466bbffbe4309a12 MD5 · raw file

  1. package com.anysoftkeyboard.addons;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import java.util.Collections;
  5. import java.util.Comparator;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import org.xmlpull.v1.XmlPullParser;
  9. import org.xmlpull.v1.XmlPullParserException;
  10. import com.anysoftkeyboard.AnySoftKeyboard;
  11. import com.menny.android.anysoftkeyboard.AnyApplication;
  12. import android.content.Context;
  13. import android.content.Intent;
  14. import android.content.pm.ActivityInfo;
  15. import android.content.pm.PackageManager;
  16. import android.content.pm.PackageManager.NameNotFoundException;
  17. import android.content.pm.ResolveInfo;
  18. import android.util.AttributeSet;
  19. import android.util.Log;
  20. import android.util.Xml;
  21. public abstract class AddOnsFactory<E extends AddOn> {
  22. private final static ArrayList<AddOnsFactory<?> > mActiveInstances = new ArrayList<AddOnsFactory<?> >();
  23. public static void onPackageChanged(Intent eventIntent)
  24. {
  25. boolean cleared = false;
  26. for(AddOnsFactory<?> factory : mActiveInstances)
  27. {
  28. if (factory.isEventRequiresCacheRefresh(eventIntent))
  29. {
  30. cleared = true;
  31. if (AnyApplication.DEBUG) Log.d("AddOnsFactory", factory.getClass().getName()+" will handle this package-changed event.");
  32. factory.clearAddOnList();
  33. }
  34. }
  35. if (cleared) AnySoftKeyboard.getInstance().forceKeyboardsRecreation();
  36. }
  37. protected final String TAG;
  38. /**
  39. * This is the interface name that a broadcast receiver implementing an
  40. * external addon should say that it supports -- that is, this is the
  41. * action it uses for its intent filter.
  42. */
  43. private final String RECEIVER_INTERFACE;
  44. /**
  45. * Name under which an external addon broadcast receiver component
  46. * publishes information about itself.
  47. */
  48. private final String RECEIVER_META_DATA;
  49. private final ArrayList<E> mAddOns = new ArrayList<E>();
  50. private final HashMap<String, E> mAddOnsById = new HashMap<String, E>();
  51. private final String ROOT_NODE_TAG;
  52. private final String ADDON_NODE_TAG;
  53. private final int mBuildInAddOnsResId;
  54. private static final String XML_PREF_ID_ATTRIBUTE = "id";
  55. private static final String XML_NAME_RES_ID_ATTRIBUTE = "nameResId";
  56. private static final String XML_DESCRIPTION_ATTRIBUTE = "description";
  57. private static final String XML_SORT_INDEX_ATTRIBUTE = "index";
  58. protected AddOnsFactory(String tag, String receiverInterface, String receiverMetaData, String rootNodeTag, String addonNodeTag, int buildInAddonResId)
  59. {
  60. TAG = tag;
  61. RECEIVER_INTERFACE = receiverInterface;
  62. RECEIVER_META_DATA = receiverMetaData;
  63. ROOT_NODE_TAG = rootNodeTag;
  64. ADDON_NODE_TAG = addonNodeTag;
  65. mBuildInAddOnsResId = buildInAddonResId;
  66. mActiveInstances.add(this);
  67. }
  68. protected boolean isEventRequiresCacheRefresh(Intent eventIntent) {
  69. return true;
  70. }
  71. protected synchronized void clearAddOnList() {
  72. mAddOns.clear();
  73. mAddOnsById.clear();
  74. }
  75. public synchronized E getAddOnById(String id, Context askContext)
  76. {
  77. if (mAddOnsById.size() == 0)
  78. {
  79. loadAddOns(askContext);
  80. }
  81. return mAddOnsById.get(id);
  82. }
  83. public synchronized final ArrayList<E> getAllAddOns(Context askContext) {
  84. if (mAddOns.size() == 0)
  85. {
  86. loadAddOns(askContext);
  87. }
  88. return mAddOns;
  89. }
  90. protected void loadAddOns(final Context askContext) {
  91. clearAddOnList();
  92. mAddOns.addAll(getAddOnsFromResId(askContext, mBuildInAddOnsResId));
  93. mAddOns.addAll(getExternalAddOns(askContext));
  94. buildOtherDataBasedOnNewAddOns(mAddOns);
  95. //sorting the keyboards according to the requested
  96. //sort order (from minimum to maximum)
  97. Collections.sort(mAddOns, new Comparator<AddOn>()
  98. {
  99. public int compare(AddOn k1, AddOn k2)
  100. {
  101. Context c1 = k1.getPackageContext();
  102. Context c2 = k2.getPackageContext();
  103. if (c1 == null)
  104. c1 = askContext;
  105. if (c2 == null)
  106. c2 = askContext;
  107. if (c1 == c2)
  108. return k1.getSortIndex() - k2.getSortIndex();
  109. else if (c1 == askContext)//I want to make sure ASK packages are first
  110. return -1;
  111. else if (c2 == askContext)
  112. return 1;
  113. else
  114. return c1.getPackageName().compareToIgnoreCase(c2.getPackageName());
  115. }
  116. });
  117. }
  118. protected void buildOtherDataBasedOnNewAddOns(ArrayList<E> newAddOns) {
  119. for(E addOn : newAddOns)
  120. mAddOnsById.put(addOn.getId(), addOn);
  121. }
  122. private ArrayList<E> getExternalAddOns(Context context){
  123. final List<ResolveInfo> broadcastReceivers =
  124. context.getPackageManager().queryBroadcastReceivers(new Intent(RECEIVER_INTERFACE), PackageManager.GET_META_DATA);
  125. final ArrayList<E> externalAddOns = new ArrayList<E>();
  126. for(final ResolveInfo receiver : broadcastReceivers){
  127. if (receiver.activityInfo == null) {
  128. Log.e(TAG, "BroadcastReceiver has null ActivityInfo. Receiver's label is "
  129. + receiver.loadLabel(context.getPackageManager()));
  130. Log.e(TAG, "Is the external keyboard a service instead of BroadcastReceiver?");
  131. // Skip to next receiver
  132. continue;
  133. }
  134. try {
  135. final Context externalPackageContext = context.createPackageContext(receiver.activityInfo.packageName, PackageManager.GET_META_DATA);
  136. final ArrayList<E> packageAddOns = getAddOnsFromActivityInfo(externalPackageContext, receiver.activityInfo);
  137. externalAddOns.addAll(packageAddOns);
  138. } catch (final NameNotFoundException e) {
  139. Log.e(TAG, "Did not find package: " + receiver.activityInfo.packageName);
  140. }
  141. }
  142. return externalAddOns;
  143. }
  144. private ArrayList<E> getAddOnsFromResId(Context context, int addOnsResId) {
  145. final XmlPullParser xml = context.getResources().getXml(addOnsResId);
  146. if (xml == null)
  147. return new ArrayList<E>();
  148. return parseAddOnsFromXml(context, xml);
  149. }
  150. private ArrayList<E> getAddOnsFromActivityInfo(Context context, ActivityInfo ai) {
  151. final XmlPullParser xml = ai.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA);
  152. if (xml == null)//issue 718: maybe a bad package?
  153. return new ArrayList<E>();
  154. return parseAddOnsFromXml(context, xml);
  155. }
  156. private ArrayList<E> parseAddOnsFromXml(Context context, XmlPullParser xml) {
  157. final ArrayList<E> addOns = new ArrayList<E>();
  158. try {
  159. int event;
  160. boolean inRoot = false;
  161. while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) {
  162. final String tag = xml.getName();
  163. if (event == XmlPullParser.START_TAG) {
  164. if (ROOT_NODE_TAG.equals(tag)) {
  165. inRoot = true;
  166. } else if (inRoot && ADDON_NODE_TAG.equals(tag)) {
  167. final AttributeSet attrs = Xml.asAttributeSet(xml);
  168. E addOn = createAddOnFromXmlAttributes(attrs, context);
  169. if (addOn != null)
  170. {
  171. addOns.add(addOn);
  172. }
  173. }
  174. } else if (event == XmlPullParser.END_TAG) {
  175. if (ROOT_NODE_TAG.equals(tag)) {
  176. inRoot = false;
  177. break;
  178. }
  179. }
  180. }
  181. } catch (final IOException e) {
  182. Log.e(TAG, "IO error:" + e);
  183. e.printStackTrace();
  184. } catch (final XmlPullParserException e) {
  185. Log.e(TAG, "Parse error:" + e);
  186. e.printStackTrace();
  187. }
  188. return addOns;
  189. }
  190. private E createAddOnFromXmlAttributes(AttributeSet attrs, Context context) {
  191. final String prefId = attrs.getAttributeValue(null, XML_PREF_ID_ATTRIBUTE);
  192. final int nameId = attrs.getAttributeResourceValue(null, XML_NAME_RES_ID_ATTRIBUTE, -1);
  193. final int descriptionInt = attrs.getAttributeResourceValue(null, XML_DESCRIPTION_ATTRIBUTE,-1);
  194. //NOTE, to be compatibel we need this. because the most of descriptions are
  195. //without @string/adb
  196. String description;
  197. if(descriptionInt != -1){
  198. description = context.getResources().getString(descriptionInt);
  199. } else {
  200. description = attrs.getAttributeValue(null, XML_DESCRIPTION_ATTRIBUTE);
  201. }
  202. final int sortIndex = attrs.getAttributeUnsignedIntValue(null, XML_SORT_INDEX_ATTRIBUTE, 1);
  203. // asserting
  204. if ((prefId == null) || (nameId == -1)) {
  205. Log.e(TAG, "External add-on does not include all mandatory details! Will not create add-on.");
  206. return null;
  207. } else {
  208. if (AnyApplication.DEBUG) {
  209. Log.d(TAG, "External addon details: prefId:" + prefId + " nameId:" + nameId);
  210. }
  211. return createConcreateAddOn(context, prefId, nameId, description, sortIndex, attrs);
  212. }
  213. }
  214. protected abstract E createConcreateAddOn(Context context, String prefId, int nameId,
  215. String description, int sortIndex, AttributeSet attrs);
  216. }