//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;
}
}