//package com.yourcompany.yourcondition; //package com.yourcompany.yoursetting; package it.angelic.soulissclient.helpers; // Constants and functions for Tasker *extensions* to the plugin protocol // See Also: http://tasker.dinglisch.net/plugins.html // Release Notes // v1.1 20140202 // added function variableNameValid() // fixed some javadoc entries (thanks to David Stone) // v1.2 20140211 // added ACTION_EDIT_EVENT // v1.3 20140227 // added REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX and REQUESTED_TIMEOUT_MS_NEVER // requestTimeoutMS(): added range check // v1.4 20140516 // support for data pass through in REQUEST_QUERY intent // some javadoc entries fixed (thanks again David :-)) // v1.5 20141120 // added RESULT_CODE_FAILED_PLUGIN_FIRST // added Setting.VARNAME_ERROR_MESSAGE // v1.6 20150213 // added Setting.getHintTimeoutMS() // added Host.addHintTimeoutMS() import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.util.Log; import java.net.URISyntaxException; import java.security.SecureRandom; import java.util.regex.Pattern; public class TaskerPlugin { private final static String TAG = "TaskerPlugin"; private final static String BASE_KEY = "net.dinglisch.android.tasker"; private final static String EXTRAS_PREFIX = BASE_KEY + ".extras."; private final static int FIRST_ON_FIRE_VARIABLES_TASKER_VERSION = 80; public final static String VARIABLE_PREFIX = "%"; // when generating non-repeating integers, look this far back for repeats // see getPositiveNonRepeatingRandomInteger() private final static int RANDOM_HISTORY_SIZE = 100; /** * Action that the EditActivity for an event plugin should be launched by */ public final static String ACTION_EDIT_EVENT = BASE_KEY + ".ACTION_EDIT_EVENT"; private final static String VARIABLE_NAME_START_EXPRESSION = "[\\w&&[^_]]"; private final static String VARIABLE_NAME_MID_EXPRESSION = "[\\w0-9]+"; private final static String VARIABLE_NAME_END_EXPRESSION = "[\\w0-9&&[^_]]"; public final static String VARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION = VARIABLE_NAME_START_EXPRESSION + VARIABLE_NAME_MID_EXPRESSION + VARIABLE_NAME_END_EXPRESSION; public final static String VARIABLE_NAME_MATCH_EXPRESSION = VARIABLE_PREFIX + "+" + VARIABLE_NAME_MAIN_PART_MATCH_EXPRESSION; private static Pattern VARIABLE_NAME_MATCH_PATTERN = null; /** * @see #addVariableBundle(Bundle, Bundle) * @see Host#getVariablesBundle(Bundle) */ private final static String EXTRA_VARIABLES_BUNDLE = EXTRAS_PREFIX + "VARIABLES"; /** * Host capabilities, passed to plugin with edit intents */ private final static String EXTRA_HOST_CAPABILITIES = EXTRAS_PREFIX + "HOST_CAPABILITIES"; /** * @see Setting#hostSupportsVariableReturn(Bundle) */ public final static int EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES = 2; /** * @see Condition#hostSupportsVariableReturn(Bundle) */ public final static int EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES = 4; /** * @see Setting#hostSupportsOnFireVariableReplacement(Bundle) */ public final static int EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT = 8; /** * @see Setting#hostSupportsVariableReturn(Bundle) */ private final static int EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES = 16; public final static int EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION = 32; public final static int EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH = 64; public final static int EXTRA_HOST_CAPABILITY_ALL = EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES | EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES | EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT | EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES | EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION | EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH; /** * Miscellaneous operational hints going one way or the other * * @see Setting#hostSupportsVariableReturn(Bundle) */ private final static String EXTRA_HINTS_BUNDLE = EXTRAS_PREFIX + "HINTS"; private final static String BUNDLE_KEY_HINT_PREFIX = ".hints."; private final static String BUNDLE_KEY_HINT_TIMEOUT_MS = BUNDLE_KEY_HINT_PREFIX + "TIMEOUT"; /** * @see #hostSupportsRelevantVariables(Bundle) * @see #addRelevantVariableList(Intent, String[]) * @see #getRelevantVariableList(Bundle) */ private final static String BUNDLE_KEY_RELEVANT_VARIABLES = BASE_KEY + ".RELEVANT_VARIABLES"; public static boolean hostSupportsRelevantVariables(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_RELEVANT_VARIABLES); } /** * Specifies to host which variables might be used by the plugin. * <p/> * Used in EditActivity, before setResult(). * * @param intentToHost the intent being returned to the host * @param variableNames array of relevant variable names */ public static void addRelevantVariableList(Intent intentToHost, String[] variableNames) { intentToHost.putExtra(BUNDLE_KEY_RELEVANT_VARIABLES, variableNames); } /** * Validate a variable name. * <p/> * The basic requirement for variables from a plugin is that they must be all lower-case. * * @param varName name to check */ public static boolean variableNameValid(String varName) { boolean validFlag = false; if (varName == null) Log.d(TAG, "variableNameValid: null name"); else { if (VARIABLE_NAME_MATCH_PATTERN == null) VARIABLE_NAME_MATCH_PATTERN = Pattern.compile(VARIABLE_NAME_MATCH_EXPRESSION, 0); if (VARIABLE_NAME_MATCH_PATTERN.matcher(varName).matches()) { if (variableNameIsLocal(varName)) validFlag = true; else Log.d(TAG, "variableNameValid: name not local: " + varName); } else Log.d(TAG, "variableNameValid: invalid name: " + varName); } return validFlag; } /** * Allows the plugin/host to indicate to each other a set of variables which they are referencing. * The host may use this to e.g. show a variable selection list in it's UI. * The host should use this if it previously indicated to the plugin that it supports relevant vars * * @param fromHostIntentExtras usually from getIntent().getExtras() * @return variableNames an array of relevant variable names */ public static String[] getRelevantVariableList(Bundle fromHostIntentExtras) { String[] relevantVars = (String[]) getBundleValueSafe(fromHostIntentExtras, BUNDLE_KEY_RELEVANT_VARIABLES, String[].class, "getRelevantVariableList"); if (relevantVars == null) relevantVars = new String[0]; return relevantVars; } /** * Used by: plugin QueryReceiver, FireReceiver * <p/> * Add a bundle of variable name/value pairs. * <p/> * Names must be valid Tasker local variable names. * Values must be String, String [] or ArrayList<String> * Null values cause deletion of possible already-existing variables. * * @param resultExtras the result extras from the receiver onReceive (from a call to getResultExtras()) * @param variables the variables to send * @see Setting#hostSupportsVariableReturn(Bundle) * @see #variableNameValid(String) */ public static void addVariableBundle(Bundle resultExtras, Bundle variables) { resultExtras.putBundle(EXTRA_VARIABLES_BUNDLE, variables); } // ----------------------------- SETTING PLUGIN ONLY --------------------------------- // public static class Setting { /** * Variable name into which a description of any error that occurred can be placed * for the user to process. * <p/> * Should *only* be set when the BroadcastReceiver result code indicates a failure. * <p/> * Note that the user needs to have configured the task to continue after failure of the plugin * action otherwise they will not be able to make use of the error message. * <p/> * For use with #addRelevantVariableList(Intent, String[]) and #addVariableBundle(Bundle, Bundle) */ public final static String VARNAME_ERROR_MESSAGE = VARIABLE_PREFIX + "errmsg"; /** * @see #setVariableReplaceKeys(Bundle, String[]) */ private final static String BUNDLE_KEY_VARIABLE_REPLACE_STRINGS = EXTRAS_PREFIX + "VARIABLE_REPLACE_KEYS"; /** * @see #requestTimeoutMS(android.content.Intent, int) */ private final static String EXTRA_REQUESTED_TIMEOUT = EXTRAS_PREFIX + "REQUESTED_TIMEOUT"; /** * @see #requestTimeoutMS(android.content.Intent, int) */ public final static int REQUESTED_TIMEOUT_MS_NONE = 0; /** * @see #requestTimeoutMS(android.content.Intent, int) */ public final static int REQUESTED_TIMEOUT_MS_MAX = 3599000; /** * @see #requestTimeoutMS(android.content.Intent, int) */ public final static int REQUESTED_TIMEOUT_MS_NEVER = REQUESTED_TIMEOUT_MS_MAX + 1000; /** * @see #signalFinish(Context, Intent, int, Bundle) * @see Host#addCompletionIntent(Intent, Intent) */ private final static String EXTRA_PLUGIN_COMPLETION_INTENT = EXTRAS_PREFIX + "COMPLETION_INTENT"; /** * @see #signalFinish(Context, Intent, int, Bundle) * @see Host#getSettingResultCode(Intent) */ public final static String EXTRA_RESULT_CODE = EXTRAS_PREFIX + "RESULT_CODE"; /** * @see #signalFinish(Context, Intent, int, Bundle) * @see Host#getSettingResultCode(Intent) */ public final static int RESULT_CODE_OK = Activity.RESULT_OK; public final static int RESULT_CODE_OK_MINOR_FAILURES = Activity.RESULT_FIRST_USER; public final static int RESULT_CODE_FAILED = Activity.RESULT_FIRST_USER + 1; public final static int RESULT_CODE_PENDING = Activity.RESULT_FIRST_USER + 2; public final static int RESULT_CODE_UNKNOWN = Activity.RESULT_FIRST_USER + 3; /** * If a plugin wants to define it's own error codes, start numbering them here. * The code will be placed in an error variable (%err in the case of Tasker) for * the user to process after the plugin action. */ public final static int RESULT_CODE_FAILED_PLUGIN_FIRST = Activity.RESULT_FIRST_USER + 9; /** * Used by: plugin EditActivity. * <p/> * Indicates to plugin that host will replace variables in specified bundle keys. * <p/> * Replacement takes place every time the setting is fired, before the bundle is * passed to the plugin FireReceiver. * * @param extrasFromHost intent extras from the intent received by the edit activity * @see #setVariableReplaceKeys(Bundle, String[]) */ public static boolean hostSupportsOnFireVariableReplacement(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_FIRE_VARIABLE_REPLACEMENT); } /** * Used by: plugin EditActivity. * <p/> * Description as above. * <p/> * This version also includes backwards compatibility with pre 4.2 Tasker versions. * At some point this function will be deprecated. * * @param editActivity the plugin edit activity, needed to test calling Tasker version * @see #setVariableReplaceKeys(Bundle, String[]) */ public static boolean hostSupportsOnFireVariableReplacement(Activity editActivity) { boolean supportedFlag = hostSupportsOnFireVariableReplacement(editActivity.getIntent().getExtras()); if (!supportedFlag) { String callerPackage = editActivity.getCallingActivity().getPackageName(); // Tasker only supporteed this from 1.0.10 supportedFlag = (callerPackage.startsWith(BASE_KEY)) && (getPackageVersionCode(editActivity.getPackageManager(), callerPackage) > FIRST_ON_FIRE_VARIABLES_TASKER_VERSION) ; } return supportedFlag; } public static boolean hostSupportsSynchronousExecution(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_SYNCHRONOUS_EXECUTION); } /** * Request the host to wait the specified number of milliseconds before continuing. * Note that the host may choose to ignore the request. * <p/> * Maximum value is REQUESTED_TIMEOUT_MS_MAX. * Also available are REQUESTED_TIMEOUT_MS_NONE (continue immediately without waiting * for the plugin to finish) and REQUESTED_TIMEOUT_MS_NEVER (wait forever for * a result). * <p/> * Used in EditActivity, before setResult(). * * @param intentToHost the intent being returned to the host * @param timeoutMS */ public static void requestTimeoutMS(Intent intentToHost, int timeoutMS) { if (timeoutMS < 0) Log.w(TAG, "requestTimeoutMS: ignoring negative timeout (" + timeoutMS + ")"); else { if ( (timeoutMS > REQUESTED_TIMEOUT_MS_MAX) && (timeoutMS != REQUESTED_TIMEOUT_MS_NEVER) ) { Log.w(TAG, "requestTimeoutMS: requested timeout " + timeoutMS + " exceeds maximum, setting to max (" + REQUESTED_TIMEOUT_MS_MAX + ")"); timeoutMS = REQUESTED_TIMEOUT_MS_MAX; } intentToHost.putExtra(EXTRA_REQUESTED_TIMEOUT, timeoutMS); } } /** * Used by: plugin EditActivity * <p/> * Indicates to host which bundle keys should be replaced. * * @param resultBundleToHost the bundle being returned to the host * @param listOfKeyNames which bundle keys to replace variables in when setting fires * @see #hostSupportsOnFireVariableReplacement(Bundle) */ public static void setVariableReplaceKeys(Bundle resultBundleToHost, String[] listOfKeyNames) { StringBuilder builder = new StringBuilder(); if (listOfKeyNames != null) { for (String keyName : listOfKeyNames) { if (keyName.contains(" ")) Log.w(TAG, "setVariableReplaceKeys: ignoring bad keyName containing space: " + keyName); else { if (builder.length() > 0) builder.append(' '); builder.append(keyName); } if (builder.length() > 0) resultBundleToHost.putString(BUNDLE_KEY_VARIABLE_REPLACE_STRINGS, builder.toString()); } } } /** * Used by: plugin FireReceiver * <p/> * Indicates to plugin whether the host will process variables which it passes back * * @param extrasFromHost intent extras from the intent received by the FireReceiver * @see #signalFinish(Context, Intent, int, Bundle) */ public static boolean hostSupportsVariableReturn(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_SETTING_RETURN_VARIABLES); } /** * Used by: plugin FireReceiver * <p/> * Tell the host that the plugin has finished execution. * <p/> * This should only be used if RESULT_CODE_PENDING was returned by FireReceiver.onReceive(). * * @param originalFireIntent the intent received from the host (via onReceive()) * @param resultCode level of success in performing the settings * @param vars any variables that the plugin wants to set in the host * @see #hostSupportsSynchronousExecution(Bundle) */ public static boolean signalFinish(Context context, Intent originalFireIntent, int resultCode, Bundle vars) { String errorPrefix = "signalFinish: "; boolean okFlag = false; String completionIntentString = (String) getExtraValueSafe(originalFireIntent, Setting.EXTRA_PLUGIN_COMPLETION_INTENT, String.class, "signalFinish"); if (completionIntentString != null) { Uri completionIntentUri = null; try { completionIntentUri = Uri.parse(completionIntentString); } // should only throw NullPointer but don't particularly trust it catch (Exception e) { Log.w(TAG, errorPrefix + "couldn't parse " + completionIntentString); } if (completionIntentUri != null) { try { Intent completionIntent = Intent.parseUri(completionIntentString, Intent.URI_INTENT_SCHEME); completionIntent.putExtra(EXTRA_RESULT_CODE, resultCode); if (vars != null) completionIntent.putExtra(EXTRA_VARIABLES_BUNDLE, vars); context.sendBroadcast(completionIntent); okFlag = true; } catch (URISyntaxException e) { Log.w(TAG, errorPrefix + "bad URI: " + completionIntentUri); } } } return okFlag; } /** * Check for a hint on the timeout value the host is using. * Used by: plugin FireReceiver. * Requires Tasker 4.7+ * * @param extrasFromHost intent extras from the intent received by the FireReceiver * @return timeoutMS the hosts timeout setting for the action or -1 if no hint is available. * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER */ public static int getHintTimeoutMS(Bundle extrasFromHost) { int timeoutMS = -1; Bundle hintsBundle = (Bundle) TaskerPlugin.getBundleValueSafe(extrasFromHost, EXTRA_HINTS_BUNDLE, Bundle.class, "getHintTimeoutMS"); if (hintsBundle != null) { Integer val = (Integer) getBundleValueSafe(hintsBundle, BUNDLE_KEY_HINT_TIMEOUT_MS, Integer.class, "getHintTimeoutMS"); if (val != null) timeoutMS = val; } return timeoutMS; } } // ----------------------------- CONDITION/EVENT PLUGIN ONLY --------------------------------- // public static class Condition { /** * Used by: plugin QueryReceiver * <p/> * Indicates to plugin whether the host will process variables which it passes back * * @param extrasFromHost intent extras from the intent received by the QueryReceiver * @see #addVariableBundle(Bundle, Bundle) */ public static boolean hostSupportsVariableReturn(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_CONDITION_RETURN_VARIABLES); } } // ----------------------------- EVENT PLUGIN ONLY --------------------------------- // public static class Event { public final static String PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY = BASE_KEY + ".MESSAGE_ID"; private final static String EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA = EXTRAS_PREFIX + "PASS_THROUGH_DATA"; /** * @param extrasFromHost intent extras from the intent received by the QueryReceiver * @see #addPassThroughData(Intent, Bundle) */ public static boolean hostSupportsRequestQueryDataPassThrough(Bundle extrasFromHost) { return hostSupports(extrasFromHost, EXTRA_HOST_CAPABILITY_REQUEST_QUERY_DATA_PASS_THROUGH); } /** * Specify a bundle of data (probably representing whatever change happened in the condition) * which will be included in the QUERY_CONDITION broadcast sent by the host for each * event instance of the plugin. * <p/> * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the * with the REQUEST_QUERY that caused it. * <p/> * Note that for security reasons it is advisable to also store a message ID with the bundle * which can be compared to known IDs on receipt. The host cannot validate the source of * REQUEST_QUERY intents so fake data may be passed. Replay attacks are also possible. * addPassThroughMesssageID() can be used to add an ID if the plugin doesn't wish to add it's * own ID to the pass through bundle. * <p/> * Note also that there are several situations where REQUEST_QUERY will not result in a * QUERY_CONDITION intent (e.g. event throttling by the host), so plugin-local data * indexed with a message ID needs to be timestamped and eventually timed-out. * <p/> * This function can be called multiple times, each time all keys in data will be added to * that of previous calls. * * @param requestQueryIntent intent being sent to the host * @param data the data to be passed-through * @see #hostSupportsRequestQueryDataPassThrough(Bundle) * @see #retrievePassThroughData(Intent) * @see #addPassThroughMessageID */ public static void addPassThroughData(Intent requestQueryIntent, Bundle data) { Bundle passThroughBundle = retrieveOrCreatePassThroughBundle(requestQueryIntent); passThroughBundle.putAll(data); } /** * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated * by a REQUEST_QUERY from the plugin. * <p/> * Note that if addPassThroughMessageID() was previously called, the data will contain an extra * key TaskerPlugin.Event.PASS_THOUGH_BUNDLE_MESSAGE_ID_KEY. * * @param queryConditionIntent QUERY_REQUEST sent from host * @return data previously added to the REQUEST_QUERY intent * @see #hostSupportsRequestQueryDataPassThrough(Bundle) * @see #addPassThroughData(Intent, Bundle) */ public static Bundle retrievePassThroughData(Intent queryConditionIntent) { return (Bundle) getExtraValueSafe( queryConditionIntent, EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA, Bundle.class, "retrievePassThroughData" ); } /** * Add a message ID to a REQUEST_QUERY intent which will then be included in the corresponding * QUERY_CONDITION broadcast sent by the host for each event instance of the plugin. * <p/> * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the * with the REQUEST_QUERY that caused it. It also allows the message to be verified * by the plugin to prevent e.g. replay attacks * * @param requestQueryIntent intent being sent to the host * @return an ID for the bundle so it can be identified and the caller verified when it is again received by the plugin * @see #hostSupportsRequestQueryDataPassThrough(Bundle) * @see #retrievePassThroughData(Intent) */ public static int addPassThroughMessageID(Intent requestQueryIntent) { Bundle passThroughBundle = retrieveOrCreatePassThroughBundle(requestQueryIntent); int id = getPositiveNonRepeatingRandomInteger(); passThroughBundle.putInt(PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY, id); return id; } /* * Retrieve the pass through data from a QUERY_REQUEST from the host which was generated * by a REQUEST_QUERY from the plugin. * * @param queryConditionIntent QUERY_REQUEST sent from host * @return the ID which was passed through by the host, or -1 if no ID was found * @see #hostSupportsRequestQueryDataPassThrough(Bundle) * @see #addPassThroughData(Intent,Bundle) */ public static int retrievePassThroughMessageID(Intent queryConditionIntent) { int toReturn = -1; Bundle passThroughData = Event.retrievePassThroughData(queryConditionIntent); if (passThroughData != null) { Integer id = (Integer) getBundleValueSafe( passThroughData, PASS_THROUGH_BUNDLE_MESSAGE_ID_KEY, Integer.class, "retrievePassThroughMessageID" ); if (id != null) toReturn = id; } return toReturn; } // internal use private static Bundle retrieveOrCreatePassThroughBundle(Intent requestQueryIntent) { Bundle passThroughBundle; if (requestQueryIntent.hasExtra(EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA)) passThroughBundle = requestQueryIntent.getBundleExtra(EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA); else { passThroughBundle = new Bundle(); requestQueryIntent.putExtra(EXTRA_REQUEST_QUERY_PASS_THROUGH_DATA, passThroughBundle); } return passThroughBundle; } } // ---------------------------------- HOST ----------------------------------------- // public static class Host { /** * Tell the plugin what capabilities the host support. This should be called when sending * intents to any EditActivity, FireReceiver or QueryReceiver. * * @param toPlugin the intent we're sending * @return capabilities one or more of the EXTRA_HOST_CAPABILITY_XXX flags */ public static Intent addCapabilities(Intent toPlugin, int capabilities) { return toPlugin.putExtra(EXTRA_HOST_CAPABILITIES, capabilities); } /** * Add an intent to the fire intent before it goes to the plugin FireReceiver, which the plugin * can use to signal when it is finished. Only use if @code{pluginWantsSychronousExecution} is true. * * @param fireIntent fire intent going to the plugin * @param completionIntent intent which will signal the host that the plugin is finished. * Implementation is host-dependent. */ public static void addCompletionIntent(Intent fireIntent, Intent completionIntent) { fireIntent.putExtra( Setting.EXTRA_PLUGIN_COMPLETION_INTENT, completionIntent.toUri(Intent.URI_INTENT_SCHEME) ); } /** * When a setting plugin is finished, it sends the host the intent which was passed to it * via @code{addCompletionIntent}. * * @param completionIntent intent returned from the plugin when it finished. * @return resultCode measure of plugin success, defaults to UNKNOWN */ public static int getSettingResultCode(Intent completionIntent) { Integer val = (Integer) getExtraValueSafe(completionIntent, Setting.EXTRA_RESULT_CODE, Integer.class, "getSettingResultCode"); return (val == null) ? Setting.RESULT_CODE_UNKNOWN : val; } /** * Extract a bundle of variables from an intent received from the FireReceiver. This * should be called if the host previously indicated to the plugin * that it supports setting variable return. * * @param resultExtras getResultExtras() from BroadcastReceiver:onReceive() * @return variables a bundle of variable name/value pairs * @see #addCapabilities(Intent, int) */ public static Bundle getVariablesBundle(Bundle resultExtras) { return (Bundle) getBundleValueSafe( resultExtras, EXTRA_VARIABLES_BUNDLE, Bundle.class, "getVariablesBundle" ); } /** * Inform a setting plugin of the timeout value the host is using. * * @param toPlugin the intent we're sending * @param timeoutMS the hosts timeout setting for the action. Note that this may differ from * that which the plugin requests. */ public static void addHintTimeoutMS(Intent toPlugin, int timeoutMS) { getHintsBundle(toPlugin, "addHintTimeoutMS").putInt(BUNDLE_KEY_HINT_TIMEOUT_MS, timeoutMS); } private static Bundle getHintsBundle(Intent intent, String funcName) { Bundle hintsBundle = (Bundle) getExtraValueSafe(intent, EXTRA_HINTS_BUNDLE, Bundle.class, funcName); if (hintsBundle == null) { hintsBundle = new Bundle(); intent.putExtra(EXTRA_HINTS_BUNDLE, hintsBundle); } return hintsBundle; } public static boolean haveRequestedTimeout(Bundle extrasFromPluginEditActivity) { return extrasFromPluginEditActivity.containsKey(Setting.EXTRA_REQUESTED_TIMEOUT); } public static int getRequestedTimeoutMS(Bundle extrasFromPluginEditActivity) { return (Integer) getBundleValueSafe( extrasFromPluginEditActivity, Setting.EXTRA_REQUESTED_TIMEOUT, Integer.class, "getRequestedTimeout" ) ; } public static String[] getSettingVariableReplaceKeys(Bundle fromPluginEditActivity) { String spec = (String) getBundleValueSafe(fromPluginEditActivity, Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS, String.class, "getSettingVariableReplaceKeys"); String[] replaceKeys = null; if (spec != null) replaceKeys = spec.split(" "); return replaceKeys; } public static boolean haveRelevantVariables(Bundle b) { return b.containsKey(BUNDLE_KEY_RELEVANT_VARIABLES); } public static void cleanRelevantVariables(Bundle b) { b.remove(BUNDLE_KEY_RELEVANT_VARIABLES); } public static void cleanHints(Bundle extras) { extras.remove(TaskerPlugin.EXTRA_HINTS_BUNDLE); } public static void cleanRequestedTimeout(Bundle extras) { extras.remove(Setting.EXTRA_REQUESTED_TIMEOUT); } public static void cleanSettingReplaceVariables(Bundle b) { b.remove(Setting.BUNDLE_KEY_VARIABLE_REPLACE_STRINGS); } } // ---------------------------------- HELPER FUNCTIONS -------------------------------- // private static Object getBundleValueSafe(Bundle b, String key, Class<?> expectedClass, String funcName) { Object value = null; if (b != null) { if (b.containsKey(key)) { Object obj = b.get(key); if (obj == null) Log.w(TAG, funcName + ": " + key + ": null value"); else if (obj.getClass() != expectedClass) Log.w(TAG, funcName + ": " + key + ": expected " + expectedClass.getClass().getName() + ", got " + obj.getClass().getName()); else value = obj; } } return value; } private static Object getExtraValueSafe(Intent i, String key, Class<?> expectedClass, String funcName) { return (i.hasExtra(key)) ? getBundleValueSafe(i.getExtras(), key, expectedClass, funcName) : null; } private static boolean hostSupports(Bundle extrasFromHost, int capabilityFlag) { Integer flags = (Integer) getBundleValueSafe(extrasFromHost, EXTRA_HOST_CAPABILITIES, Integer.class, "hostSupports"); return (flags != null) && ((flags & capabilityFlag) > 0) ; } public static int getPackageVersionCode(PackageManager pm, String packageName) { int code = -1; if (pm != null) { try { PackageInfo pi = pm.getPackageInfo(packageName, 0); if (pi != null) code = pi.versionCode; } catch (Exception e) { Log.e(TAG, "getPackageVersionCode: exception getting package info"); } } return code; } private static boolean variableNameIsLocal(String varName) { int digitCount = 0; int length = varName.length(); for (int x = 0; x < length; x++) { char ch = varName.charAt(x); if (Character.isUpperCase(ch)) return false; else if (Character.isDigit(ch)) digitCount++; } return digitCount != (varName.length() - 1); } // state tracking for random number sequence private static int[] lastRandomsSeen = null; private static int randomInsertPointer = 0; private static SecureRandom sr = null; /** * Generate a sequence of secure random positive integers which is guaranteed not to repeat * in the last 100 calls to this function. * * @return a random positive integer */ public static int getPositiveNonRepeatingRandomInteger() { // initialize on first call if (sr == null) { sr = new SecureRandom(); lastRandomsSeen = new int[RANDOM_HISTORY_SIZE]; for (int x = 0; x < lastRandomsSeen.length; x++) lastRandomsSeen[x] = -1; } int toReturn; do { // pick a number toReturn = sr.nextInt(Integer.MAX_VALUE); // check we havn't see it recently for (int seen : lastRandomsSeen) { if (seen == toReturn) { toReturn = -1; break; } } } while (toReturn == -1); // update history lastRandomsSeen[randomInsertPointer] = toReturn; randomInsertPointer = (randomInsertPointer + 1) % lastRandomsSeen.length; return toReturn; } }