added combined notifications settings - you can now use separate notifications for mentions

This commit is contained in:
Mariotaku Lee 2015-10-10 21:12:03 +08:00
parent 29ffac03ef
commit 3e9e91e871
15 changed files with 573 additions and 47 deletions

View File

@ -132,6 +132,10 @@ public interface TwidereConstants extends SharedPreferenceConstants, IntentConst
String QUERY_PARAM_RECIPIENT_ID = "recipient_id";
String QUERY_PARAM_READ_POSITION = "param_read_position";
String QUERY_PARAM_READ_POSITIONS = "param_read_positions";
String QUERY_PARAM_LIMIT = "limit";
String QUERY_PARAM_EXTRA_ID = "extra_id";
String QUERY_PARAM_TIMESTAMP = "timestamp";
String QUERY_PARAM_FROM_NOTIFICATION = "from_notification";
String DEFAULT_PROTOCOL = PROTOCOL_HTTPS;

View File

@ -310,5 +310,5 @@ public interface SharedPreferenceConstants {
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = true)
String KEY_BUG_REPORTS = "bug_reports";
@Preference(type = BOOLEAN, hasDefault = true, defaultBoolean = true)
String COMBINED_NOTIFICATIONS = "combined_notifications";
String KEY_COMBINED_NOTIFICATIONS = "combined_notifications";
}

View File

@ -48,6 +48,7 @@ import edu.tsinghua.hotmobi.model.LatLng;
import edu.tsinghua.hotmobi.model.LinkEvent;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.NetworkEvent;
import edu.tsinghua.hotmobi.model.NotificationEvent;
import edu.tsinghua.hotmobi.model.RefreshEvent;
import edu.tsinghua.hotmobi.model.ScrollRecord;
import edu.tsinghua.hotmobi.model.SessionEvent;
@ -95,6 +96,8 @@ public class HotMobiLogger {
return "scroll";
} else if (event instanceof BatteryRecord) {
return "battery";
} else if (event instanceof NotificationEvent) {
return "notification";
}
throw new UnsupportedOperationException("Unknown event type " + event);
}

View File

@ -0,0 +1,171 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.tsinghua.hotmobi.model;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
import java.util.TimeZone;
import edu.tsinghua.hotmobi.HotMobiLogger;
/**
* Created by mariotaku on 15/10/10.
*/
@ParcelablePlease
@JsonObject
public class NotificationEvent extends BaseEvent implements Parcelable {
public static final Creator<NotificationEvent> CREATOR = new Creator<NotificationEvent>() {
@Override
public NotificationEvent createFromParcel(Parcel in) {
return new NotificationEvent(in);
}
@Override
public NotificationEvent[] newArray(int size) {
return new NotificationEvent[size];
}
};
@ParcelableThisPlease
@JsonField(name = "item_id")
long itemId;
@ParcelableThisPlease
@JsonField(name = "account_id")
long accountId;
@ParcelableThisPlease
@JsonField(name = "type")
String type;
@ParcelableThisPlease
@JsonField(name = "action", typeConverter = Action.NotificationActionConverter.class)
Action action;
public NotificationEvent() {
}
public NotificationEvent(Parcel in) {
super(in);
NotificationEventParcelablePlease.readFromParcel(this, in);
}
public Action getAction() {
return action;
}
public void setAction(Action action) {
this.action = action;
}
@Override
public int describeContents() {
return 0;
}
public long getItemId() {
return itemId;
}
public void setItemId(long itemId) {
this.itemId = itemId;
}
public long getAccountId() {
return accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
NotificationEventParcelablePlease.writeToParcel(this, dest, flags);
}
public static NotificationEvent create(Context context, Action action, long postTime, long respondTime, String type, long accountId, long itemId) {
final NotificationEvent event = new NotificationEvent();
event.setAction(action);
event.setStartTime(postTime);
event.setEndTime(respondTime);
event.setTimeOffset(TimeZone.getDefault().getOffset(postTime));
event.setLocation(HotMobiLogger.getCachedLatLng(context));
event.setType(type);
event.setAccountId(accountId);
event.setItemId(itemId);
return event;
}
public static NotificationEvent deleted(Context context, long postTime, String type, long accountId, long itemId) {
return create(context, Action.DELETE, System.currentTimeMillis(), postTime, type, accountId, itemId);
}
public static NotificationEvent open(Context context, long postTime, String type, long accountId, long itemId) {
return create(context, Action.OPEN, System.currentTimeMillis(), postTime, type, accountId, itemId);
}
public enum Action {
OPEN("open"), DELETE("delete"), UNKNOWN("unknown");
private final String value;
Action(String value) {
this.value = value;
}
public static Action parse(String action) {
if (OPEN.value.equalsIgnoreCase(action)) {
return OPEN;
} else if (DELETE.value.equalsIgnoreCase(action)) {
return DELETE;
}
return UNKNOWN;
}
public static class NotificationActionConverter extends StringBasedTypeConverter<Action> {
@Override
public Action getFromString(String string) {
return Action.parse(string);
}
@Override
public String convertToString(Action action) {
if (action == null) return null;
return action.value;
}
}
}
}

View File

@ -238,6 +238,7 @@ public class LinkHandlerActivity extends BaseAppCompatActivity implements System
mMainContent.setOnFitSystemWindowsListener(this);
setStatusBarColor(linkId, data);
setTaskInfo(linkId, data);
Utils.logOpenNotificationFromUri(this, data);
if (!showFragment(linkId, data)) {
finish();
}

View File

@ -23,6 +23,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.media.RingtoneManager;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
@ -147,9 +148,9 @@ public class AccountPreferences implements Constants {
return enabledIds;
}
@Nullable
@NonNull
public static AccountPreferences[] getNotificationEnabledPreferences(final Context context, final long[] accountIds) {
if (context == null || accountIds == null) return null;
if (context == null || accountIds == null) return new AccountPreferences[0];
final AccountPreferences[] temp = new AccountPreferences[accountIds.length];
int i = 0;
for (final long accountId : accountIds) {

View File

@ -0,0 +1,59 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.model;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
/**
* Created by mariotaku on 15/10/10.
*/
@JsonObject
public class PebbleMessage {
@JsonField(name = "title")
String title;
@JsonField(name = "body")
String body;
public PebbleMessage() {
}
public PebbleMessage(String title, String body) {
this.title = title;
this.body = body;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

View File

@ -99,6 +99,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.receiver.NotificationReceiver;
import org.mariotaku.twidere.service.BackgroundOperationService;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.DatabaseQueryUtils;
import org.mariotaku.twidere.util.ImagePreloader;
import org.mariotaku.twidere.util.MediaPreviewUtils;
import org.mariotaku.twidere.util.ParseUtils;
@ -179,7 +180,11 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
};
private boolean mNameFirst;
private static PendingIntent getDeleteIntent(Context context, String type, long accountId, long position) {
private static PendingIntent getMarkReadDeleteIntent(Context context, String type, long accountId, long position) {
return getMarkReadDeleteIntent(context, type, accountId, position, -1);
}
private static PendingIntent getMarkReadDeleteIntent(Context context, String type, long accountId, long position, long extraId) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
recvIntent.setAction(BROADCAST_NOTIFICATION_DELETED);
@ -189,11 +194,13 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITION, String.valueOf(position));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_EXTRA_ID, String.valueOf(extraId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
}
private static PendingIntent getDeleteIntent(Context context, String type, long accountId, StringLongPair[] positions) {
private static PendingIntent getMarkReadDeleteIntent(Context context, String type, long accountId, StringLongPair[] positions) {
// Setup delete intent
final Intent recvIntent = new Intent(context, NotificationReceiver.class);
final Uri.Builder recvLinkBuilder = new Uri.Builder();
@ -202,6 +209,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
recvLinkBuilder.appendPath(type);
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_READ_POSITIONS, StringLongPair.toString(positions));
recvLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
recvIntent.setData(recvLinkBuilder.build());
return PendingIntent.getBroadcast(context, 0, recvIntent, 0);
}
@ -1048,7 +1056,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
case TABLE_ID_STATUSES: {
final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context,
getAccountIds(context));
assert prefs != null;
for (final AccountPreferences pref : prefs) {
if (!pref.isHomeTimelineNotificationEnabled()) continue;
showTimelineNotification(pref, getPositionTag(TAB_TYPE_HOME_TIMELINE, pref.getAccountId()));
@ -1059,10 +1066,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
case TABLE_ID_MENTIONS: {
final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context,
getAccountIds(context));
assert prefs != null;
final boolean combined = mPreferences.getBoolean(KEY_COMBINED_NOTIFICATIONS);
for (final AccountPreferences pref : prefs) {
if (!pref.isMentionsNotificationEnabled()) continue;
showMentionsNotification(pref, getPositionTag(TAB_TYPE_MENTIONS_TIMELINE, pref.getAccountId()));
showMentionsNotification(pref, getPositionTag(TAB_TYPE_MENTIONS_TIMELINE, pref.getAccountId()), combined);
}
notifyUnreadCountChanged(NOTIFICATION_ID_MENTIONS_TIMELINE);
break;
@ -1070,7 +1077,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
case TABLE_ID_DIRECT_MESSAGES_INBOX: {
final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context,
getAccountIds(context));
assert prefs != null;
for (final AccountPreferences pref : prefs) {
if (!pref.isDirectMessagesNotificationEnabled()) continue;
final StringLongPair[] pairs = mReadStateManager.getPositionPairs(TAB_TYPE_DIRECT_MESSAGES);
@ -1147,7 +1153,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(getContentIntent(context, AUTHORITY_HOME, accountId));
builder.setDeleteIntent(getDeleteIntent(context, AUTHORITY_HOME, accountId, statusId));
builder.setDeleteIntent(getMarkReadDeleteIntent(context, AUTHORITY_HOME, accountId, statusId));
builder.setNumber(statusesCount);
builder.setColor(pref.getNotificationLightColor());
setNotificationPreferences(builder, pref, pref.getHomeTimelineNotificationType());
@ -1163,7 +1169,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
}
}
private void showMentionsNotification(AccountPreferences pref, long position) {
private void showMentionsNotification(AccountPreferences pref, long position, boolean combined) {
final long accountId = pref.getAccountId();
final Context context = getContext();
final Resources resources = context.getResources();
@ -1177,37 +1183,111 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
selection = Expression.and(Expression.equals(Statuses.ACCOUNT_ID, accountId),
Expression.greaterThan(Statuses.STATUS_ID, position));
}
final int itemsLimit = 5;
final String filteredSelection = Utils.buildStatusFilterWhereClause(Mentions.TABLE_NAME,
selection).getSQL();
final String[] userProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
final String[] statusProjection = {Statuses.STATUS_ID, Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME,
Statuses.TEXT_UNESCAPED, Statuses.STATUS_TIMESTAMP};
final Cursor statusCursor = mDatabaseWrapper.query(Mentions.TABLE_NAME, statusProjection,
filteredSelection, null, null, null, Statuses.SORT_ORDER_TIMESTAMP_DESC);
filteredSelection, null, null, null, Statuses.SORT_ORDER_TIMESTAMP_DESC,
String.valueOf(itemsLimit));
final int statusesCount = DatabaseQueryUtils.count(mDatabaseWrapper.getSQLiteDatabase(),
Mentions.TABLE_NAME, filteredSelection, null, null, null, null);
try {
if (combined) {
displayGroupedMentionsNotifications(pref, accountId, context, resources, nm,
filteredSelection, statusCursor, statusesCount);
} else {
displaySeparateMentionsNotifications(pref, accountId, context, resources, nm,
filteredSelection, statusCursor, statusesCount);
}
} finally {
statusCursor.close();
}
}
private void displaySeparateMentionsNotifications(AccountPreferences pref, long accountId,
Context context, Resources resources, NotificationManager nm,
String filteredSelection, Cursor statusCursor, int statusesCount) {
//noinspection TryFinallyCanBeTryWithResources
if (statusCursor.getCount() == 0 || statusesCount == 0) return;
final String accountName = Utils.getAccountName(context, accountId);
final String accountScreenName = Utils.getAccountScreenName(context, accountId);
final ParcelableStatus.CursorIndices indices = new ParcelableStatus.CursorIndices(statusCursor);
final UserColorNameManager manager = mUserColorNameManager;
// Setup notification
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setAutoCancel(true);
builder.setSmallIcon(R.drawable.ic_stat_mention);
// Add rich notification and get latest tweet timestamp
for (int i = 0, j = Math.min(statusCursor.getCount(), 5); statusCursor.moveToPosition(i) && i < j; i++) {
final long statusId = statusCursor.getLong(indices.status_id);
final String text = statusCursor.getString(indices.text_unescaped);
final NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle();
builder.setTicker(text);
builder.setContentTitle(resources.getString(R.string.user_mentioned_you,
manager.getDisplayName(statusCursor.getLong(indices.user_id),
statusCursor.getString(indices.user_name),
statusCursor.getString(indices.user_screen_name),
mNameFirst, false)));
builder.setContentText(text);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(getStatusContentIntent(context, accountId, statusId));
builder.setDeleteIntent(getMarkReadDeleteIntent(context, AUTHORITY_MENTIONS, accountId,
statusId, statusId));
builder.setWhen(statusCursor.getLong(indices.status_timestamp));
builder.setStyle(style);
style.bigText(text);
style.setSummaryText(mNameFirst ? accountName : accountScreenName);
//TODO show account info
try {
nm.notify("mentions_" + accountId + "_" + statusId, NOTIFICATION_ID_MENTIONS_TIMELINE,
builder.build());
Utils.sendPebbleNotification(context, text);
} catch (SecurityException e) {
// Silently ignore
}
}
setNotificationPreferences(builder, pref, pref.getMentionsNotificationType());
}
private void displayGroupedMentionsNotifications(AccountPreferences pref, long accountId,
Context context, Resources resources, NotificationManager nm,
String filteredSelection, Cursor statusCursor, int statusesCount) {
final String[] userProjection = {Statuses.USER_ID, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
final Cursor userCursor = mDatabaseWrapper.query(Mentions.TABLE_NAME, userProjection,
filteredSelection, null, Statuses.USER_ID, null, Statuses.SORT_ORDER_TIMESTAMP_DESC);
filteredSelection, null, Statuses.USER_ID, null, Statuses.SORT_ORDER_TIMESTAMP_DESC,
"1");
//noinspection TryFinallyCanBeTryWithResources
try {
final int usersCount = userCursor.getCount();
final int statusesCount = statusCursor.getCount();
final int usersCount = DatabaseQueryUtils.count(mDatabaseWrapper.getSQLiteDatabase(),
Mentions.TABLE_NAME, filteredSelection, null, Statuses.USER_ID, null, null);
if (statusesCount == 0 || usersCount == 0) return;
final String accountName = Utils.getAccountName(context, accountId);
final String accountScreenName = Utils.getAccountScreenName(context, accountId);
final int idxStatusText = statusCursor.getColumnIndex(Statuses.TEXT_UNESCAPED),
idxStatusId = statusCursor.getColumnIndex(Statuses.STATUS_ID),
idxStatusTimestamp = statusCursor.getColumnIndex(Statuses.STATUS_TIMESTAMP),
idxStatusUserId = statusCursor.getColumnIndex(Statuses.USER_ID),
idxStatusUserName = statusCursor.getColumnIndex(Statuses.USER_NAME),
idxStatusUserScreenName = statusCursor.getColumnIndex(Statuses.USER_SCREEN_NAME),
idxUserName = userCursor.getColumnIndex(Statuses.USER_NAME),
idxUserScreenName = userCursor.getColumnIndex(Statuses.USER_NAME),
idxUserId = userCursor.getColumnIndex(Statuses.USER_NAME);
idxUserScreenName = userCursor.getColumnIndex(Statuses.USER_SCREEN_NAME),
idxUserId = userCursor.getColumnIndex(Statuses.USER_ID);
final CharSequence notificationTitle = resources.getQuantityString(R.plurals.N_new_mentions,
statusesCount, statusesCount);
final String notificationContent;
userCursor.moveToFirst();
final UserColorNameManager manager = UserColorNameManager.getInstance(context);
final String displayName = manager.getUserNickname(userCursor.getLong(idxUserId),
final String displayName = mUserColorNameManager.getUserNickname(userCursor.getLong(idxUserId),
mNameFirst ? userCursor.getString(idxUserName) : userCursor.getString(idxUserScreenName));
if (usersCount == 1) {
notificationContent = context.getString(R.string.notification_mention, displayName);
@ -1219,7 +1299,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
// Add rich notification and get latest tweet timestamp
long when = -1, statusId = -1;
final InboxStyle style = new InboxStyle();
for (int i = 0, j = Math.min(statusesCount, 5); statusCursor.moveToPosition(i) && i < j; i++) {
for (int i = 0, j = Math.min(statusCursor.getCount(), 5); statusCursor.moveToPosition(i) && i < j; i++) {
if (when == -1) {
when = statusCursor.getLong(idxStatusTimestamp);
}
@ -1227,7 +1307,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
statusId = statusCursor.getLong(idxStatusId);
}
final SpannableStringBuilder sb = new SpannableStringBuilder();
sb.append(manager.getUserNickname(statusCursor.getLong(idxUserId),
sb.append(mUserColorNameManager.getUserNickname(statusCursor.getLong(idxStatusUserId),
mNameFirst ? statusCursor.getString(idxStatusUserName) : statusCursor.getString(idxStatusUserScreenName)));
sb.setSpan(new StyleSpan(Typeface.BOLD), 0, sb.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
sb.append(' ');
@ -1249,11 +1329,10 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(getContentIntent(context, AUTHORITY_MENTIONS, accountId));
builder.setDeleteIntent(getDeleteIntent(context, AUTHORITY_MENTIONS, accountId, statusId));
builder.setDeleteIntent(getMarkReadDeleteIntent(context, AUTHORITY_MENTIONS, accountId, statusId));
builder.setNumber(statusesCount);
builder.setWhen(when);
builder.setStyle(style);
builder.setColor(pref.getNotificationLightColor());
setNotificationPreferences(builder, pref, pref.getMentionsNotificationType());
try {
nm.notify("mentions_" + accountId, NOTIFICATION_ID_MENTIONS_TIMELINE,
@ -1263,7 +1342,6 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
// Silently ignore
}
} finally {
statusCursor.close();
userCursor.close();
}
}
@ -1275,6 +1353,24 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(type);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, String.valueOf(true));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
homeIntent.setData(homeLinkBuilder.build());
return PendingIntent.getActivity(context, 0, homeIntent, 0);
}
private PendingIntent getStatusContentIntent(Context context, long accountId, long statusId) {
// Setup click intent
final Intent homeIntent = new Intent(Intent.ACTION_VIEW);
homeIntent.setPackage(BuildConfig.APPLICATION_ID);
final Uri.Builder homeLinkBuilder = new Uri.Builder();
homeLinkBuilder.scheme(SCHEME_TWIDERE);
homeLinkBuilder.authority(AUTHORITY_STATUS);
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_ACCOUNT_ID, String.valueOf(accountId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_STATUS_ID, String.valueOf(statusId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_EXTRA_ID, String.valueOf(statusId));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, String.valueOf(true));
homeLinkBuilder.appendQueryParameter(QUERY_PARAM_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
homeIntent.setData(homeLinkBuilder.build());
return PendingIntent.getActivity(context, 0, homeIntent, 0);
}
@ -1297,6 +1393,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
} else {
notificationDefaults &= ~(NotificationCompat.DEFAULT_VIBRATE | NotificationCompat.DEFAULT_SOUND);
}
builder.setColor(pref.getNotificationLightColor());
builder.setDefaults(notificationDefaults);
}
@ -1421,7 +1518,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
builder.setContentText(notificationContent);
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
builder.setContentIntent(getContentIntent(context, AUTHORITY_DIRECT_MESSAGES, accountId));
builder.setDeleteIntent(getDeleteIntent(context, AUTHORITY_DIRECT_MESSAGES, accountId, positions));
builder.setDeleteIntent(getMarkReadDeleteIntent(context, AUTHORITY_DIRECT_MESSAGES, accountId, positions));
builder.setNumber(messagesCount);
builder.setWhen(when);
builder.setStyle(style);

View File

@ -33,6 +33,9 @@ import org.mariotaku.twidere.util.ReadStateManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.dagger.ApplicationModule;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.NotificationEvent;
/**
* Created by mariotaku on 15/4/4.
*/
@ -45,12 +48,19 @@ public class NotificationReceiver extends BroadcastReceiver implements Constants
case BROADCAST_NOTIFICATION_DELETED: {
final Uri uri = intent.getData();
if (uri == null) return;
final String tag = getPositionTag(uri.getLastPathSegment());
if (tag == null) return;
final String type = uri.getLastPathSegment();
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
final ReadStateManager manager = ApplicationModule.get(context).getReadStateManager();
final long extraId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_EXTRA_ID), -1);
final long timestamp = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1);
final ApplicationModule module = ApplicationModule.get(context);
if (AUTHORITY_MENTIONS.equals(type) && accountId != -1 && extraId != -1 && timestamp != -1) {
final HotMobiLogger logger = module.getHotMobiLogger();
logger.log(accountId, NotificationEvent.deleted(context, timestamp, type, accountId, extraId));
}
final ReadStateManager manager = module.getReadStateManager();
final String paramReadPosition, paramReadPositions;
if (!TextUtils.isEmpty(paramReadPosition = uri.getQueryParameter(QUERY_PARAM_READ_POSITION))) {
final String tag = getPositionTag(type);
if (tag != null && !TextUtils.isEmpty(paramReadPosition = uri.getQueryParameter(QUERY_PARAM_READ_POSITION))) {
manager.setPosition(Utils.getReadPositionTagWithAccounts(tag, accountId),
ParseUtils.parseLong(paramReadPosition, -1));
} else if (!TextUtils.isEmpty(paramReadPositions = uri.getQueryParameter(QUERY_PARAM_READ_POSITIONS))) {

View File

@ -0,0 +1,64 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.mariotaku.sqliteqb.library.SQLFunctions;
public class DatabaseQueryUtils {
public static int count(final SQLiteDatabase db, final String table, final String selection,
final String[] selectionArgs, final String groupBy, final String having, final String orderBy) {
if (db == null) return -1;
final Cursor c = db.query(table, new String[]{SQLFunctions.COUNT()}, selection, selectionArgs, groupBy, having, orderBy);
try {
if (c.moveToFirst()) return c.getInt(0);
return -1;
} finally {
c.close();
}
}
public static int count(final SQLiteDatabase db, boolean distinct, String table, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
if (db == null) return -1;
final Cursor c = db.query(distinct, table, new String[]{SQLFunctions.COUNT()}, selection, selectionArgs, groupBy, having, orderBy, limit);
try {
if (c.moveToFirst()) return c.getInt(0);
return -1;
} finally {
c.close();
}
}
public static int count(final SQLiteDatabase db, String table, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
if (db == null) return -1;
final Cursor c = db.query(table, new String[]{SQLFunctions.COUNT()}, selection, selectionArgs, groupBy, having, orderBy, limit);
try {
if (c.moveToFirst()) return c.getInt(0);
return -1;
} finally {
c.close();
}
}
}

View File

@ -0,0 +1,103 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util;
import android.support.annotation.Nullable;
import com.bluelinelabs.logansquare.LoganSquare;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
/**
* Created by mariotaku on 15/8/6.
*/
public class JsonSerializer {
@Nullable
public static <T> String serialize(@Nullable final List<T> list, final Class<T> cls) {
if (list == null) return null;
try {
return LoganSquare.serialize(list, cls);
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> String serialize(@Nullable final T object, final Class<T> cls) {
if (object == null) return null;
try {
return LoganSquare.mapperFor(cls).serialize(object);
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> String serializeArray(@Nullable final T[] object, final Class<T> cls) {
if (object == null) return null;
try {
return LoganSquare.mapperFor(cls).serialize(Arrays.asList(object));
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> List<T> parseList(@Nullable final String string, final Class<T> cls) {
if (string == null) return null;
try {
return LoganSquare.mapperFor(cls).parseList(string);
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> T[] parseArray(@Nullable final String string, final Class<T> cls) {
if (string == null) return null;
try {
final List<T> list = LoganSquare.mapperFor(cls).parseList(string);
//noinspection unchecked
return list.toArray((T[]) Array.newInstance(cls, list.size()));
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> T parse(@Nullable final String string, final Class<T> cls) {
if (string == null) return null;
try {
return LoganSquare.mapperFor(cls).parse(string);
} catch (IOException e) {
return null;
}
}
@Nullable
public static <T> String serialize(@Nullable final T obj) {
if (obj == null) return null;
//noinspection unchecked
return serialize(obj, (Class<T>) obj.getClass());
}
}

View File

@ -48,10 +48,10 @@ public class PermissionsManager implements Constants {
mPackageManager = context.getPackageManager();
}
public boolean accept(final String package_name, final String[] permissions) {
if (package_name == null || permissions == null) return false;
public boolean accept(final String packageName, final String[] permissions) {
if (packageName == null || permissions == null) return false;
final SharedPreferences.Editor editor = mPreferences.edit();
editor.putString(package_name, TwidereArrayUtils.toString(permissions, '|', false));
editor.putString(packageName, TwidereArrayUtils.toString(permissions, '|', false));
return editor.commit();
}

View File

@ -115,9 +115,7 @@ import android.widget.Toast;
import com.bluelinelabs.logansquare.LoganSquare;
import org.apache.commons.lang3.ArrayUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mariotaku.restfu.RestAPIFactory;
import org.mariotaku.restfu.RestClient;
import org.mariotaku.restfu.http.Authorization;
@ -199,6 +197,7 @@ import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.PebbleMessage;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.provider.TwidereDataStore.Activities;
@ -228,6 +227,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
import org.mariotaku.twidere.service.RefreshService;
import org.mariotaku.twidere.util.TwidereLinkify.HighlightStyle;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import org.mariotaku.twidere.util.dagger.ApplicationModule;
import org.mariotaku.twidere.util.menu.TwidereMenuInfo;
import org.mariotaku.twidere.view.CardMediaContainer.OnMediaClickListener;
import org.mariotaku.twidere.view.CardMediaContainer.PreviewStyle;
@ -248,7 +248,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map.Entry;
@ -258,6 +257,9 @@ import java.util.zip.CRC32;
import javax.net.ssl.SSLException;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.NotificationEvent;
import static android.text.TextUtils.isEmpty;
import static android.text.format.DateUtils.getRelativeTimeSpanString;
import static org.mariotaku.twidere.provider.TwidereDataStore.CACHE_URIS;
@ -3944,6 +3946,21 @@ public final class Utils implements Constants {
return isOutOfMemory(cause);
}
public static void logOpenNotificationFromUri(Context context, Uri uri) {
if (!uri.getBooleanQueryParameter(QUERY_PARAM_FROM_NOTIFICATION, false)) return;
String type = uri.getLastPathSegment();
if (type == null) {
type = uri.getAuthority();
}
final long accountId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_ACCOUNT_ID), -1);
final long extraId = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_EXTRA_ID), -1);
final long timestamp = ParseUtils.parseLong(uri.getQueryParameter(QUERY_PARAM_TIMESTAMP), -1);
if (!AUTHORITY_STATUS.equals(type) || accountId < 0 || extraId < 0 || timestamp < 0) return;
final ApplicationModule module = ApplicationModule.get(context);
final HotMobiLogger logger = module.getHotMobiLogger();
logger.log(accountId, NotificationEvent.open(context, timestamp, type, accountId, extraId));
}
static class UtilsL {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ -3995,20 +4012,15 @@ public final class Utils implements Constants {
if (prefs.getBoolean(KEY_PEBBLE_NOTIFICATIONS, false)) {
final String app_name = context.getString(R.string.app_name);
final String appName = context.getString(R.string.app_name);
final HashMap<String, String> data = new HashMap<>();
data.put("title", app_name);
data.put("body", message);
final JSONObject jsonData = new JSONObject(data);
final String notificationData = new JSONArray().put(jsonData).toString();
final List<PebbleMessage> messages = new ArrayList<>();
messages.add(new PebbleMessage(appName, message));
final Intent intent = new Intent(INTENT_ACTION_PEBBLE_NOTIFICATION);
intent.putExtra("messageType", "PEBBLE_ALERT");
intent.putExtra("sender", app_name);
intent.putExtra("notificationData", notificationData);
intent.putExtra("sender", appName);
intent.putExtra("notificationData", JsonSerializer.serialize(messages, PebbleMessage.class));
context.getApplicationContext().sendBroadcast(intent);
}

View File

@ -791,4 +791,5 @@
<string name="combined_notifications_summary_on">Notifications will be grouped</string>
<string name="combined_notifications_summary_off">Notifications will be displayed separately</string>
<string name="save_media_no_storage_permission_message">Storage permission is needed to save media.</string>
<string name="user_mentioned_you"><xliff:g id="name">%s</xliff:g> mentioned you</string>
</resources>

View File

@ -18,7 +18,7 @@
android:title="@string/silent_notifications" />
<org.mariotaku.twidere.preference.AutoFixCheckBoxPreference
android:defaultValue="false"
android:defaultValue="true"
android:key="combined_notifications"
android:summaryOff="@string/combined_notifications_summary_off"
android:summaryOn="@string/combined_notifications_summary_on"