Merge pull request #178 from sbosley/120427_sb_missed_call

Missed call notifications
pull/14/head
sbosley 12 years ago
commit e8bfb35cb4

@ -32,6 +32,8 @@
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- for push notifications -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- for missed call reminders -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!-- ============================================== Exported Permissions = -->
@ -198,6 +200,12 @@
<action android:name="com.todoroo.astrid.FLUSH_DETAILS" />
</intent-filter>
</receiver>
<receiver android:name="com.todoroo.astrid.calls.PhoneStateChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>
<!-- ======================================================== Services = -->
@ -270,6 +278,10 @@
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.calls.MissedCallActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
<!-- tags -->
<receiver android:name="com.todoroo.astrid.tags.TagsPlugin">
<intent-filter>

@ -0,0 +1,195 @@
package com.todoroo.astrid.calls;
import java.io.InputStream;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentUris;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.TextView;
import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.NotificationFragment.SnoozeDialog;
import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.reminders.SnoozeCallback;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.service.ThemeService;
public class MissedCallActivity extends Activity {
public static final String EXTRA_NUMBER = "number"; //$NON-NLS-1$
public static final String EXTRA_NAME = "name"; //$NON-NLS-1$
public static final String EXTRA_TIME = "time"; //$NON-NLS-1$
public static final String EXTRA_CONTACT_ID = "contactId"; //$NON-NLS-1$
private static final String PREF_IGNORE_PRESSES = "missedCallsIgnored"; //$NON-NLS-1$
// Prompt user to ignore all missed calls after this many ignore presses
private static final int IGNORE_PROMPT_COUNT = 3;
@Autowired private TaskService taskService;
private final OnClickListener dismissListener = new OnClickListener() {
@Override
public void onClick(View v) {
finish();
AndroidUtilities.callOverridePendingTransition(MissedCallActivity.this, 0, android.R.anim.fade_out);
}
};
private final OnClickListener ignoreListener = new OnClickListener() {
@Override
public void onClick(final View v) {
// Check for number of ignore presses
int ignorePresses = Preferences.getInt(PREF_IGNORE_PRESSES, 0);
ignorePresses++;
if (ignorePresses == IGNORE_PROMPT_COUNT) {
DialogUtilities.okCancelCustomDialog(MissedCallActivity.this,
getString(R.string.MCA_ignore_title),
getString(R.string.MCA_ignore_body),
R.string.MCA_ignore_all,
R.string.MCA_ignore_this,
0,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Preferences.setBoolean(R.string.p_field_missed_calls, false);
dismissListener.onClick(v);
}
},
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismissListener.onClick(v);
}
});
} else {
dismissListener.onClick(v);
}
Preferences.setInt(PREF_IGNORE_PRESSES, ignorePresses);
}
};
private String name;
private String number;
private String timeString;
private TextView returnCallButton;
private TextView callLaterButton;
private TextView ignoreButton;
private View dismissButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DependencyInjectionService.getInstance().inject(this);
setContentView(R.layout.missed_call_activity);
Intent intent = getIntent();
name = intent.getStringExtra(EXTRA_NAME);
number = intent.getStringExtra(EXTRA_NUMBER);
timeString = intent.getStringExtra(EXTRA_TIME);
long contactId = intent.getExtras().getLong(EXTRA_CONTACT_ID);
int color = ThemeService.getThemeColor();
returnCallButton = (TextView) findViewById(R.id.call_now);
callLaterButton = (TextView) findViewById(R.id.call_later);
ignoreButton = (TextView) findViewById(R.id.call_ignore);
dismissButton = findViewById(R.id.dismiss);
((TextView) findViewById(R.id.reminder_title))
.setText(getString(R.string.MCA_title,
TextUtils.isEmpty(name) ? number : name, timeString));
ImageView pictureView = ((ImageView) findViewById(R.id.contact_picture));
if (contactId >= 0) {
Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(getContentResolver(), uri);
if (input == null)
return;
pictureView.setImageBitmap(BitmapFactory.decodeStream(input));
pictureView.setVisibility(View.VISIBLE);
}
Resources r = getResources();
returnCallButton.setBackgroundColor(r.getColor(color));
callLaterButton.setBackgroundColor(r.getColor(color));
addListeners();
if (!Preferences.getBoolean(R.string.p_rmd_nagging, true)) {
findViewById(R.id.missed_calls_speech_bubble).setVisibility(View.GONE);
} else {
TextView dialogView = (TextView) findViewById(R.id.reminder_message);
dialogView.setText(Notifications.getRandomReminder(getResources().getStringArray(R.array.MCA_dialog_speech_options)));
}
}
private void addListeners() {
ignoreButton.setOnClickListener(ignoreListener);
dismissButton.setOnClickListener(dismissListener);
returnCallButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent call = new Intent(Intent.ACTION_CALL);
call.setData(Uri.parse("tel:" + number)); //$NON-NLS-1$
startActivity(call);
finish();
}
});
callLaterButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
final String taskTitle;
String dialogTitle;
if (TextUtils.isEmpty(name)) {
taskTitle = getString(R.string.MCA_task_title_no_name, number);
dialogTitle = getString(R.string.MCA_schedule_dialog_title, number);
} else {
taskTitle = getString(R.string.MCA_task_title_name, name, number);
dialogTitle = getString(R.string.MCA_schedule_dialog_title, name);
}
SnoozeDialog sd = new SnoozeDialog(MissedCallActivity.this, new SnoozeCallback() {
@Override
public void snoozeForTime(long time) {
Task newTask = new Task();
newTask.setValue(Task.TITLE, taskTitle);
newTask.setValue(Task.DUE_DATE, time);
taskService.save(newTask);
finish();
}
});
new AlertDialog.Builder(MissedCallActivity.this)
.setTitle(dialogTitle)
.setView(sd)
.setPositiveButton(android.R.string.ok, sd)
.setNegativeButton(android.R.string.cancel, null)
.show().setOwnerActivity(MissedCallActivity.this);
}
});
}
}

@ -0,0 +1,132 @@
package com.todoroo.astrid.calls;
import java.util.Date;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.timsu.astrid.R;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
@SuppressWarnings("nls")
public class PhoneStateChangedReceiver extends BroadcastReceiver {
private static final String PREF_LAST_INCOMING_NUMBER = "last_incoming_number";
private static final long WAIT_BEFORE_READ_LOG = 3000L;
@Override
public void onReceive(final Context context, Intent intent) {
if (!Preferences.getBoolean(R.string.p_field_missed_calls, true)) {
Preferences.clear(PREF_LAST_INCOMING_NUMBER);
return;
}
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {
String number = digitsOnly(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));
if (TextUtils.isEmpty(number))
return;
Preferences.setString(PREF_LAST_INCOMING_NUMBER, number);
} else if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {
final String lastNumber = Preferences.getStringValue(PREF_LAST_INCOMING_NUMBER);
if (TextUtils.isEmpty(lastNumber)) {
return;
}
Preferences.clear(PREF_LAST_INCOMING_NUMBER);
new Thread() {
@Override
public void run() {
AndroidUtilities.sleepDeep(WAIT_BEFORE_READ_LOG);
Cursor calls = context.getContentResolver().query(
Calls.CONTENT_URI,
null,
Calls.TYPE + " = ? AND " + Calls.NEW + " = ?",
new String[] { Integer.toString(Calls.MISSED_TYPE), "1" },
Calls.DATE + " DESC"
);
try {
if (calls.moveToFirst()) {
int numberIndex = calls.getColumnIndex(Calls.NUMBER);
String number = calls.getString(numberIndex);
// Sanity check for phone number match
// in case the phone logs haven't updated for some reaosn
if (!lastNumber.equals(digitsOnly(number))) {
return;
}
// If a lot of time has passed since the most recent missed call, ignore
// It could be the same person calling you back before you call them back,
// but if you answer this time, the missed call will still be in the database
// and will be processed again.
int dateIndex = calls.getColumnIndex(Calls.DATE);
long date = calls.getLong(dateIndex);
if (DateUtilities.now() - date > 2 * DateUtilities.ONE_MINUTE) {
return;
}
int nameIndex = calls.getColumnIndex(Calls.CACHED_NAME);
String name = calls.getString(nameIndex);
int timeIndex = calls.getColumnIndex(Calls.DATE);
long time = calls.getLong(timeIndex);
String timeString = DateUtilities.getTimeString(context, new Date(time));
long contactId = getContactIdFromNumber(context, number);
Intent missedCallIntent = new Intent(context, MissedCallActivity.class);
missedCallIntent.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
missedCallIntent.putExtra(MissedCallActivity.EXTRA_NAME, name);
missedCallIntent.putExtra(MissedCallActivity.EXTRA_TIME, timeString);
missedCallIntent.putExtra(MissedCallActivity.EXTRA_CONTACT_ID, contactId);
missedCallIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
context.startActivity(missedCallIntent);
}
} finally {
calls.close();
}
}
}.start();
}
}
private String digitsOnly(String number) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < number.length(); i++) {
char c = number.charAt(i);
if (Character.isDigit(c))
builder.append(c);
}
return builder.toString();
}
private long getContactIdFromNumber(Context context, String number) {
Uri contactUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor c = context.getContentResolver().query(contactUri, new String[] { ContactsContract.PhoneLookup._ID }, null, null, null);
try {
if (c.moveToFirst()) {
long id = c.getLong(c.getColumnIndex(ContactsContract.PhoneLookup._ID));
return id;
}
} finally {
c.close();
}
return -1;
}
}

@ -128,7 +128,7 @@ public class Notifications extends BroadcastReceiver {
// --- notification creation
/** @return a random reminder string */
static String getRandomReminder(String[] reminders) {
public static String getRandomReminder(String[] reminders) {
int next = ReminderService.random.nextInt(reminders.length);
String reminder = reminders[next];
return reminder;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -53,7 +53,7 @@
android:scaleType="fitCenter"
android:layout_gravity="right"
android:layout_marginBottom="10dip"
android:src="@android:drawable/ic_menu_close_clear_cancel"/>
android:src="@drawable/close_clear_cancel"/>
<Button
android:id="@+id/reminder_edit"
android:layout_width="fill_parent"

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reminder_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:orientation="vertical"
android:background="@drawable/reminder_dialog_background">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="5dip"
android:layout_marginRight="5dip"
android:layout_marginBottom="15dip"
android:layout_marginLeft="10dip">
<ImageView
android:id="@+id/contact_picture"
android:layout_marginTop="5dip"
android:layout_marginRight="10dip"
android:visibility="gone"
android:layout_width="46dip"
android:layout_height="46dip"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/reminder_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="3dip"
android:textSize="24sp"
android:textColor="@android:color/white"
android:layout_weight="1"
android:text="@string/MCA_title"/>
<ImageView
android:id="@+id/dismiss"
android:layout_width="25dip"
android:layout_height="25dip"
android:scaleType="fitCenter"
android:src="@drawable/close_clear_cancel"/>
</LinearLayout>
<TextView
android:id="@+id/call_now"
android:layout_width="fill_parent"
android:layout_height="35dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginBottom="10dip"
android:textColor="@android:color/white"
android:textSize="24sp"
android:gravity="center"
android:text="@string/MCA_return_call"/>
<TextView
android:id="@+id/call_later"
android:layout_width="fill_parent"
android:layout_height="35dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginBottom="10dip"
android:textColor="@android:color/white"
android:textSize="24sp"
android:gravity="center"
android:text="@string/MCA_add_task"/>
<TextView
android:id="@+id/call_ignore"
android:layout_width="fill_parent"
android:layout_height="35dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip"
android:layout_marginBottom="10dip"
android:textColor="@android:color/white"
android:textSize="24sp"
android:gravity="center"
android:text="@string/MCA_ignore"
android:background="#707070"/>
<include layout="@layout/astrid_speech_bubble" android:id="@+id/missed_calls_speech_bubble"/>
</LinearLayout>

@ -28,7 +28,7 @@
android:layout_width="25dip"
android:layout_height="25dip"
android:scaleType="fitCenter"
android:src="@android:drawable/ic_menu_close_clear_cancel"/>
android:src="@drawable/close_clear_cancel"/>
</LinearLayout>

@ -28,7 +28,7 @@
android:layout_width="25dip"
android:layout_height="25dip"
android:scaleType="fitCenter"
android:src="@android:drawable/ic_menu_close_clear_cancel"/>
android:src="@drawable/close_clear_cancel"/>
</LinearLayout>

@ -0,0 +1,9 @@
<?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"
android:gravity="center">
<include layout="@layout/astrid_missed_call_view"/>
</LinearLayout>

@ -33,7 +33,7 @@
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
android:src="@drawable/close_clear_cancel" />
<ImageButton
android:id="@+id/save"

@ -43,7 +43,7 @@
android:layout_height="wrap_content"
android:layout_marginRight="10dip"
android:background="@android:color/transparent"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
android:src="@drawable/close_clear_cancel" />
<ImageButton
android:id="@+id/save"

@ -45,6 +45,9 @@
<!-- default random reminder setting (in hours) -->
<string name="p_rmd_default_random_hours">notif_default_reminder</string>
<!-- field missed calls preference -->
<string name="p_field_missed_calls">field_missed_calls</string>
<string-array name="TEA_reminder_random_hours">
<!-- values (in hours) associated with items above. -->

@ -463,6 +463,58 @@
<!-- Button to disagree with EULA -->
<string name="InA_disagree">I Disagree</string>
<!-- ===================================================== MissedCallActivity == -->
<!-- Missed call: return call (%1$s -> caller, %2$s -> time of call)-->
<string name="MCA_title"> %1$s\ncalled at %2$s</string>
<!-- Missed call: return call -->
<string name="MCA_return_call">Call now</string>
<!-- Missed call: return call -->
<string name="MCA_add_task">Call later</string>
<!-- Missed call: return call -->
<string name="MCA_ignore">Ignore</string>
<!-- Missed call: dialog to ignore all missed calls title -->
<string name="MCA_ignore_title">Ignore all missed calls?</string>
<!-- Missed call: dialog to ignore all missed calls body -->
<string name="MCA_ignore_body">You\'ve ignored several missed calls. Should Astrid stop asking you about them?</string>
<!-- Missed call: dialog to ignore all missed calls ignore all button -->
<string name="MCA_ignore_all">Ignore all calls</string>
<!-- Missed call: dialog to ignore all missed calls ignore just this button -->
<string name="MCA_ignore_this">Ignore this call only</string>
<!-- Missed call: preference title -->
<string name="MCA_missed_calls_pref_title">Field missed calls</string>
<!-- Missed call: preference description -->
<string name="MCA_missed_calls_pref_desc">Astrid will notify you about missed calls and offer to remind you to call back</string>
<!-- Missed call: task title with name (%1$s -> name, %2$s -> number)-->
<string name="MCA_task_title_name">Call %1$s back at %2$s</string>
<!-- Missed call: task title no name (%s -> number)-->
<string name="MCA_task_title_no_name">Call %s back</string>
<!-- Missed call: schedule dialog title (%s -> name or number)-->
<string name="MCA_schedule_dialog_title">Call %s back in...</string>
<!-- Missed call speech bubble options -->
<string-array name="MCA_dialog_speech_options">
<item>It must be nice to be so popular!</item>
<item>Yay! People like you!</item>
<item>Make their day, give \'em a call!</item>
<item>Wouldn\'t you be happy if people called you back?</item>
<item>You can do it!</item>
<item>You can always send a text...</item>
</string-array>
<!-- ===================================================== HelpActivity == -->
<!-- Help: Button to get support from our website -->

@ -13,4 +13,8 @@
android:key="@string/p_use_contact_picker"
android:title="@string/EPr_use_contact_picker"
android:summary="@string/EPr_use_contact_picker_desc"/>
<CheckBoxPreference
android:key="@string/p_field_missed_calls"
android:title="@string/MCA_missed_calls_pref_title"
android:summary="@string/MCA_missed_calls_pref_desc" />
</PreferenceScreen>

@ -1014,7 +1014,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
AstridActivity activity = (AstridActivity) getActivity();
if (activity instanceof TaskListActivity && activity.fragmentLayout != AstridActivity.LAYOUT_DOUBLE || activity instanceof TaskEditActivity) {
item = menu.add(Menu.NONE, MENU_DISCARD_ID, 0, R.string.TEA_menu_discard);
item.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
item.setIcon(R.drawable.close_clear_cancel);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
item = menu.add(Menu.NONE, MENU_SAVE_ID, 0, R.string.TEA_menu_save);

@ -65,6 +65,21 @@ public class ThemeService {
return R.style.Theme_White_Blue;
}
public static int getThemeColor() {
int theme = getTheme();
switch(theme) {
case R.style.Theme:
case R.style.Theme_Transparent:
return R.color.blue_theme_color;
case R.style.Theme_White:
case R.style.Theme_TransparentWhite:
return R.color.red_theme_color;
case R.style.Theme_White_Blue:
default:
return R.color.dark_blue_theme_color;
}
}
public static int getEditDialogTheme() {
int themeSetting = ThemeService.getTheme();
int theme;

@ -203,6 +203,8 @@ public final class UpgradeService {
newVersionString(changeLog, "4.1.0 (4/16/12)", new String[] {
"Swipe between lists! Swipe left and right to move through your lists. Enable or adjust " +
"in Settings > Performance",
"Respond to or set reminders for missed calls. This feature requires a new permission to read " +
"the phone state.",
"Assign tasks to contacts without typing",
"Links to tasks in comments",
"Astrid.com sync improvements",

@ -117,12 +117,15 @@ public class ABTests {
private void initialize() { // Set up
//Calls to addTest go here
addTest(AB_TEST_SWIPE_ENABLED_KEY, new int[] { 1, 1 },
new int[] { 1, 1 }, new String[] { "swipe-lists-disabled", "swipe-lists-enabled" }); //$NON-NLS-1$//$NON-NLS-2$
new int[] { 9, 1 }, new String[] { "swipe-lists-disabled", "swipe-lists-enabled" }); //$NON-NLS-1$//$NON-NLS-2$
addTest(AB_TEST_CONTACTS_PICKER_ENABLED, new int[] { 1, 1 },
new int[] { 1, 1 }, new String[] { "contacts-disabled", "contacts-enabled" }); //$NON-NLS-1$//$NON-NLS-2$
new int[] { 9, 1 }, new String[] { "contacts-disabled", "contacts-enabled" }); //$NON-NLS-1$//$NON-NLS-2$
addTest(AB_TEST_MISSED_CALLS_ENABLED, new int[] { 1, 1 },
new int[] { 9, 1 }, new String[] { "missed-calls-disabled", "missed-calls-enabled" }); //$NON-NLS-1$//$NON-NLS-2$
}
public static final String AB_TEST_SWIPE_ENABLED_KEY = "swipeEnabled"; //$NON-NLS-1$
public static final String AB_TEST_CONTACTS_PICKER_ENABLED = "contactsEnabled"; //$NON-NLS-1$
public static final String AB_TEST_MISSED_CALLS_ENABLED = "missedCalls"; //$NON-NLS-1$
}

@ -55,6 +55,9 @@ public class AstridPreferences {
boolean contactsPickerEnabled = (ABChooser.readChoiceForTest(ABTests.AB_TEST_CONTACTS_PICKER_ENABLED) == 1);
Preferences.setIfUnset(prefs, editor, r, R.string.p_use_contact_picker, contactsPickerEnabled);
boolean missedCallsEnabled = (ABChooser.readChoiceForTest(ABTests.AB_TEST_MISSED_CALLS_ENABLED) == 1);
Preferences.setIfUnset(prefs, editor, r, R.string.p_field_missed_calls, missedCallsEnabled);
if ("white-blue".equals(Preferences.getStringValue(R.string.p_theme))) { //$NON-NLS-1$ migrate from when white-blue wasn't the default
Preferences.setString(R.string.p_theme, ThemeService.THEME_WHITE);
}

Loading…
Cancel
Save