//package com.yourcompany.yourcondition; //package com.yourcompany.yoursetting; package com.eveningoutpost.dexdrip.localeTasker; // 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 java.net.URISyntaxException; import java.security.SecureRandom; import java.util.regex.Pattern; 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; 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. * * 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. * * 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 * * Add a bundle of variable name/value pairs. * * 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. * * Should *only* be set when the BroadcastReceiver result code indicates a failure. * * 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. * * 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. * * Indicates to plugin that host will replace variables in specified bundle keys. * * 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. * * Description as above. * * 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() != null) ? editActivity.getCallingActivity().getPackageName() : "null"; // 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. * * 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). * * 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 * * 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 * * 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 * * Tell the host that the plugin has finished execution. * * 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 * * 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. * * The minimal purpose is to enable the plugin to associate a QUERY_CONDITION to the * with the REQUEST_QUERY that caused it. * * 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. * * 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. * * 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. * * 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 previouly 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. * * 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 a guaranteed non-repeating within 100 calls message ID * @see #hostSupportsRequestQueryDataPassThrough(Bundle) * @see #retrievePassThroughData(Intent) * @return an ID for the bundle so it can be identified and the caller verified when it is again received by the plugin * */ 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. * @see #REQUESTED_TIMEOUT_MS_NONE, REQUESTED_TIMEOUT_MS_MAX, REQUESTED_TIMEOUT_MS_NEVER */ 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++; } if ( digitCount == ( varName.length() - 1 ) ) return false; return true; } // 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; } }