Premium IAPs for Tasker and TeslaUnread

pull/384/head
Alex Baker 9 years ago
parent ff98c7a849
commit 3db5a847d4

@ -91,6 +91,7 @@ dependencies {
exclude group: 'com.android.support', module: 'support-v4'
}
googleplayCompile 'com.twofortyfouram:android-plugin-api-for-locale:[1.0.1,2.0['
googleplayCompile 'com.google.android.gms:play-services-location:8.4.0'
googleplayCompile 'com.google.android.gms:play-services-analytics:8.4.0'
googleplayCompile('com.google.apis:google-api-services-tasks:v1-rev41-1.21.0') {

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.tasks">
<!-- **************** -->
@ -85,6 +86,55 @@
<service android:name="com.google.android.gms.analytics.CampaignTrackingService" />
<activity
android:name=".locale.ui.activity.TaskerSettingsActivity"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Tasks"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustResize"/>
<!--
This is the "edit" Activity. Note that the host will reject plug-in
Activities for the following reasons:
- Missing "android:label=[...]"
- Missing "android:icon=[...]"
- The Activity isn't exported (e.g. android:exported="false")
- The Activity isn't enabled (e.g. android:enabled="false")
- The Activity requires permissions not available to the host
-->
<activity-alias
android:name="com.twofortyfouram.locale.example.setting.toast.ui.activity.PluginActivity"
android:exported="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:targetActivity=".locale.ui.activity.TaskerSettingsActivity"
tools:ignore="ExportedActivity">
<!-- this Intent filter allows the plug-in to be discovered by the host. -->
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter>
</activity-alias>
<!--
This is the "fire" BroadcastReceiver. Note that the host will reject plug-in
BroadcastReceivers for the following reasons:
- The BroadcastReceiver isn't exported (e.g. android:exported="false")
- The BroadcastReceiver isn't enabled (e.g. android:enabled="false")
- The BroadcastReceiver requires permissions not available to the host
- There are multiple BroadcastReceivers for com.twofortyfouram.locale.intent.action.FIRE_SETTING
-->
<receiver
android:name=".locale.receiver.FireReceiver"
android:exported="true"
android:process=":background"
tools:ignore="ExportedReceiver">
<!-- this Intent filter allows the plug-in to discovered by the host. -->
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
</receiver>
</application>
</manifest>

@ -1,15 +1,14 @@
package org.tasks.activities;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.billing.IabHelper;
import org.tasks.billing.IabResult;
import org.tasks.billing.Inventory;
import org.tasks.billing.Purchase;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.ActivityComponent;
@ -24,28 +23,19 @@ import javax.inject.Inject;
import timber.log.Timber;
public class DonationActivity extends InjectingAppCompatActivity implements IabHelper.OnIabSetupFinishedListener,
IabHelper.QueryInventoryFinishedListener, IabHelper.OnIabPurchaseFinishedListener,
IabHelper.OnConsumeFinishedListener, IabHelper.OnConsumeMultiFinishedListener {
public class DonationActivity extends InjectingAppCompatActivity implements IabHelper.OnIabPurchaseFinishedListener {
private static final int RC_REQUEST = 10001;
private IabHelper iabHelper;
private Inventory inventory;
private boolean itemSelected;
@Inject DialogBuilder dialogBuilder;
@Inject IabHelper iabHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iabHelper = new IabHelper(this, getString(R.string.gp_key));
if (BuildConfig.DEBUG) {
iabHelper.enableDebugLogging(true, BuildConfig.APPLICATION_ID);
}
iabHelper.startSetup(this);
final String[] donationValues = getValues();
dialogBuilder.newDialog()
.setTitle(R.string.select_amount)
@ -80,18 +70,7 @@ public class DonationActivity extends InjectingAppCompatActivity implements IabH
}
private void initiateDonation(int amount) {
final String sku = String.format("%03d", amount);
if (inventory != null && inventory.hasPurchase(sku)) {
iabHelper.consumeAsync(inventory.getPurchase(sku), new IabHelper.OnConsumeFinishedListener() {
@Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
DonationActivity.this.onConsumeFinished(purchase, result);
launchPurchaseFlow(sku);
}
});
} else {
launchPurchaseFlow(sku);
}
launchPurchaseFlow(String.format("%03d", amount));
}
private void launchPurchaseFlow(String sku) {
@ -106,42 +85,6 @@ public class DonationActivity extends InjectingAppCompatActivity implements IabH
return values.toArray(new String[values.size()]);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (iabHelper != null) {
iabHelper.dispose();
}
iabHelper = null;
}
@Override
public void onIabSetupFinished(IabResult result) {
if (iabHelper == null) {
return;
}
if (result.isSuccess()) {
Timber.d("IAB setup successful");
iabHelper.queryInventoryAsync(this);
} else {
error(result.getMessage());
}
}
@Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (iabHelper == null) {
return;
}
if (result.isFailure()) {
Timber.e("Query inventory failed: %s", result);
} else {
this.inventory = inventory;
iabHelper.consumeAsync(inventory.getAllPurchases(), this);
}
}
private void error(String message) {
Timber.e(message);
Toast.makeText(DonationActivity.this, message, Toast.LENGTH_LONG).show();
@ -150,45 +93,20 @@ public class DonationActivity extends InjectingAppCompatActivity implements IabH
@Override
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (iabHelper == null) {
return;
}
if (result.isSuccess()) {
Timber.d("Purchased %s", purchase);
iabHelper.consumeAsync(purchase, this);
} else {
error(result.getMessage());
}
}
@Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
Timber.d("Consumed %s", purchase);
} else {
Timber.e("Error consuming %s: %s", purchase, result);
}
finish();
}
@Override
public void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results) {
for (int i = 0 ; i < purchases.size() && i < results.size() ; i++) {
Timber.d("Consume %s: %s", purchases.get(i), results.get(i));
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == RC_REQUEST) {
String resultString = resultCode == RESULT_OK
? "RESULT_OK"
: resultCode == RESULT_CANCELED
? "RESULT_CANCELED"
: Integer.toString(resultCode);
Timber.d("onActivityResult(RC_REQUEST, %s, %s)", resultString, data);
finish();
iabHelper.handleActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
finish();
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}

@ -32,10 +32,16 @@ import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import org.json.JSONException;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Provides convenience methods for in-app billing. You can create one instance of this
@ -69,10 +75,11 @@ import java.util.List;
* @author Bruno Oliveira (Google)
*
*/
@Singleton
public class IabHelper {
// Is debug logging enabled?
boolean mDebugLog = false;
String mDebugTag = "IabHelper";
boolean mDebugLog = BuildConfig.DEBUG;
String mDebugTag = BuildConfig.APPLICATION_ID;
// Is setup done?
boolean mSetupDone = false;
@ -149,37 +156,13 @@ public class IabHelper {
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
public IabHelper(Context ctx, String base64PublicKey) {
@Inject
public IabHelper(@ForApplication Context ctx) {
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
mSignatureBase64 = ctx.getString(R.string.gp_key);
logDebug("IAB helper created.");
}
/**
* Enables or disable debug logging through LogCat.
*/
public void enableDebugLogging(boolean enable, String tag) {
checkNotDisposed();
mDebugLog = enable;
mDebugTag = tag;
}
public void enableDebugLogging(boolean enable) {
checkNotDisposed();
mDebugLog = enable;
}
/**
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
* when the setup process is complete.
@ -190,7 +173,7 @@ public class IabHelper {
*
* @param result The result of the setup process.
*/
public void onIabSetupFinished(IabResult result);
void onIabSetupFinished(IabResult result);
}
/**

@ -0,0 +1,79 @@
package org.tasks.locale.bundle;
import android.os.Bundle;
import org.tasks.BuildConfig;
import timber.log.Timber;
public final class PluginBundleValues {
public static final String BUNDLE_EXTRA_STRING_TITLE = "org.tasks.locale.STRING_TITLE";
public static final String BUNDLE_EXTRA_STRING_QUERY = "org.tasks.locale.STRING_QUERY";
public static final String BUNDLE_EXTRA_STRING_VALUES = "org.tasks.locale.STRING_VALUES";
public static final String BUNDLE_EXTRA_PREVIOUS_BUNDLE = "org.tasks.locale.PREVIOUS_BUNDLE";
public static final String BUNDLE_EXTRA_INT_VERSION_CODE = "org.tasks.locale.INT_VERSION_CODE";
public static boolean isBundleValid(final Bundle bundle) {
if (null == bundle) {
Timber.e("bundle is null");
return false;
}
if (isNullOrEmpty(bundle, BUNDLE_EXTRA_STRING_TITLE)) {
return false;
}
if (isNullOrEmpty(bundle, BUNDLE_EXTRA_STRING_QUERY)) {
return false;
}
if (bundle.containsKey(BUNDLE_EXTRA_STRING_VALUES) && bundle.getString(BUNDLE_EXTRA_STRING_VALUES).trim().length() == 0) {
Timber.e("Empty %s", BUNDLE_EXTRA_STRING_VALUES);
return false;
}
Integer version = bundle.getInt(BUNDLE_EXTRA_INT_VERSION_CODE, -1);
if (version == -1) {
Timber.e("invalid version code: %s", version);
return false;
}
return true;
}
private static boolean isNullOrEmpty(Bundle bundle, String key) {
String value = bundle.getString(key);
boolean isNullOrEmpty = value == null || value.trim().length() == 0;
if (isNullOrEmpty) {
Timber.e("Invalid %s", key);
}
return isNullOrEmpty;
}
public static Bundle generateBundle(String title, String query, String values) {
Bundle result = new Bundle();
result.putInt(BUNDLE_EXTRA_INT_VERSION_CODE, BuildConfig.VERSION_CODE);
result.putString(BUNDLE_EXTRA_STRING_TITLE, title);
result.putString(BUNDLE_EXTRA_STRING_QUERY, query);
if (values != null) {
result.putString(BUNDLE_EXTRA_STRING_VALUES, values);
}
return result;
}
public static String getTitle(Bundle bundle) {
return bundle.getString(BUNDLE_EXTRA_STRING_TITLE);
}
public static String getQuery(Bundle bundle) {
return bundle.getString(BUNDLE_EXTRA_STRING_QUERY);
}
public static String getValuesForNewTasks(Bundle bundle) {
return bundle.getString(BUNDLE_EXTRA_STRING_VALUES);
}
private PluginBundleValues() {
}
}

@ -0,0 +1,78 @@
package org.tasks.locale.receiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.tasks.locale.bundle.PluginBundleValues;
import timber.log.Timber;
public final class FireReceiver extends BroadcastReceiver {
protected boolean isBundleValid(final Bundle bundle) {
return PluginBundleValues.isBundleValid(bundle);
}
protected void firePluginSetting(final Context context, final Bundle bundle) {
context.sendBroadcast(new Intent() {{
setComponent(new ComponentName("org.tasks", "org.tasks.receivers.ListNotificationReceiver"));
putExtra("extra_filter_title", PluginBundleValues.getTitle(bundle));
putExtra("extra_filter_query", PluginBundleValues.getQuery(bundle));
String valuesForNewTasks = PluginBundleValues.getValuesForNewTasks(bundle);
if (valuesForNewTasks != null) {
putExtra("extra_filter_values", valuesForNewTasks);
}
}});
}
@Override
public final void onReceive(final Context context, final Intent intent) {
Timber.d("Received %s", intent); //$NON-NLS-1$
/*
* Note: It is OK if a host sends an ordered broadcast for plug-in
* settings. Such a behavior would allow the host to optionally block until the
* plug-in setting finishes.
*/
if (!com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING.equals(intent.getAction())) {
Timber.e("Intent action is not %s", com.twofortyfouram.locale.api.Intent.ACTION_FIRE_SETTING); //$NON-NLS-1$
return;
}
/*
* Ignore implicit intents, because they are not valid. It would be
* meaningless if ALL plug-in setting BroadcastReceivers installed were
* asked to handle queries not intended for them. Ideally this
* implementation here would also explicitly assert the class name as
* well, but then the unit tests would have trouble. In the end,
* asserting the package is probably good enough.
*/
if (!context.getPackageName().equals(intent.getPackage())
&& !new ComponentName(context, this.getClass().getName()).equals(intent
.getComponent())) {
Timber.e("Intent is not explicit"); //$NON-NLS-1$
return;
}
final Bundle bundle = intent
.getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);
if (null == bundle) {
Timber.e("%s is missing",
com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE); //$NON-NLS-1$
return;
}
if (!isBundleValid(bundle)) {
Timber.e("%s is invalid",
com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE); //$NON-NLS-1$
return;
}
firePluginSetting(context, bundle);
}
}

@ -0,0 +1,141 @@
package org.tasks.locale.ui.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.tasks.injection.InjectingAppCompatActivity;
import timber.log.Timber;
public abstract class AbstractFragmentPluginAppCompatActivity extends InjectingAppCompatActivity {
protected boolean mIsCancelled = false;
/* package */ static boolean isLocalePluginIntent(final Intent intent) {
final String action = intent.getAction();
return com.twofortyfouram.locale.api.Intent.ACTION_EDIT_CONDITION.equals(action)
|| com.twofortyfouram.locale.api.Intent.ACTION_EDIT_SETTING.equals(action);
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (isLocalePluginIntent(getIntent())) {
final Bundle previousBundle = getPreviousBundle();
Timber.d("Creating Activity with Intent=%s, savedInstanceState=%s, EXTRA_BUNDLE=%s",
getIntent(), savedInstanceState, previousBundle); //$NON-NLS-1$
}
}
@Override
protected void onPostCreate(final Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
if (isLocalePluginIntent(getIntent())) {
if (null == savedInstanceState) {
final Bundle previousBundle = getPreviousBundle();
final String previousBlurb = getPreviousBlurb();
if (null != previousBundle && null != previousBlurb) {
onPostCreateWithPreviousResult(previousBundle, previousBlurb);
}
}
}
}
@Override
public void finish() {
if (isLocalePluginIntent(getIntent())) {
if (!mIsCancelled) {
final Bundle resultBundle = getResultBundle();
if (null != resultBundle) {
String blurb = getResultBlurb(resultBundle);
Intent resultIntent = new Intent();
resultIntent.putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE,
resultBundle);
resultIntent.putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB,
blurb);
setResult(RESULT_OK, resultIntent);
}
}
}
super.finish();
}
/**
* @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_BUNDLE EXTRA_BUNDLE} that was
* previously saved to the host and subsequently passed back to this Activity for further
* editing. Internally, this method relies on {@link #isBundleValid(Bundle)}. If
* the bundle exists but is not valid, this method will return null.
*/
public final Bundle getPreviousBundle() {
final Bundle bundle = getIntent().getBundleExtra(
com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);
if (null != bundle) {
if (isBundleValid(bundle)) {
return bundle;
}
}
return null;
}
/**
* @return The {@link com.twofortyfouram.locale.api.Intent#EXTRA_STRING_BLURB
* EXTRA_STRING_BLURB} that was
* previously saved to the host and subsequently passed back to this Activity for further
* editing.
*/
public final String getPreviousBlurb() {
return getIntent().getStringExtra(
com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB);
}
/**
* <p>Validates the Bundle, to ensure that a malicious application isn't attempting to pass
* an invalid Bundle.</p>
*
* @param bundle The plug-in's Bundle previously returned by the edit
* Activity. {@code bundle} should not be mutated by this method.
* @return true if {@code bundle} is valid for the plug-in.
*/
public abstract boolean isBundleValid(final Bundle bundle);
/**
* Plug-in Activity lifecycle callback to allow the Activity to restore
* state for editing a previously saved plug-in instance. This callback will
* occur during the onPostCreate() phase of the Activity lifecycle.
* <p>{@code bundle} will have been
* validated by {@link #isBundleValid(Bundle)} prior to this
* method being called. If {@link #isBundleValid(Bundle)} returned false, then this
* method will not be called. This helps ensure that plug-in Activity subclasses only have to
* worry about bundle validation once, in the {@link #isBundleValid(Bundle)}
* method.</p>
* <p>Note this callback only occurs the first time the Activity is created, so it will not be
* called
* when the Activity is recreated (e.g. {@code savedInstanceState != null}) such as after a
* configuration change like a screen rotation.</p>
*
* @param previousBundle Previous bundle that the Activity saved.
* @param previousBlurb Previous blurb that the Activity saved
*/
public abstract void onPostCreateWithPreviousResult(
final Bundle previousBundle, final String previousBlurb);
/**
* @return Bundle for the plug-in or {@code null} if a valid Bundle cannot
* be generated.
*/
public abstract Bundle getResultBundle();
/**
* @param bundle Valid bundle for the component.
* @return Blurb for {@code bundle}.
*/
public abstract String getResultBlurb(final Bundle bundle);
}

@ -0,0 +1,207 @@
package org.tasks.locale.ui.activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import org.tasks.R;
import org.tasks.activities.FilterSelectionActivity;
import org.tasks.injection.ActivityComponent;
import org.tasks.locale.bundle.PluginBundleValues;
import org.tasks.preferences.ActivityPreferences;
import org.tasks.ui.MenuColorizer;
import java.util.Set;
import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
public final class TaskerSettingsActivity extends AbstractFragmentPluginAppCompatActivity {
private static final int REQUEST_SELECT_FILTER = 10124;
@Bind(R.id.toolbar) Toolbar toolbar;
@Inject ActivityPreferences preferences;
private Bundle previousBundle;
private String title;
private String query;
private String values;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
preferences.applyThemeAndStatusBarColor();
setContentView(R.layout.tasker_settings);
ButterKnife.bind(this);
if (savedInstanceState != null) {
previousBundle = savedInstanceState.getParcelable(PluginBundleValues.BUNDLE_EXTRA_PREVIOUS_BUNDLE);
title = savedInstanceState.getString(PluginBundleValues.BUNDLE_EXTRA_STRING_TITLE);
query = savedInstanceState.getString(PluginBundleValues.BUNDLE_EXTRA_STRING_QUERY);
values = savedInstanceState.getString(PluginBundleValues.BUNDLE_EXTRA_STRING_VALUES);
updateActivity();
}
setSupportActionBar(toolbar);
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(true);
Drawable drawable = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_close_24dp));
DrawableCompat.setTint(drawable, getResources().getColor(android.R.color.white));
supportActionBar.setHomeAsUpIndicator(drawable);
supportActionBar.setDisplayShowTitleEnabled(false);
}
}
@OnClick(R.id.filter_selection)
void selectFilter() {
startActivityForResult(new Intent(TaskerSettingsActivity.this, FilterSelectionActivity.class), REQUEST_SELECT_FILTER);
}
@Override
public void onPostCreateWithPreviousResult(final Bundle previousBundle, final String previousBlurb) {
this.previousBundle = previousBundle;
title = PluginBundleValues.getTitle(previousBundle);
query = PluginBundleValues.getQuery(previousBundle);
updateActivity();
}
@Override
public boolean isBundleValid(final Bundle bundle) {
return PluginBundleValues.isBundleValid(bundle);
}
@Override
public Bundle getResultBundle() {
return PluginBundleValues.generateBundle(title, query, values);
}
@Override
public String getResultBlurb(final Bundle bundle) {
return PluginBundleValues.getTitle(bundle);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.tasker_menu, menu);
MenuColorizer.colorMenu(this, menu, getResources().getColor(android.R.color.white));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_save:
finish();
break;
case android.R.id.home:
if (equalBundles(getResultBundle(), previousBundle)) {
cancel();
} else {
new AlertDialog.Builder(this, R.style.TasksDialog)
.setMessage(R.string.discard_changes)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
cancel();
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
}
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
private void cancel() {
mIsCancelled = true;
finish();
}
private boolean equalBundles(Bundle one, Bundle two) {
if (one == null) {
return two == null;
}
if (two == null) {
return false;
}
if(one.size() != two.size())
return false;
Set<String> setOne = one.keySet();
Object valueOne;
Object valueTwo;
for(String key : setOne) {
valueOne = one.get(key);
valueTwo = two.get(key);
if(valueOne instanceof Bundle && valueTwo instanceof Bundle &&
!equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
return false;
}
else if(valueOne == null) {
if(valueTwo != null || !two.containsKey(key))
return false;
}
else if(!valueOne.equals(valueTwo))
return false;
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_SELECT_FILTER) {
if (resultCode == RESULT_OK) {
title = data.getStringExtra("extra_filter_name");
query = data.getStringExtra("extra_filter_query");
values = data.getStringExtra("extra_filter_values");
updateActivity();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(PluginBundleValues.BUNDLE_EXTRA_PREVIOUS_BUNDLE, previousBundle);
outState.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_TITLE, title);
outState.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_QUERY, query);
outState.putString(PluginBundleValues.BUNDLE_EXTRA_STRING_VALUES, values);
}
private void updateActivity() {
((TextView) findViewById(R.id.text_view)).setText(title);
}
@Override
public void inject(ActivityComponent component) {
component.inject(this);
}
}

@ -0,0 +1,88 @@
package org.tasks.receivers;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import com.todoroo.andlib.sql.Query;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.tasks.BuildConfig;
import org.tasks.analytics.Tracker;
import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingBroadcastReceiver;
import org.tasks.preferences.DefaultFilterProvider;
import javax.inject.Inject;
import javax.inject.Singleton;
import timber.log.Timber;
@Singleton
public class TeslaUnreadReceiver extends InjectingBroadcastReceiver {
private static final String TESLA_URI = "content://com.teslacoilsw.notifier/unread_count";
private static final String TESLA_TAG = BuildConfig.APPLICATION_ID + "/com.todoroo.astrid.activity.TaskListActivity";
private final Context context;
private final DefaultFilterProvider defaultFilterProvider;
private final TaskDao taskDao;
private final Tracker tracker;
private boolean enabled;
@Inject
public TeslaUnreadReceiver(@ForApplication Context context, DefaultFilterProvider defaultFilterProvider,
TaskDao taskDao, Tracker tracker) {
this.context = context;
this.defaultFilterProvider = defaultFilterProvider;
this.taskDao = taskDao;
this.tracker = tracker;
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
try {
Filter defaultFilter = defaultFilterProvider.getDefaultFilter();
String query = PermaSql.replacePlaceholders(defaultFilter.getSqlQuery());
int count = taskDao.count(Query.select(Task.ID).withQueryTemplate(query));
ContentValues contentValues = new ContentValues();
contentValues.put("tag", TESLA_TAG);
contentValues.put("count", count);
context.getContentResolver().insert(Uri.parse(TESLA_URI), contentValues);
} catch (IllegalArgumentException ex) {
/* Fine, TeslaUnread is not installed. */
} catch (Exception e) {
Timber.e(e, e.getMessage());
tracker.reportException(e);
}
}
@Override
protected void inject(BroadcastComponent component) {
component.inject(this);
}
public void setEnabled(boolean newValue) {
try {
if (newValue) {
context.registerReceiver(this, new IntentFilter(AstridApiConstants.BROADCAST_EVENT_REFRESH));
} else if (enabled) {
context.unregisterReceiver(this);
}
enabled = newValue;
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
}
}

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar"/>
<LinearLayout
android:id="@+id/filter_selection"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:orientation="horizontal"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="100"
android:clickable="false"
android:text="@string/filter"
android:textColor="@android:color/black"
android:textSize="18sp" />
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="false"
android:gravity="end"
android:hint="@string/select_filter"
android:textColorHint="@android:color/darker_gray"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_save"
android:title="@string/save"
android:icon="@drawable/ic_save_24dp"
app:showAsAction="always"/>
</menu>

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="gp_key">MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk8mXRE3dDXwtinUILCEzKjov2rxs3kZbLRzNrcjFWXpG9OEsUzRGLzqEN+WwibVuMRpZLj/+IxbU2sJWq/M0q+90rOhmXn46ZPeNyr77IqX2pWKIAWpzBoWq/mshRwtm9m1FIiGdBNlXrhSE7u3TGB5FuEuuSqKWvWzxeqQ7fHmlM04Lqrh1mN3FaMne8rWv+DWVHDbLrtnXBuC36glOAj17HxrzaE2v6Pv7Df3QefJ3rM1+0fAp/5jNInaP0qHAlG8WTbUmDShQ5kG3urbv3HLByyx6TSqhmNudXUK/6TusvIj50OptAG7x+UFYf956dD3diXhY3yoICvyFWx1sNwIDAQAB</string>
<string name="sku_tasker">tasker</string>
<string name="sku_tesla_unread">tesla_unread</string>
</resources>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="select_filter">Select Filter</string>
<string name="filter">Filter</string>
<string name="discard_changes">Discard changes?</string>
</resources>

@ -27,7 +27,6 @@ import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.core.CustomFilterActivity;
import org.tasks.R;
import org.tasks.activities.DonationActivity;
import org.tasks.filters.FilterCounter;
import org.tasks.filters.FilterProvider;
import org.tasks.filters.NavigationDrawerAction;
@ -280,11 +279,6 @@ public class FilterAdapter extends ArrayAdapter<FilterListItem> {
R.drawable.ic_help_24dp,
new Intent(activity, HelpAndFeedbackActivity.class),
0));
add(new NavigationDrawerAction(
activity.getResources().getString(R.string.TLA_menu_donate),
R.drawable.ic_attach_money_24dp,
new Intent(activity, DonationActivity.class),
0));
}
notifyDataSetChanged();

@ -1,5 +1,7 @@
package org.tasks;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.util.Log;
import com.todoroo.astrid.dao.Database;
@ -20,18 +22,24 @@ import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.tags.TagService;
import org.tasks.analytics.Tracker;
import org.tasks.billing.IabHelper;
import org.tasks.billing.IabResult;
import org.tasks.billing.Inventory;
import org.tasks.filters.FilterCounter;
import org.tasks.injection.ApplicationComponent;
import org.tasks.injection.InjectingApplication;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.TeslaUnreadReceiver;
import org.tasks.sync.SyncThrottle;
import javax.inject.Inject;
import timber.log.Timber;
import static org.tasks.preferences.BasicPreferences.toggleTasker;
@SuppressWarnings("UnusedDeclaration")
public class Tasks extends InjectingApplication {
public class Tasks extends InjectingApplication implements IabHelper.OnIabSetupFinishedListener, IabHelper.QueryInventoryFinishedListener {
@Inject Database database;
@Inject TaskDao taskDao;
@ -54,6 +62,8 @@ public class Tasks extends InjectingApplication {
@Inject SyncThrottle syncThrottle;
@Inject Preferences preferences;
@Inject Tracker tracker;
@Inject TeslaUnreadReceiver teslaUnreadReceiver;
@Inject IabHelper iabHelper;
@Override
public void onCreate() {
@ -66,6 +76,41 @@ public class Tasks extends InjectingApplication {
}
tracker.setTrackingEnabled(preferences.isTrackingEnabled());
iabHelper.startSetup(this);
teslaUnreadReceiver.setEnabled(preferences.getBoolean(R.string.p_tesla_unread_enabled, false));
try {
toggleTasker(this, preferences.getBoolean(R.string.p_tasker_enabled, false));
} catch(Exception e) {
Timber.e(e, e.getMessage());
tracker.reportException(e);
}
}
@Override
public void onIabSetupFinished(IabResult result) {
if (result.isSuccess()) {
Timber.d("IAB setup successful");
iabHelper.queryInventoryAsync(this);
} else {
Timber.e(result.getMessage());
}
}
@Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (result.isFailure()) {
Timber.e("Query inventory failed: %s", result);
} else {
if (inventory.hasPurchase(getString(R.string.sku_tesla_unread))) {
preferences.setBoolean(R.string.p_purchased_tesla_unread, false);
}
if (inventory.hasPurchase(getString(R.string.sku_tasker))) {
preferences.setBoolean(R.string.p_purchased_tasker, false);
}
}
}
private static class ErrorReportingTree extends Timber.Tree {

@ -37,6 +37,7 @@ import org.tasks.activities.ResetPreferencesActivity;
import org.tasks.activities.SortActivity;
import org.tasks.activities.TimePickerActivity;
import org.tasks.files.FileExplore;
import org.tasks.locale.ui.activity.TaskerSettingsActivity;
import org.tasks.preferences.AppearancePreferences;
import org.tasks.preferences.BackupPreferences;
import org.tasks.preferences.BasicPreferences;
@ -151,4 +152,6 @@ public interface ActivityComponent {
void inject(AppearancePreferences appearancePreferences);
void inject(GtasksPreferences gtasksPreferences);
void inject(TaskerSettingsActivity taskerSettingsActivity);
}

@ -15,6 +15,7 @@ import org.tasks.receivers.ListNotificationReceiver;
import org.tasks.receivers.MyPackageReplacedReceiver;
import org.tasks.receivers.RefreshReceiver;
import org.tasks.receivers.TaskNotificationReceiver;
import org.tasks.receivers.TeslaUnreadReceiver;
import dagger.Subcomponent;
@ -48,4 +49,6 @@ public interface BroadcastComponent {
void inject(BootCompletedReceiver bootCompletedReceiver);
void inject(TasksWidget tasksWidget);
void inject(TeslaUnreadReceiver teslaUnreadReceiver);
}

@ -1,28 +1,41 @@
package org.tasks.preferences;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.preference.Preference;
import com.todoroo.astrid.core.OldTaskPreferences;
import com.todoroo.astrid.reminders.ReminderPreferences;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.billing.IabHelper;
import org.tasks.billing.IabResult;
import org.tasks.billing.Purchase;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.receivers.TeslaUnreadReceiver;
import javax.inject.Inject;
public class BasicPreferences extends InjectingPreferenceActivity {
public class BasicPreferences extends InjectingPreferenceActivity implements IabHelper.OnIabPurchaseFinishedListener {
private static final String EXTRA_RESULT = "extra_result";
private static final int RC_PREFS = 10001;
private static final int REQUEST_PURCHASE_TESLA_UNREAD = 10002;
private static final int REQUEST_PURCHASE_TASKER = 10003;
private Bundle result;
@Inject Tracker tracker;
@Inject TeslaUnreadReceiver teslaUnreadReceiver;
@Inject Preferences preferences;
@Inject IabHelper iabHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -38,6 +51,36 @@ public class BasicPreferences extends InjectingPreferenceActivity {
addPreferencesFromResource(R.xml.preferences_addons);
addPreferencesFromResource(R.xml.preferences_privacy);
findPreference(getString(R.string.p_tesla_unread_enabled)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
if ((boolean) newValue && !preferences.getBoolean(R.string.p_purchased_tesla_unread, BuildConfig.DEBUG)) {
iabHelper.launchPurchaseFlow(BasicPreferences.this, getString(R.string.sku_tesla_unread), REQUEST_PURCHASE_TESLA_UNREAD, BasicPreferences.this);
} else {
teslaUnreadReceiver.setEnabled((boolean) newValue);
return true;
}
}
return false;
}
});
findPreference(getString(R.string.p_tasker_enabled)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (newValue != null) {
if ((boolean) newValue && !preferences.getBoolean(R.string.p_purchased_tasker, BuildConfig.DEBUG)) {
iabHelper.launchPurchaseFlow(BasicPreferences.this, getString(R.string.sku_tasker), REQUEST_PURCHASE_TASKER, BasicPreferences.this);
} else {
toggleTasker(BasicPreferences.this, (boolean) newValue);
return true;
}
}
return false;
}
});
findPreference(getString(R.string.p_collect_statistics)).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -54,6 +97,12 @@ public class BasicPreferences extends InjectingPreferenceActivity {
setupActivity(R.string.EPr_manage_header, OldTaskPreferences.class);
}
public static void toggleTasker(Context context, boolean enabled) {
ComponentName componentName = new ComponentName(context, "com.twofortyfouram.locale.example.setting.toast.ui.activity.PluginActivity");
int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
context.getPackageManager().setComponentEnabledSetting(componentName, state, PackageManager.DONT_KILL_APP);
}
private void setupActivity(int key, final Class<?> target) {
findPreference(getString(key)).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@ -79,6 +128,8 @@ public class BasicPreferences extends InjectingPreferenceActivity {
putExtras(result);
}});
}
} else if (requestCode == REQUEST_PURCHASE_TESLA_UNREAD || requestCode == REQUEST_PURCHASE_TASKER) {
iabHelper.handleActivityResult(requestCode, resultCode, data);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
@ -88,4 +139,22 @@ public class BasicPreferences extends InjectingPreferenceActivity {
public void inject(ActivityComponent component) {
component.inject(this);
}
@Override
public void onIabPurchaseFinished(IabResult result, final Purchase info) {
if (result.isSuccess()) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (info.getSku().equals(getString(R.string.sku_tasker))) {
preferences.setBoolean(R.string.p_purchased_tasker, true);
findPreference(getString(R.string.p_tasker_enabled)).setEnabled(true);
} else if (info.getSku().equals(getString(R.string.sku_tesla_unread))) {
preferences.setBoolean(R.string.p_purchased_tesla_unread, true);
findPreference(getString(R.string.p_tesla_unread_enabled)).setEnabled(true);
}
}
});
}
}
}

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

@ -284,5 +284,9 @@
<string name="tracking_category_preferences">Preferences</string>
<string name="tracking_action_set">Set</string>
<string name="p_tesla_unread_enabled">tesla_unread_enabled</string>
<string name="p_tasker_enabled">tasker_enabled</string>
<string name="p_purchased_tesla_unread">purchased_tesla_unread</string>
<string name="p_purchased_tasker">purchased_tasker</string>
</resources>

@ -834,7 +834,7 @@ File %1$s contained %2$s.\n\n
<string name="show_completed">Show completed</string>
<string name="reverse">Reverse</string>
<string name="task_count">%s Tasks</string>
<string name="get_plugins">Get Plug-ins</string>
<string name="get_plugins">In-app purchases</string>
<string name="no_application_found">No application found to open attachment</string>
<string name="add_attachment">Add attachment</string>
<string name="high_priority">High</string>
@ -859,6 +859,10 @@ File %1$s contained %2$s.\n\n
<string name="no_title">(No title)</string>
<string name="back_button_saves_task">Back button saves task</string>
<string name="default_list">Default list</string>
<string name="plugin_description">Tasks is an open source project maintained by a single developer. Niche features are offered as in-app purchases in order to support their development.</string>
<string name="tesla_unread_description">Display a badge for the number of active tasks in your default list. Requires TeslaUnread for Nova Launcher</string>
<string name="tasker_description">Context-aware list notifications. Requires Tasker or Locale</string>
<string name="donate_summary">Donations are greatly appreciated</string>
<string-array name="sync_SPr_interval_entries">
<!-- sync_SPr_interval_entries: Synchronization Intervals -->

@ -5,12 +5,28 @@
android:title="@string/get_plugins">
<Preference
android:title="Tasker/Locale">
android:selectable="false"
android:summary="@string/plugin_description"/>
<Preference
android:title="@string/TLA_menu_donate"
android:summary="@string/donate_summary">
<intent
android:action="android.intent.action.VIEW"
android:data="market://details?id=org.tasks.locale" />
android:targetPackage="org.tasks"
android:targetClass="org.tasks.activities.DonationActivity" />
</Preference>
<SwitchPreference
android:key="@string/p_tesla_unread_enabled"
android:title="TeslaUnread"
android:summary="@string/tesla_unread_description"/>
<SwitchPreference
android:key="@string/p_tasker_enabled"
android:title="Tasker/Locale"
android:summary="@string/tasker_description"/>
</PreferenceCategory>
</PreferenceScreen>

@ -9,7 +9,7 @@
android:data="http://tasks.org/privacy.html" />
</Preference>
<com.todoroo.astrid.ui.MultilineCheckboxPreference
<SwitchPreference
android:defaultValue="true"
android:key="@string/p_collect_statistics"
android:summary="@string/send_anonymous_statistics_summary"

Loading…
Cancel
Save