package net.momodalo.app.vimtouch.addons; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; public abstract class AddOnsFactory<E extends AddOn> { private final static ArrayList<AddOnsFactory<?> > mActiveInstances = new ArrayList<AddOnsFactory<?> >(); /* public static void onPackageChanged(final Intent eventIntent, final AnySoftKeyboard ask) { boolean cleared = false; boolean recreateView = false; for(AddOnsFactory<?> factory : mActiveInstances) { try { if (factory.isEventRequiresCacheRefresh(eventIntent, ask.getApplicationContext())) { cleared = true; if (factory.isEventRequiresViewReset(eventIntent, ask.getApplicationContext())) recreateView = true; factory.clearAddOnList(); } } catch (NameNotFoundException e) { e.printStackTrace(); } } if (cleared) ask.resetKeyboardView(recreateView); } */ protected final String TAG; /** * This is the interface name that a broadcast receiver implementing an * external addon should say that it supports -- that is, this is the * action it uses for its intent filter. */ private final String RECEIVER_INTERFACE; /** * Name under which an external addon broadcast receiver component * publishes information about itself. */ private final String RECEIVER_META_DATA; private final ArrayList<E> mAddOns = new ArrayList<E>(); private final HashMap<String, E> mAddOnsById = new HashMap<String, E>(); private final String ROOT_NODE_TAG; private final String ADDON_NODE_TAG; private static final String XML_PREF_ID_ATTRIBUTE = "id"; private static final String XML_NAME_RES_ID_ATTRIBUTE = "nameResId"; private static final String XML_DESCRIPTION_ATTRIBUTE = "description"; private static final String XML_SORT_INDEX_ATTRIBUTE = "index"; protected AddOnsFactory(String tag, String receiverInterface, String receiverMetaData, String rootNodeTag, String addonNodeTag) { TAG = tag; RECEIVER_INTERFACE = receiverInterface; RECEIVER_META_DATA = receiverMetaData; ROOT_NODE_TAG = rootNodeTag; ADDON_NODE_TAG = addonNodeTag; mActiveInstances.add(this); } /* protected boolean isEventRequiresCacheRefresh(Intent eventIntent, Context context) throws NameNotFoundException { String action = eventIntent.getAction(); String packageNameSchemePart = eventIntent.getData().getSchemeSpecificPart(); if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { //will reset only if the new package has my addons boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); if (hasAddon) { Log.d(TAG, "It seems that an addon exists in a newly installed package "+packageNameSchemePart+". I need to reload stuff."); return true; } } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { //If I'm managing OR it contains an addon (could be new feature in the package), I want to reset. boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); if (isPackagedManaged) { Log.d(TAG, "It seems that an addon I use (in package "+packageNameSchemePart+") has been changed. I need to reload stuff."); return true; } else { boolean hasAddon = isPackageContainAnAddon(context, packageNameSchemePart); if (hasAddon) { Log.d(TAG, "It seems that an addon exists in an updated package "+packageNameSchemePart+". I need to reload stuff."); return true; } } } else //removed { //so only if I manage this package, I want to reset boolean isPackagedManaged = isPackageManaged(packageNameSchemePart); if (isPackagedManaged) { Log.d(TAG, "It seems that an addon I use (in package "+packageNameSchemePart+") has been removed. I need to reload stuff."); return true; } } return false; } protected boolean isPackageManaged(String packageNameSchemePart) { for (AddOn addOn : mAddOns) { if (addOn.getPackageContext().getPackageName().equals(packageNameSchemePart)) { return true; } } return false; } protected boolean isPackageContainAnAddon(Context context, String packageNameSchemePart) throws NameNotFoundException { PackageInfo newPackage = context.getPackageManager().getPackageInfo(packageNameSchemePart, PackageManager.GET_RECEIVERS+PackageManager.GET_META_DATA); if (newPackage.receivers != null) { ActivityInfo[] receivers = newPackage.receivers; for(ActivityInfo aReceiver : receivers) { //issue 904 if (aReceiver == null || aReceiver.applicationInfo == null || !aReceiver.enabled || !aReceiver.applicationInfo.enabled) continue; final XmlPullParser xml = aReceiver.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); if (xml != null) { return true; } } } return false; } protected boolean isEventRequiresViewReset(Intent eventIntent, Context context) { return false; } */ protected synchronized void clearAddOnList() { mAddOns.clear(); mAddOnsById.clear(); } public synchronized E getAddOnById(String id, Context askContext) { if (mAddOnsById.size() == 0) { loadAddOns(askContext); } return mAddOnsById.get(id); } public synchronized final ArrayList<E> getAllAddOns(Context askContext) { if (mAddOns.size() == 0) { loadAddOns(askContext); } return mAddOns; } protected void loadAddOns(final Context askContext) { clearAddOnList(); mAddOns.addAll(getExternalAddOns(askContext)); buildOtherDataBasedOnNewAddOns(mAddOns); //sorting the keyboards according to the requested //sort order (from minimum to maximum) Collections.sort(mAddOns, new Comparator<AddOn>() { public int compare(AddOn k1, AddOn k2) { Context c1 = k1.getPackageContext(); Context c2 = k2.getPackageContext(); if (c1 == null) c1 = askContext; if (c2 == null) c2 = askContext; if (c1 == c2) return k1.getSortIndex() - k2.getSortIndex(); else if (c1 == askContext)//I want to make sure ASK packages are first return -1; else if (c2 == askContext) return 1; else return c1.getPackageName().compareToIgnoreCase(c2.getPackageName()); } }); } protected void buildOtherDataBasedOnNewAddOns(ArrayList<E> newAddOns) { for(E addOn : newAddOns) mAddOnsById.put(addOn.getId(), addOn); } private ArrayList<E> getExternalAddOns(Context context){ final ArrayList<E> externalAddOns = new ArrayList<E>(); final List<ResolveInfo> broadcastReceivers = context.getPackageManager().queryBroadcastReceivers(new Intent(RECEIVER_INTERFACE), PackageManager.GET_META_DATA); for(final ResolveInfo receiver : broadcastReceivers){ if (receiver.activityInfo == null) { Log.e(TAG, "BroadcastReceiver has null ActivityInfo. Receiver's label is " + receiver.loadLabel(context.getPackageManager())); Log.e(TAG, "Is the external keyboard a service instead of BroadcastReceiver?"); // Skip to next receiver continue; } if (!receiver.activityInfo.enabled || !receiver.activityInfo.applicationInfo.enabled) continue; try { final Context externalPackageContext = context.createPackageContext(receiver.activityInfo.packageName, PackageManager.GET_META_DATA); final ArrayList<E> packageAddOns = getAddOnsFromActivityInfo(externalPackageContext, receiver.activityInfo); externalAddOns.addAll(packageAddOns); } catch (final NameNotFoundException e) { Log.e(TAG, "Did not find package: " + receiver.activityInfo.packageName); } } return externalAddOns; } private ArrayList<E> getAddOnsFromActivityInfo(Context context, ActivityInfo ai) { final XmlPullParser xml = ai.loadXmlMetaData(context.getPackageManager(), RECEIVER_META_DATA); if (xml == null)//issue 718: maybe a bad package? return new ArrayList<E>(); return parseAddOnsFromXml(context, xml); } private ArrayList<E> parseAddOnsFromXml(Context context, XmlPullParser xml) { final ArrayList<E> addOns = new ArrayList<E>(); try { int event; boolean inRoot = false; while ((event = xml.next()) != XmlPullParser.END_DOCUMENT) { final String tag = xml.getName(); if (event == XmlPullParser.START_TAG) { if (ROOT_NODE_TAG.equals(tag)) { inRoot = true; } else if (inRoot && ADDON_NODE_TAG.equals(tag)) { final AttributeSet attrs = Xml.asAttributeSet(xml); E addOn = createAddOnFromXmlAttributes(attrs, context); if (addOn != null) { addOns.add(addOn); } } } else if (event == XmlPullParser.END_TAG) { if (ROOT_NODE_TAG.equals(tag)) { inRoot = false; break; } } } } catch (final IOException e) { Log.e(TAG, "IO error:" + e); e.printStackTrace(); } catch (final XmlPullParserException e) { Log.e(TAG, "Parse error:" + e); e.printStackTrace(); } return addOns; } private E createAddOnFromXmlAttributes(AttributeSet attrs, Context context) { final String prefId = attrs.getAttributeValue(null, XML_PREF_ID_ATTRIBUTE); final int nameId = attrs.getAttributeResourceValue(null, XML_NAME_RES_ID_ATTRIBUTE, AddOn.INVALID_RES_ID); final int descriptionInt = attrs.getAttributeResourceValue(null, XML_DESCRIPTION_ATTRIBUTE, AddOn.INVALID_RES_ID); //NOTE, to be compatibel we need this. because the most of descriptions are //without @string/adb String description; if(descriptionInt != AddOn.INVALID_RES_ID){ description = context.getResources().getString(descriptionInt); } else { description = attrs.getAttributeValue(null, XML_DESCRIPTION_ATTRIBUTE); } final int sortIndex = attrs.getAttributeUnsignedIntValue(null, XML_SORT_INDEX_ATTRIBUTE, 1); // asserting if ((prefId == null) || (nameId == AddOn.INVALID_RES_ID)) { Log.e(TAG, "External add-on does not include all mandatory details! Will not create add-on."); return null; } else { return createConcreteAddOn(context, prefId, nameId, description, sortIndex, attrs); } } protected abstract E createConcreteAddOn(Context context, String prefId, int nameId, String description, int sortIndex, AttributeSet attrs); }