diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java index 9c186fc40..79c312dfb 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableStatus.java @@ -86,19 +86,19 @@ public class ParcelableStatus implements Parcelable, Comparable T assertNonNull(@Nullable T object) { + if (object == null) throw new NullPointerException(); + return object; + } } diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java index 0c5f4d306..aa185764d 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/util/TwitterContentUtils.java @@ -171,12 +171,18 @@ public class TwitterContentUtils { } private static final CharSequenceTranslator UNESCAPE_TWITTER_RAW_TEXT = new LookupTranslator(EntityArrays.BASIC_UNESCAPE()); + private static final CharSequenceTranslator ESCAPE_TWITTER_RAW_TEXT = new LookupTranslator(EntityArrays.BASIC_ESCAPE()); public static String unescapeTwitterStatusText(final CharSequence text) { if (text == null) return null; return UNESCAPE_TWITTER_RAW_TEXT.translate(text); } + public static String escapeTwitterStatusText(final CharSequence text) { + if (text == null) return null; + return ESCAPE_TWITTER_RAW_TEXT.translate(text); + } + public static > T getStatusesWithQuoteData(Twitter twitter, @NonNull T list) throws TwitterException { LongSparseMap quotes = new LongSparseMap<>(); // Phase 1: collect all statuses contains a status link, and put it in the map diff --git a/twidere/src/main/java/edu/tsinghua/hotmobi/HotMobiLogger.java b/twidere/src/main/java/edu/tsinghua/hotmobi/HotMobiLogger.java index fea8aeab2..67f95f8de 100644 --- a/twidere/src/main/java/edu/tsinghua/hotmobi/HotMobiLogger.java +++ b/twidere/src/main/java/edu/tsinghua/hotmobi/HotMobiLogger.java @@ -27,7 +27,9 @@ import android.content.SharedPreferences; import android.location.Location; import android.os.BatteryManager; import android.text.TextUtils; +import android.util.Log; +import org.mariotaku.twidere.BuildConfig; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.util.JsonSerializer; @@ -58,6 +60,7 @@ import edu.tsinghua.hotmobi.model.ScreenEvent; import edu.tsinghua.hotmobi.model.ScrollRecord; import edu.tsinghua.hotmobi.model.SessionEvent; import edu.tsinghua.hotmobi.model.TweetEvent; +import edu.tsinghua.hotmobi.model.UploadLogEvent; /** * Created by mariotaku on 15/8/10. @@ -107,6 +110,8 @@ public class HotMobiLogger { return "notification"; } else if (event instanceof ScreenEvent) { return "screen"; + } else if (event instanceof UploadLogEvent) { + return "upload_log"; } throw new UnsupportedOperationException("Unknown event type " + event); } @@ -187,6 +192,18 @@ public class HotMobiLogger { getInstance(context).log(record, null); } + public static boolean log(final String msg) { + if (BuildConfig.DEBUG) { + final StackTraceElement ste = new Throwable().fillInStackTrace().getStackTrace()[1]; + final String fullName = ste.getClassName(); + final String name = fullName.substring(fullName.lastIndexOf('.')); + final String tag = name + "." + ste.getMethodName(); + Log.d(tag, msg); + return true; + } else + return false; + } + public void log(long accountId, final T event, final PreProcessing preProcessing) { mExecutor.execute(new WriteLogTask<>(mApplication, accountId, event, preProcessing)); } diff --git a/twidere/src/main/java/edu/tsinghua/hotmobi/UploadLogsTask.java b/twidere/src/main/java/edu/tsinghua/hotmobi/UploadLogsTask.java index b478797ef..b50828d3f 100644 --- a/twidere/src/main/java/edu/tsinghua/hotmobi/UploadLogsTask.java +++ b/twidere/src/main/java/edu/tsinghua/hotmobi/UploadLogsTask.java @@ -41,7 +41,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import edu.tsinghua.spice.Utilies.SpiceProfilingUtil; +import edu.tsinghua.hotmobi.model.UploadLogEvent; /** * Upload logs to target server @@ -67,7 +67,7 @@ public class UploadLogsTask implements Runnable { final double deltaDays = (System.currentTimeMillis() - lastUpload) / (double) HotMobiLogger.UPLOAD_INTERVAL_MILLIS; if (deltaDays < 1) { - SpiceProfilingUtil.log("Last uploaded was conducted in 1 day ago."); + HotMobiLogger.log("Last uploaded was conducted in 1 day ago."); return; } } @@ -106,8 +106,13 @@ public class UploadLogsTask implements Runnable { builder.headers(headers); body = new FileTypedData(logFile); builder.body(body); + final UploadLogEvent uploadLogEvent = UploadLogEvent.create(context, logFile); response = client.execute(builder.build()); if (response.isSuccessful()) { + uploadLogEvent.markEnd(); + if (!uploadLogEvent.shouldSkip()) { + HotMobiLogger.getInstance(context).log(uploadLogEvent); + } succeeded &= logFile.delete(); } } catch (IOException e) { diff --git a/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java b/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java index 5a9784ac3..e57bad1e6 100644 --- a/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java +++ b/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java @@ -31,7 +31,7 @@ import com.bluelinelabs.logansquare.annotation.JsonObject; import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; import org.mariotaku.twidere.model.AccountPreferences; -import org.mariotaku.twidere.util.Utils; +import org.mariotaku.twidere.util.DataStoreUtils; import java.util.HashMap; @@ -92,7 +92,7 @@ public class SessionEvent extends BaseEvent implements Parcelable { public void dumpPreferences(Context context) { final HashMap preferences = new HashMap<>(); - for (AccountPreferences pref : AccountPreferences.getAccountPreferences(context, Utils.getAccountIds(context))) { + for (AccountPreferences pref : AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountIds(context))) { final long accountId = pref.getAccountId(); preferences.put("notification_" + accountId + "_home", String.valueOf(pref.isHomeTimelineNotificationEnabled())); preferences.put("notification_" + accountId + "_interactions", String.valueOf(pref.isInteractionsNotificationEnabled())); diff --git a/twidere/src/main/java/edu/tsinghua/hotmobi/model/UploadLogEvent.java b/twidere/src/main/java/edu/tsinghua/hotmobi/model/UploadLogEvent.java new file mode 100644 index 000000000..cb2b95557 --- /dev/null +++ b/twidere/src/main/java/edu/tsinghua/hotmobi/model/UploadLogEvent.java @@ -0,0 +1,77 @@ +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.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; +import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease; + +import java.io.File; + +/** + * Created by mariotaku on 16/1/2. + */ +@ParcelablePlease +@JsonObject +public class UploadLogEvent extends BaseEvent implements Parcelable { + public static final Creator CREATOR = new Creator() { + public UploadLogEvent createFromParcel(Parcel source) { + UploadLogEvent target = new UploadLogEvent(); + UploadLogEventParcelablePlease.readFromParcel(target, source); + return target; + } + + public UploadLogEvent[] newArray(int size) { + return new UploadLogEvent[size]; + } + }; + @ParcelableThisPlease + @JsonField(name = "file_name") + String fileName; + @ParcelableThisPlease + @JsonField(name = "file_length") + + long fileLength; + + public long getFileLength() { + + return fileLength; + } + + public void setFileLength(long fileLength) { + this.fileLength = fileLength; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + UploadLogEventParcelablePlease.writeToParcel(this, dest, flags); + } + + public static UploadLogEvent create(Context context, File file) { + UploadLogEvent event = new UploadLogEvent(); + event.markStart(context); + event.setFileLength(file.length()); + event.setFileName(file.getName()); + return event; + } + + public boolean shouldSkip() { + return fileName.contains("upload_log"); + } +} diff --git a/twidere/src/main/java/edu/tsinghua/spice/Task/SpiceFileFilter.java b/twidere/src/main/java/edu/tsinghua/spice/Task/SpiceFileFilter.java deleted file mode 100644 index ac6e8c1a4..000000000 --- a/twidere/src/main/java/edu/tsinghua/spice/Task/SpiceFileFilter.java +++ /dev/null @@ -1,23 +0,0 @@ -package edu.tsinghua.spice.Task; - -import java.io.File; -import java.io.FileFilter; - -/** - * Created by Denny C. Ng on 2/21/15. - */ - -public final class SpiceFileFilter implements FileFilter { - - @Override - public boolean accept(final File file) { - return file.isFile() && "spi".equalsIgnoreCase(getExtension(file)); - } - - static String getExtension(final File file) { - final String name = file.getName(); - final int pos = name.lastIndexOf('.'); - if (pos == -1) return null; - return name.substring(pos + 1); - } -} \ No newline at end of file diff --git a/twidere/src/main/java/edu/tsinghua/spice/Utilies/SpiceProfilingUtil.java b/twidere/src/main/java/edu/tsinghua/spice/Utilies/SpiceProfilingUtil.java deleted file mode 100644 index 45fea0b82..000000000 --- a/twidere/src/main/java/edu/tsinghua/spice/Utilies/SpiceProfilingUtil.java +++ /dev/null @@ -1,48 +0,0 @@ -package edu.tsinghua.spice.Utilies; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.util.Log; - -import org.mariotaku.twidere.BuildConfig; - -/** - * Created by Denny C. Ng on 2/20/15. - */ - -public class SpiceProfilingUtil { - - public static final String FILE_NAME_PROFILE = "Profile_SPICE"; - public static final String FILE_NAME_LOCATION = "Location_SPICE"; - public static final String FILE_NAME_APP = "App_SPICE"; - public static final String FILE_NAME_NETWORK = "Network_SPICE"; - public static final String FILE_NAME_ONWIFI = "onWifi_SPICE"; - public static final String FILE_NAME_ONLAUNCH = "onLaunch_SPICE"; - public static final String FILE_NAME_SCREEN = "Screen_SPICE"; - - @SuppressLint("InlinedApi") - public static boolean isCharging(final Context context) { - if (context == null) return false; - final Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - if (intent == null) return false; - final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - return plugged == BatteryManager.BATTERY_PLUGGED_AC - || plugged == BatteryManager.BATTERY_PLUGGED_USB - || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; - } - - public static boolean log(final String msg) { - if (BuildConfig.DEBUG) { - final StackTraceElement ste = new Throwable().fillInStackTrace().getStackTrace()[1]; - final String fullName = ste.getClassName(); - final String name = fullName.substring(fullName.lastIndexOf('.')); - final String tag = name + "." + ste.getMethodName(); - Log.d(tag, msg); - return true; - } else - return false; - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java index a0476a0c5..39d127274 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/ComposeActivity.java @@ -474,7 +474,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte intent.putExtra(EXTRA_ACCOUNT_IDS, accountIds); if (accountIds.length > 0) { final long account_id = accountIds[0]; - intent.putExtra(EXTRA_NAME, Utils.getAccountName(this, account_id)); + intent.putExtra(EXTRA_NAME, DataStoreUtils.getAccountName(this, account_id)); intent.putExtra(EXTRA_SCREEN_NAME, DataStoreUtils.getAccountScreenName(this, account_id)); } if (mInReplyToStatusId > 0) { @@ -605,7 +605,7 @@ public class ComposeActivity extends ThemedFragmentActivity implements OnMenuIte mValidator = new TwidereValidator(this); setContentView(R.layout.activity_compose); setFinishOnTouchOutside(false); - final long[] defaultAccountIds = Utils.getAccountIds(this); + final long[] defaultAccountIds = DataStoreUtils.getAccountIds(this); if (defaultAccountIds.length <= 0) { final Intent intent = new Intent(INTENT_ACTION_TWITTER_LOGIN); intent.setClass(this, SignInActivity.class); diff --git a/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java b/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java index fdd94f86b..c1c73e0a4 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java +++ b/twidere/src/main/java/org/mariotaku/twidere/activity/support/HomeActivity.java @@ -347,7 +347,7 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen } mMultiSelectHandler = new MultiSelectEventHandler(this); mMultiSelectHandler.dispatchOnCreate(); - if (!Utils.hasAccount(this)) { + if (!DataStoreUtils.hasAccount(this)) { final Intent signInIntent = new Intent(INTENT_ACTION_TWITTER_LOGIN); signInIntent.setClass(this, SignInActivity.class); startActivity(signInIntent); diff --git a/twidere/src/main/java/org/mariotaku/twidere/adapter/DraftsAdapter.java b/twidere/src/main/java/org/mariotaku/twidere/adapter/DraftsAdapter.java index bc4fe6252..29b66516f 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/adapter/DraftsAdapter.java +++ b/twidere/src/main/java/org/mariotaku/twidere/adapter/DraftsAdapter.java @@ -33,6 +33,7 @@ import org.mariotaku.twidere.model.DraftItemCursorIndices; import org.mariotaku.twidere.model.ParcelableMedia; import org.mariotaku.twidere.model.ParcelableMediaUpdate; import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; +import org.mariotaku.twidere.util.DataStoreUtils; import org.mariotaku.twidere.util.MediaLoaderWrapper; import org.mariotaku.twidere.util.MediaLoadingHandler; import org.mariotaku.twidere.util.SharedPreferencesWrapper; @@ -43,8 +44,6 @@ import org.mariotaku.twidere.view.holder.DraftViewHolder; import javax.inject.Inject; -import static org.mariotaku.twidere.util.Utils.getAccountColors; - public class DraftsAdapter extends SimpleCursorAdapter implements Constants { @Inject @@ -81,7 +80,7 @@ public class DraftsAdapter extends SimpleCursorAdapter implements Constants { } else { holder.media_preview_container.setVisibility(View.GONE); } - holder.content.drawEnd(getAccountColors(context, accountIds)); + holder.content.drawEnd(DataStoreUtils.getAccountColors(context, accountIds)); holder.setTextSize(mTextSize); final boolean emptyContent = TextUtils.isEmpty(text); if (emptyContent) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/api/twitter/model/StatusUtils.java b/twidere/src/main/java/org/mariotaku/twidere/api/twitter/model/StatusUtils.java new file mode 100644 index 000000000..ae418c9d2 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/api/twitter/model/StatusUtils.java @@ -0,0 +1,65 @@ +package org.mariotaku.twidere.api.twitter.model; + +import android.support.annotation.NonNull; + +import org.mariotaku.twidere.model.ParcelableStatus; +import org.mariotaku.twidere.util.TwitterContentUtils; + +import java.util.Date; + +/** + * Created by mariotaku on 16/1/3. + */ +public class StatusUtils { + + public static Status fromParcelableStatus(@NonNull ParcelableStatus parcelable) { + Status status = new Status(); + status.id = parcelable.id; + status.text = TwitterContentUtils.escapeTwitterStatusText(parcelable.text_plain); + status.createdAt = new Date(parcelable.timestamp); + status.inReplyToStatusId = parcelable.in_reply_to_status_id; + status.inReplyToUserId = parcelable.in_reply_to_user_id; + status.inReplyToScreenName = parcelable.in_reply_to_screen_name; + if (parcelable.is_retweet) { + Status retweet = status.retweetedStatus = new Status(); + retweet.id = parcelable.retweet_id; + retweet.text = TwitterContentUtils.escapeTwitterStatusText(parcelable.text_plain); + retweet.createdAt = new Date(parcelable.retweet_timestamp); + User retweetUser = retweet.user = new User(); + retweetUser.id = parcelable.user_id; + retweetUser.screenName = parcelable.user_screen_name; + retweetUser.name = parcelable.user_name; + retweetUser.profileBackgroundImageUrl = parcelable.user_profile_image_url; + + User user = status.user = new User(); + user.id = parcelable.retweeted_by_user_id; + user.name = parcelable.retweeted_by_user_name; + user.screenName = parcelable.retweeted_by_user_screen_name; + user.profileImageUrl = parcelable.retweeted_by_user_profile_image; + } else if (parcelable.is_quote) { + Status quote = status.quotedStatus = new Status(); + quote.id = parcelable.quoted_id; + quote.text = TwitterContentUtils.escapeTwitterStatusText(parcelable.quoted_text_plain); + quote.createdAt = new Date(parcelable.quoted_timestamp); + User quotedUser = quote.user = new User(); + quotedUser.id = parcelable.quoted_user_id; + quotedUser.name = parcelable.quoted_user_name; + quotedUser.screenName = parcelable.quoted_user_screen_name; + quotedUser.profileImageUrl = parcelable.quoted_user_profile_image; + + User user = status.user = new User(); + user.id = parcelable.user_id; + user.screenName = parcelable.user_screen_name; + user.name = parcelable.user_name; + user.profileBackgroundImageUrl = parcelable.user_profile_image_url; + } else { + User user = status.user = new User(); + user.id = parcelable.user_id; + user.screenName = parcelable.user_screen_name; + user.name = parcelable.user_name; + user.profileBackgroundImageUrl = parcelable.user_profile_image_url; + } + return status; + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/CustomTabsFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/CustomTabsFragment.java index 2c2a57010..5a1dcf35d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/CustomTabsFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/CustomTabsFragment.java @@ -64,6 +64,7 @@ import org.mariotaku.twidere.activity.support.CustomTabEditorActivity; import org.mariotaku.twidere.model.CustomTabConfiguration; import org.mariotaku.twidere.model.CustomTabConfiguration.CustomTabConfigurationComparator; import org.mariotaku.twidere.provider.TwidereDataStore.Tabs; +import org.mariotaku.twidere.util.DataStoreUtils; import org.mariotaku.twidere.util.ThemeUtils; import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.view.holder.TwoLineWithIconViewHolder; @@ -80,7 +81,6 @@ import static org.mariotaku.twidere.util.CustomTabUtils.getTabIconObject; import static org.mariotaku.twidere.util.CustomTabUtils.getTabTypeName; import static org.mariotaku.twidere.util.CustomTabUtils.isTabAdded; import static org.mariotaku.twidere.util.CustomTabUtils.isTabTypeValid; -import static org.mariotaku.twidere.util.Utils.getAccountIds; public class CustomTabsFragment extends BaseFragment implements LoaderCallbacks, MultiChoiceModeListener, OnItemClickListener { @@ -221,7 +221,7 @@ public class CustomTabsFragment extends BaseFragment implements LoaderCallbacks< final Resources res = getResources(); final boolean hasOfficialKeyAccounts = Utils.hasAccountSignedWithOfficialKeys(getActivity()); final boolean forcePrivateAPI = mPreferences.getBoolean(KEY_FORCE_USING_PRIVATE_APIS, false); - final long[] accountIds = getAccountIds(getActivity()); + final long[] accountIds = DataStoreUtils.getAccountIds(getActivity()); final MenuItem itemAdd = menu.findItem(R.id.add_submenu); if (itemAdd != null && itemAdd.hasSubMenu()) { final SubMenu subMenu = itemAdd.getSubMenu(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java index 0f07be8fb..19060ec50 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/StatusFragment.java @@ -111,6 +111,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; import org.mariotaku.twidere.util.AsyncTaskUtils; import org.mariotaku.twidere.util.AsyncTwitterWrapper; import org.mariotaku.twidere.util.CompareUtils; +import org.mariotaku.twidere.util.DataStoreUtils; import org.mariotaku.twidere.util.HtmlSpanBuilder; import org.mariotaku.twidere.util.KeyboardShortcutsHandler; import org.mariotaku.twidere.util.KeyboardShortcutsHandler.KeyboardShortcutCallback; @@ -197,14 +198,13 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac mStatusAdapter.setRepliesLoading(true); mStatusAdapter.setConversationsLoading(true); mStatusAdapter.updateItemDecoration(); - final long accountId = args.getLong(EXTRA_ACCOUNT_ID, -1); - final String screenName = args.getString(EXTRA_SCREEN_NAME); - final long statusId = args.getLong(EXTRA_STATUS_ID, -1); + final ParcelableStatus status = args.getParcelable(EXTRA_STATUS); final long maxId = args.getLong(EXTRA_MAX_ID, -1); final long sinceId = args.getLong(EXTRA_SINCE_ID, -1); final boolean twitterOptimizedSearches = mPreferences.getBoolean(KEY_TWITTER_OPTIMIZED_SEARCHES); - final ConversationLoader loader = new ConversationLoader(getActivity(), accountId, - statusId, screenName, sinceId, maxId, null, true); + assert status != null; + final ConversationLoader loader = new ConversationLoader(getActivity(), status, sinceId, + maxId, null, true, twitterOptimizedSearches); loader.setComparator(ParcelableStatus.REVERSE_ID_COMPARATOR); return loader; } @@ -618,7 +618,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac final Bundle args = new Bundle(); args.putLong(EXTRA_ACCOUNT_ID, status.account_id); args.putLong(EXTRA_STATUS_ID, status.is_retweet ? status.retweet_id : status.id); - args.putString(EXTRA_SCREEN_NAME, status.is_retweet ? status.retweeted_by_user_screen_name : status.user_screen_name); + args.putParcelable(EXTRA_STATUS, status); if (mConversationLoaderInitialized) { getLoaderManager().restartLoader(LOADER_ID_STATUS_CONVERSATIONS, args, mConversationsLoaderCallback); return; @@ -917,7 +917,7 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac retweetedByView.setVisibility(View.GONE); } - profileContainer.drawEnd(Utils.getAccountColor(context, status.account_id)); + profileContainer.drawEnd(DataStoreUtils.getAccountColor(context, status.account_id)); final int layoutPosition = getLayoutPosition(); if (status.is_quote && ArrayUtils.isEmpty(status.media)) { @@ -1542,16 +1542,17 @@ public class StatusFragment extends BaseSupportFragment implements LoaderCallbac final ParcelableStatus status = mStatus; if (status == null) return; mData = data; - if (data == null) { + if (data == null || data.isEmpty()) { setCount(ITEM_IDX_CONVERSATION, 0); setCount(ITEM_IDX_REPLY, 0); } else { int conversationCount = 0, replyCount = 0; boolean containsStatus = false; + final long statusId = status.is_retweet ? status.retweet_id : status.id; for (ParcelableStatus item : data) { - if (item.id < status.id) { + if (item.id < statusId) { conversationCount++; - } else if (item.id > status.id) { + } else if (item.id > statusId) { replyCount++; } else { containsStatus = true; diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java index d0a110026..465875788 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/support/UserFragment.java @@ -493,7 +493,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener mUser = user; final int userColor = mUserColorNameManager.getUserColor(user.id, true); mProfileImageView.setBorderColor(userColor != 0 ? userColor : Color.WHITE); - mProfileNameContainer.drawEnd(Utils.getAccountColor(activity, user.account_id)); + mProfileNameContainer.drawEnd(DataStoreUtils.getAccountColor(activity, user.account_id)); final String nick = mUserColorNameManager.getUserNickname(user.id, true); mNameView.setText(TextUtils.isEmpty(nick) ? user.name : getString(R.string.name_with_nickname, user.name, nick)); final int typeIconRes = Utils.getUserTypeIconRes(user.is_verified, user.is_protected); @@ -840,7 +840,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener MenuUtils.setMenuItemAvailability(menu, R.id.incoming_friendships, isMyself); MenuUtils.setMenuItemAvailability(menu, R.id.saved_searches, isMyself); MenuUtils.setMenuItemAvailability(menu, R.id.scheduled_statuses, isMyself - && Utils.getOfficialKeyType(getActivity(), user.account_id) == ConsumerKeyType.TWEETDECK); + && TwitterAPIFactory.getOfficialKeyType(getActivity(), user.account_id) == ConsumerKeyType.TWEETDECK); // final MenuItem followItem = menu.findItem(MENU_FOLLOW); // followItem.setVisible(!isMyself); // final boolean shouldShowFollowItem = !creatingFriendship && !destroyingFriendship && !isMyself @@ -1448,7 +1448,7 @@ public class UserFragment extends BaseSupportFragment implements OnClickListener } mPagerAdapter.addTab(UserTimelineFragment.class, tabArgs, getString(R.string.statuses), R.drawable.ic_action_quote, TAB_TYPE_STATUSES, TAB_POSITION_STATUSES, null); - if (Utils.isOfficialKeyAccount(context, accountId)) { + if (TwitterAPIFactory.isOfficialKeyAccount(context, accountId)) { mPagerAdapter.addTab(UserMediaTimelineFragment.class, tabArgs, getString(R.string.media), R.drawable.ic_action_gallery, TAB_TYPE_MEDIA, TAB_POSITION_MEDIA, null); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesAboutMeLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesAboutMeLoader.java deleted file mode 100644 index b1c1d9762..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesAboutMeLoader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.support; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; - -import org.mariotaku.twidere.model.ParcelableActivity; - -import java.util.List; - -import org.mariotaku.twidere.api.twitter.model.Activity; -import org.mariotaku.twidere.api.twitter.model.Paging; -import org.mariotaku.twidere.api.twitter.Twitter; -import org.mariotaku.twidere.api.twitter.TwitterException; - -public class ActivitiesAboutMeLoader extends TwitterAPIActivitiesLoader { - - public ActivitiesAboutMeLoader(final Context context, final long accountId, long sinceId, - long maxId, final List data, - final String[] saveFileArgs, final int position) { - super(context, accountId, sinceId, maxId, data, saveFileArgs, position); - } - - @Override - protected List getActivities(final Twitter twitter, final Paging paging) throws TwitterException { - if (twitter == null) return null; - return twitter.getActivitiesAboutMe(paging); - } - - @Override - protected boolean shouldFilterActivity(SQLiteDatabase database, ParcelableActivity activity) { - return false; - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesByFriendsLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesByFriendsLoader.java deleted file mode 100644 index 74c5482c3..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ActivitiesByFriendsLoader.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.support; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; - -import org.mariotaku.twidere.model.ParcelableActivity; - -import java.util.List; - -import org.mariotaku.twidere.api.twitter.model.Activity; -import org.mariotaku.twidere.api.twitter.model.Paging; -import org.mariotaku.twidere.api.twitter.Twitter; -import org.mariotaku.twidere.api.twitter.TwitterException; - -public class ActivitiesByFriendsLoader extends TwitterAPIActivitiesLoader { - - - public ActivitiesByFriendsLoader(final Context context, final long accountId, long sinceId, - long maxId, final List data, - final String[] saveFileArgs, final int position) { - super(context, accountId, sinceId, maxId, data, saveFileArgs, position); - } - - @Override - protected List getActivities(final Twitter twitter, final Paging paging) throws TwitterException { - if (twitter == null) return null; - return twitter.getActivitiesByFriends(paging); - } - - @Override - protected boolean shouldFilterActivity(SQLiteDatabase database, ParcelableActivity activity) { - return false; - } - - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ConversationLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ConversationLoader.java index 090adcbfa..f346a7ae8 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ConversationLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ConversationLoader.java @@ -26,27 +26,74 @@ import android.support.annotation.NonNull; import org.mariotaku.twidere.api.twitter.Twitter; import org.mariotaku.twidere.api.twitter.TwitterException; import org.mariotaku.twidere.api.twitter.model.Paging; +import org.mariotaku.twidere.api.twitter.model.SearchQuery; import org.mariotaku.twidere.api.twitter.model.Status; +import org.mariotaku.twidere.api.twitter.model.StatusUtils; import org.mariotaku.twidere.model.ParcelableStatus; +import org.mariotaku.twidere.model.util.ParcelableStatusUtils; +import org.mariotaku.twidere.util.Nullables; +import org.mariotaku.twidere.util.ParcelUtils; +import org.mariotaku.twidere.util.TwitterAPIFactory; +import org.mariotaku.twidere.util.Utils; import java.util.ArrayList; import java.util.List; public class ConversationLoader extends TwitterAPIStatusesLoader { - private final long mInReplyToStatusId; + private final boolean mTwitterOptimizedSearches; - public ConversationLoader(final Context context, final long accountId, final long statusId, - final String screenName, final long sinceId, final long maxId, - final List data, final boolean fromUser) { - super(context, accountId, sinceId, maxId, data, null, -1, fromUser); - mInReplyToStatusId = statusId; + @NonNull + private final ParcelableStatus mStatus; + + public ConversationLoader(final Context context, @NonNull final ParcelableStatus status, + final long sinceId, final long maxId, final List data, + final boolean fromUser, final boolean twitterOptimizedSearches) { + super(context, status.account_id, sinceId, maxId, data, null, -1, fromUser); + mStatus = Nullables.assertNonNull(ParcelUtils.clone(status)); + ParcelableStatusUtils.makeOriginalStatus(mStatus); + mTwitterOptimizedSearches = twitterOptimizedSearches; } @NonNull @Override public List getStatuses(@NonNull final Twitter twitter, final Paging paging) throws TwitterException { - return twitter.showConversation(mInReplyToStatusId, paging); + final ParcelableStatus status = mStatus; + if (Utils.shouldForceUsingPrivateAPIs(getContext()) || TwitterAPIFactory.isOfficialTwitterInstance(getContext(), twitter)) { + return twitter.showConversation(status.id, paging); + } + final List statuses = new ArrayList<>(); + final long maxId = getMaxId(), sinceId = getSinceId(); + boolean getConversations = maxId < status.id || (maxId <= 0 && sinceId <= 0); + boolean getReplies = sinceId > status.id || (maxId <= 0 && sinceId <= 0); + if (getConversations) { + long inReplyToId = status.in_reply_to_status_id; + int count = 0; + while (inReplyToId > 0 && count < 10) { + final Status item = twitter.showStatus(inReplyToId); + inReplyToId = item.getInReplyToStatusId(); + statuses.add(item); + count++; + } + } + if ((sinceId <= 0 && maxId <= 0) || (sinceId > 0 && sinceId < status.id) || (maxId > 0 && maxId >= status.id)) { + statuses.add(StatusUtils.fromParcelableStatus(status)); + } + if (getReplies) { + SearchQuery query = new SearchQuery(); + if (mTwitterOptimizedSearches) { + query.query("to:" + status.user_screen_name); + } else { + query.query("@" + status.user_screen_name); + } + query.sinceId(status.id); + for (Status item : twitter.search(query)) { + if (item.getInReplyToStatusId() == status.id) { + statuses.add(item); + } + } + } + return statuses; } @Override diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/MediaTimelineLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/MediaTimelineLoader.java index bf359106f..ff60dcf51 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/MediaTimelineLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/MediaTimelineLoader.java @@ -23,17 +23,16 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; -import org.mariotaku.twidere.model.ParcelableStatus; - -import java.util.List; - +import org.mariotaku.twidere.api.twitter.Twitter; +import org.mariotaku.twidere.api.twitter.TwitterException; import org.mariotaku.twidere.api.twitter.model.Paging; import org.mariotaku.twidere.api.twitter.model.ResponseList; import org.mariotaku.twidere.api.twitter.model.Status; -import org.mariotaku.twidere.api.twitter.Twitter; -import org.mariotaku.twidere.api.twitter.TwitterException; +import org.mariotaku.twidere.model.ParcelableStatus; +import org.mariotaku.twidere.util.DataStoreUtils; + +import java.util.List; -import static org.mariotaku.twidere.util.Utils.getAccountId; import static org.mariotaku.twidere.util.Utils.isFiltered; public class MediaTimelineLoader extends TwitterAPIStatusesLoader { @@ -48,7 +47,7 @@ public class MediaTimelineLoader extends TwitterAPIStatusesLoader { super(context, accountId, sinceId, maxId, data, savedStatusesArgs, tabPosition, fromUser); mUserId = userId; mUserScreenName = screenName; - mIsMyTimeline = userId > 0 ? accountId == userId : accountId == getAccountId(context, screenName); + mIsMyTimeline = userId > 0 ? accountId == userId : accountId == DataStoreUtils.getAccountId(context, screenName); } @NonNull diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableActivitiesLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableActivitiesLoader.java deleted file mode 100644 index 33723573f..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/ParcelableActivitiesLoader.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2015 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.support; - -import android.content.Context; -import android.support.v4.content.AsyncTaskLoader; - -import org.mariotaku.twidere.Constants; -import org.mariotaku.twidere.model.ParcelableActivity; -import org.mariotaku.twidere.util.collection.NoDuplicatesArrayList; - -import java.util.List; - -public abstract class ParcelableActivitiesLoader extends AsyncTaskLoader> implements Constants { - - private final List mData = new NoDuplicatesArrayList<>(); - private final boolean mFirstLoad; - private final int mTabPosition; - - private Long mLastViewedId; - - public ParcelableActivitiesLoader(final Context context, final List data, final int tab_position) { - super(context); - mFirstLoad = data == null; - if (data != null) { - mData.addAll(data); - } - mTabPosition = tab_position; - } - - public Long getLastViewedId() { - return mLastViewedId; - } - - protected boolean containsStatus(final long id) { - for (final ParcelableActivity activity : mData) { - if (activity.max_position <= id && activity.min_position >= id) return true; - } - return false; - } - - protected boolean deleteActivity(final List activities, final long id) { - if (activities == null || activities.isEmpty()) return false; - boolean result = false; - for (final ParcelableActivity activity : activities.toArray(new ParcelableActivity[activities.size()])) { - if (id <= activity.max_position && id >= activity.min_position) { - result |= activities.remove(activity); - } - } - return result; - } - - protected List getData() { - return mData; - } - - protected int getTabPosition() { - return mTabPosition; - } - - protected boolean isFirstLoad() { - return mFirstLoad; - } - - @Override - protected void onStartLoading() { - forceLoad(); - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIActivitiesLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIActivitiesLoader.java deleted file mode 100644 index 39a2ce8da..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIActivitiesLoader.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2015 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.loader.support; - -import android.content.Context; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; -import android.util.Pair; - -import com.bluelinelabs.logansquare.LoganSquare; - -import org.mariotaku.twidere.BuildConfig; -import org.mariotaku.twidere.api.twitter.Twitter; -import org.mariotaku.twidere.api.twitter.TwitterException; -import org.mariotaku.twidere.api.twitter.model.Activity; -import org.mariotaku.twidere.api.twitter.model.Paging; -import org.mariotaku.twidere.model.ParcelableActivity; -import org.mariotaku.twidere.util.JsonSerializer; -import org.mariotaku.twidere.util.TwitterAPIFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import static org.mariotaku.twidere.util.Utils.truncateActivities; - -public abstract class TwitterAPIActivitiesLoader extends ParcelableActivitiesLoader { - - private final Context mContext; - private final long mAccountIds; - private final long mMaxId, mSinceId; - private final Object[] mSavedStatusesFileArgs; - private Comparator mComparator; - - public TwitterAPIActivitiesLoader(final Context context, final long accountId, final long sinceId, - final long maxId, final List data, final String[] savedStatusesArgs, - final int tabPosition) { - super(context, data, tabPosition); - mContext = context; - mAccountIds = accountId; - mSinceId = sinceId; - mMaxId = maxId; - mSavedStatusesFileArgs = savedStatusesArgs; - } - - @SuppressWarnings("unchecked") - @Override - public final List loadInBackground() { - final File serializationFile = getSerializationFile(); - final List data = getData(); - if (isFirstLoad() && getTabPosition() >= 0 && serializationFile != null) { - final List cached = getCachedData(serializationFile); - if (cached != null) { - data.addAll(cached); - if (mComparator != null) { - Collections.sort(data, mComparator); - } else { - Collections.sort(data); - } - return new CopyOnWriteArrayList<>(data); - } - } - final List activities; - final boolean truncated; - final Context context = getContext(); - final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - final int loadItemLimit = prefs.getInt(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT); - try { - final Paging paging = new Paging(); - paging.setCount(loadItemLimit); - if (mMaxId > 0) { - paging.setMaxId(mMaxId); - } - if (mSinceId > 0) { - paging.setSinceId(mSinceId); - } - activities = new ArrayList<>(); - truncated = truncateActivities(getActivities(getTwitter(), paging), activities, mSinceId); - } catch (final TwitterException e) { - // mHandler.post(new ShowErrorRunnable(e)); - Log.w(LOGTAG, e); - return new CopyOnWriteArrayList<>(data); - } - final Pair position; - if (activities.isEmpty()) { - position = new Pair<>(-1L, -1L); - } else { - final Activity minActivity = Collections.min(activities); - position = new Pair<>(minActivity.getMinPosition(), minActivity.getMaxPosition()); - } - final boolean insertGap = position.first > 0 && position.second > 0 && activities.size() > 1 - && !data.isEmpty() && !truncated; -// mHandler.post(CacheUsersStatusesTask.getRunnable(context, new StatusListResponse(mAccountIds, activities))); - for (final Activity activity : activities) { - final long min = activity.getMinPosition(), max = activity.getMaxPosition(); - final boolean deleted = deleteActivity(data, max); - final boolean isGap = position.first == min && position.second == max && insertGap && !deleted; - data.add(new ParcelableActivity(activity, mAccountIds, isGap)); - } -// final ParcelableActivity[] array = data.toArray(new ParcelableActivity[data.size()]); -// for (int i = 0, size = array.length; i < size; i++) { -// final ParcelableActivity status = array[i]; -// if (shouldFilterActivity(mDatabase, status) && !status.is_gap && i != size - 1) { -// deleteActivity(data, status.max_position); -// } -// } - if (mComparator != null) { - Collections.sort(data, mComparator); - } else { - Collections.sort(data); - } - saveCachedData(serializationFile, data); - return new CopyOnWriteArrayList<>(data); - } - - public final void setComparator(Comparator comparator) { - mComparator = comparator; - } - - protected abstract List getActivities(Twitter twitter, Paging paging) throws TwitterException; - - protected final Twitter getTwitter() { - return TwitterAPIFactory.getTwitterInstance(mContext, mAccountIds, true, true); - } - - protected abstract boolean shouldFilterActivity(final SQLiteDatabase database, final ParcelableActivity activity); - - private List getCachedData(final File file) { - if (file == null) return null; - try { - return JsonSerializer.parseList(file, ParcelableActivity.class); - } catch (final IOException e) { - if (BuildConfig.DEBUG) { - Log.w(LOGTAG, e); - } - } catch (RuntimeException e) { - if (BuildConfig.DEBUG) { - throw e; - } - Log.e(LOGTAG, "Error unserializing data", e); - } - return null; - } - - private File getSerializationFile() { - if (mSavedStatusesFileArgs == null) return null; - try { - return JsonSerializer.getSerializationFile(mContext, mSavedStatusesFileArgs); - } catch (final IOException e) { - return null; - } - } - - private void saveCachedData(final File file, final List data) { - if (file == null || data == null) return; - final SharedPreferences prefs = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - final int databaseItemLimit = prefs.getInt(KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT); - try { - final List activities = data.subList(0, Math.min(databaseItemLimit, data.size())); - LoganSquare.serialize(activities, new FileOutputStream(file), ParcelableActivity.class); - } catch (final IOException e) { - // Ignore - } - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java index 7a3c811ec..4069bdc42 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/TwitterAPIStatusesLoader.java @@ -55,12 +55,13 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader private final Context mContext; private final long mAccountId; private final long mMaxId, mSinceId; + @Nullable private final Object[] mSavedStatusesFileArgs; private Comparator mComparator; public TwitterAPIStatusesLoader(final Context context, final long accountId, final long sinceId, final long maxId, final List data, - final String[] savedStatusesArgs, final int tabPosition, boolean fromUser) { + @Nullable final String[] savedStatusesArgs, final int tabPosition, boolean fromUser) { super(context, data, tabPosition, fromUser); mContext = context; mAccountId = accountId; @@ -105,11 +106,14 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader paging.setMaxId(mMaxId); } if (mSinceId > 0) { - paging.setSinceId(mSinceId - 1); + paging.setSinceId(mSinceId); + if (mMaxId <= 0) { + paging.setLatestResults(true); + } } statuses = new ArrayList<>(); truncated = Utils.truncateStatuses(getStatuses(twitter, paging), statuses, mSinceId); - if (!Utils.isOfficialTwitterInstance(context, twitter)) { + if (!TwitterAPIFactory.isOfficialTwitterInstance(context, twitter)) { TwitterContentUtils.getStatusesWithQuoteData(twitter, statuses); } } catch (final TwitterException e) { @@ -167,6 +171,17 @@ public abstract class TwitterAPIStatusesLoader extends ParcelableStatusesLoader mComparator = comparator; } + public long getSinceId() { + return mSinceId; + } + + public long getMaxId() { + return mMaxId; + } + + public long getAccountId() { + return mAccountId; + } @NonNull protected abstract List getStatuses(@NonNull Twitter twitter, Paging paging) throws TwitterException; diff --git a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserTimelineLoader.java b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserTimelineLoader.java index b3aa89860..e9e11e831 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserTimelineLoader.java +++ b/twidere/src/main/java/org/mariotaku/twidere/loader/support/UserTimelineLoader.java @@ -32,8 +32,9 @@ import org.mariotaku.twidere.api.twitter.model.ResponseList; import org.mariotaku.twidere.api.twitter.model.Status; import org.mariotaku.twidere.api.twitter.Twitter; import org.mariotaku.twidere.api.twitter.TwitterException; +import org.mariotaku.twidere.util.DataStoreUtils; +import org.mariotaku.twidere.util.Utils; -import static org.mariotaku.twidere.util.Utils.getAccountId; import static org.mariotaku.twidere.util.Utils.isFiltered; public class UserTimelineLoader extends TwitterAPIStatusesLoader { @@ -49,7 +50,7 @@ public class UserTimelineLoader extends TwitterAPIStatusesLoader { super(context, accountId, sinceId, maxId, data, savedStatusesArgs, tabPosition, fromUser); mUserId = userId; mUserScreenName = screenName; - mIsMyTimeline = userId > 0 ? accountId == userId : accountId == getAccountId(context, screenName); + mIsMyTimeline = userId > 0 ? accountId == userId : accountId == DataStoreUtils.getAccountId(context, screenName); } @NonNull diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableStatusUtils.java b/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableStatusUtils.java new file mode 100644 index 000000000..c143fd91c --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/model/util/ParcelableStatusUtils.java @@ -0,0 +1,24 @@ +package org.mariotaku.twidere.model.util; + +import android.support.annotation.NonNull; + +import org.mariotaku.twidere.api.twitter.model.Status; +import org.mariotaku.twidere.model.ParcelableStatus; + +/** + * Created by mariotaku on 16/1/3. + */ +public class ParcelableStatusUtils { + + public static void makeOriginalStatus(@NonNull ParcelableStatus status) { + if (!status.is_retweet) return; + status.id = status.retweet_id; + status.retweeted_by_user_id = -1; + status.retweeted_by_user_name = null; + status.retweeted_by_user_screen_name = null; + status.retweeted_by_user_profile_image = null; + status.retweet_timestamp = -1; + status.retweet_id = -1; + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java index 1ca83bee8..1901ac968 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java +++ b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java @@ -86,7 +86,6 @@ import org.mariotaku.twidere.model.AccountPreferences; import org.mariotaku.twidere.model.DraftItem; import org.mariotaku.twidere.model.DraftItemCursorIndices; import org.mariotaku.twidere.model.ParcelableStatus; -import org.mariotaku.twidere.model.ParcelableStatusCursorIndices; import org.mariotaku.twidere.model.StringLongPair; import org.mariotaku.twidere.model.UnreadItem; import org.mariotaku.twidere.provider.TwidereDataStore.Accounts; @@ -96,7 +95,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; -import org.mariotaku.twidere.provider.TwidereDataStore.Mentions; import org.mariotaku.twidere.provider.TwidereDataStore.NetworkUsages; import org.mariotaku.twidere.provider.TwidereDataStore.Preferences; import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches; @@ -108,7 +106,6 @@ import org.mariotaku.twidere.receiver.NotificationReceiver; import org.mariotaku.twidere.service.BackgroundOperationService; import org.mariotaku.twidere.util.AsyncTwitterWrapper; import org.mariotaku.twidere.util.DataStoreUtils; -import org.mariotaku.twidere.util.DatabaseQueryUtils; import org.mariotaku.twidere.util.ImagePreloader; import org.mariotaku.twidere.util.NotificationManagerWrapper; import org.mariotaku.twidere.util.ParseUtils; @@ -146,7 +143,6 @@ import static org.mariotaku.twidere.util.DataStoreUtils.getTableId; import static org.mariotaku.twidere.util.DataStoreUtils.getTableNameById; import static org.mariotaku.twidere.util.Utils.clearAccountColor; import static org.mariotaku.twidere.util.Utils.clearAccountName; -import static org.mariotaku.twidere.util.Utils.getAccountIds; import static org.mariotaku.twidere.util.Utils.getNotificationUri; import static org.mariotaku.twidere.util.Utils.isNotificationsSilent; @@ -1215,7 +1211,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta switch (tableId) { case TABLE_ID_STATUSES: { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - getAccountIds(context)); + DataStoreUtils.getAccountIds(context)); for (final AccountPreferences pref : prefs) { if (!pref.isHomeTimelineNotificationEnabled()) continue; showTimelineNotification(pref, getPositionTag(TAB_TYPE_HOME_TIMELINE, pref.getAccountId())); @@ -1225,7 +1221,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } case TABLE_ID_ACTIVITIES_ABOUT_ME: { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - getAccountIds(context)); + DataStoreUtils.getAccountIds(context)); final boolean combined = mPreferences.getBoolean(KEY_COMBINED_NOTIFICATIONS); for (final AccountPreferences pref : prefs) { if (!pref.isInteractionsNotificationEnabled()) continue; @@ -1236,7 +1232,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } case TABLE_ID_DIRECT_MESSAGES_INBOX: { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - getAccountIds(context)); + DataStoreUtils.getAccountIds(context)); for (final AccountPreferences pref : prefs) { if (!pref.isDirectMessagesNotificationEnabled()) continue; final StringLongPair[] pairs = mReadStateManager.getPositionPairs(TAB_TYPE_DIRECT_MESSAGES); @@ -1443,7 +1439,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta final int usersCount = userCursor.getCount(); final int messagesCount = messageCursor.getCount(); if (messagesCount == 0 || usersCount == 0) return; - final String accountName = Utils.getAccountName(context, accountId); + final String accountName = DataStoreUtils.getAccountName(context, accountId); final String accountScreenName = DataStoreUtils.getAccountScreenName(context, accountId); final int idxMessageText = messageCursor.getColumnIndex(DirectMessages.TEXT_UNESCAPED), idxMessageTimestamp = messageCursor.getColumnIndex(DirectMessages.MESSAGE_TIMESTAMP), diff --git a/twidere/src/main/java/org/mariotaku/twidere/receiver/ConnectivityStateReceiver.java b/twidere/src/main/java/org/mariotaku/twidere/receiver/ConnectivityStateReceiver.java index e432d04bd..aaa560651 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/receiver/ConnectivityStateReceiver.java +++ b/twidere/src/main/java/org/mariotaku/twidere/receiver/ConnectivityStateReceiver.java @@ -31,12 +31,12 @@ import org.mariotaku.twidere.BuildConfig; import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.app.TwidereApplication; import org.mariotaku.twidere.util.ConnectivityUtils; +import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.util.net.NetworkUsageUtils; import edu.tsinghua.hotmobi.HotMobiLogger; import edu.tsinghua.hotmobi.UploadLogsTask; import edu.tsinghua.hotmobi.model.NetworkEvent; -import edu.tsinghua.spice.Utilies.SpiceProfilingUtil; import static org.mariotaku.twidere.util.Utils.startRefreshServiceIfNeeded; @@ -64,7 +64,7 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons final int networkType = ConnectivityUtils.getActiveNetworkType(context.getApplicationContext()); NetworkUsageUtils.setNetworkType(networkType); final boolean isWifi = networkType == ConnectivityManager.TYPE_WIFI; - final boolean isCharging = SpiceProfilingUtil.isCharging(context.getApplicationContext()); + final boolean isCharging = Utils.isCharging(context.getApplicationContext()); if (isWifi && isCharging) { final long currentTime = System.currentTimeMillis(); final long lastSuccessfulTime = HotMobiLogger.getLastUploadTime(context); diff --git a/twidere/src/main/java/org/mariotaku/twidere/service/RefreshService.java b/twidere/src/main/java/org/mariotaku/twidere/service/RefreshService.java index 9569c1f71..469022c33 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/service/RefreshService.java +++ b/twidere/src/main/java/org/mariotaku/twidere/service/RefreshService.java @@ -49,7 +49,6 @@ import javax.inject.Inject; import edu.tsinghua.hotmobi.HotMobiLogger; import edu.tsinghua.hotmobi.model.ScreenEvent; -import static org.mariotaku.twidere.util.Utils.getAccountIds; import static org.mariotaku.twidere.util.Utils.getDefaultAccountId; import static org.mariotaku.twidere.util.Utils.hasAutoRefreshAccounts; import static org.mariotaku.twidere.util.Utils.isBatteryOkay; @@ -84,7 +83,7 @@ public class RefreshService extends Service implements Constants { } else if (BROADCAST_RESCHEDULE_TRENDS_REFRESHING.equals(action)) { rescheduleTrendsRefreshing(); } else if (isAutoRefreshAllowed()) { - final long[] accountIds = getAccountIds(context); + final long[] accountIds = DataStoreUtils.getAccountIds(context); final AccountPreferences[] accountPrefs = AccountPreferences.getAccountPreferences(context, accountIds); if (BROADCAST_REFRESH_HOME_TIMELINE.equals(action)) { final long[] refreshIds = getRefreshableIds(accountPrefs, new HomeRefreshableFilter()); diff --git a/twidere/src/main/java/org/mariotaku/twidere/task/twitter/GetStatusesTask.java b/twidere/src/main/java/org/mariotaku/twidere/task/twitter/GetStatusesTask.java index 30036359e..444f014ce 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/task/twitter/GetStatusesTask.java +++ b/twidere/src/main/java/org/mariotaku/twidere/task/twitter/GetStatusesTask.java @@ -72,7 +72,8 @@ public abstract class GetStatusesTask extends ManagedAsyncTask statuses, long maxId, boolean truncated, boolean notify) { + private void storeStatus(long accountId, List statuses, + long maxId, boolean notify, int loadItemLimit) { if (statuses == null || statuses.isEmpty() || accountId <= 0) { return; } @@ -96,7 +97,8 @@ public abstract class GetStatusesTask extends ManagedAsyncTask 0 && ArrayUtils.contains(statusIds, maxId); final boolean noRowsDeleted = rowsDeleted == 0; - final boolean insertGap = minId > 0 && (noRowsDeleted || deletedOldGap) && !truncated - && !noItemsBefore && statuses.size() > 1; + final boolean insertGap = minId > 0 && (noRowsDeleted || deletedOldGap) && !noItemsBefore + && statuses.size() >= loadItemLimit; if (insertGap && minIdx != -1) { values[minIdx].put(TwidereDataStore.Statuses.IS_GAP, true); } @@ -181,10 +183,9 @@ public abstract class GetStatusesTask extends ManagedAsyncTask statuses = new ArrayList<>(); - final boolean truncated = Utils.truncateStatuses(getStatuses(twitter, paging), statuses, sinceId); + final List statuses = getStatuses(twitter, paging); TwitterContentUtils.getStatusesWithQuoteData(twitter, statuses); - storeStatus(accountId, statuses, maxId, truncated, true); + storeStatus(accountId, statuses, maxId, true, loadItemLimit); publishProgress(new TwitterWrapper.StatusListResponse(accountId, statuses)); } catch (final TwitterException e) { Log.w(LOGTAG, e); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java b/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java index 964a53fb0..163893203 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java @@ -116,7 +116,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper { private final UserColorNameManager mUserColorNameManager; private final ReadStateManager mReadStateManager; - private int mGetHomeTimelineTaskId, mGetMentionsTaskId; private int mGetReceivedDirectMessagesTaskId, mGetSentDirectMessagesTaskId; private int mGetLocalTrendsTaskId; @@ -282,9 +281,8 @@ public class AsyncTwitterWrapper extends TwitterWrapper { } public boolean getHomeTimelineAsync(final long[] accountIds, final long[] max_ids, final long[] since_ids) { - mAsyncTaskManager.cancel(mGetHomeTimelineTaskId); - final GetHomeTimelineTask task = new GetHomeTimelineTask(accountIds, max_ids, since_ids); - mGetHomeTimelineTaskId = mAsyncTaskManager.add(task, true); + final GetHomeTimelineTask task = new GetHomeTimelineTask(this, accountIds, max_ids, since_ids); + mAsyncTaskManager.add(task, true); return true; } @@ -544,7 +542,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper { @Override protected ResponseList getActivities(long accountId, Twitter twitter, Paging paging) throws TwitterException { - if (Utils.isOfficialKeyAccount(getContext(), accountId)) { + if (TwitterAPIFactory.isOfficialKeyAccount(getContext(), accountId)) { return twitter.getActivitiesAboutMe(paging); } final ResponseList activities = new ResponseList<>(); @@ -588,7 +586,7 @@ public class AsyncTwitterWrapper extends TwitterWrapper { public Object doLongOperation(Object o) throws InterruptedException { for (long accountId : accountIds) { Twitter twitter = TwitterAPIFactory.getTwitterInstance(mContext, accountId, false); - if (Utils.isOfficialTwitterInstance(mContext, twitter)) continue; + if (TwitterAPIFactory.isOfficialTwitterInstance(mContext, twitter)) continue; try { twitter.setActivitiesAboutMeUnread(cursor); } catch (TwitterException e) { @@ -2125,10 +2123,13 @@ public class AsyncTwitterWrapper extends TwitterWrapper { } - class GetHomeTimelineTask extends GetStatusesTask { + static class GetHomeTimelineTask extends GetStatusesTask { - public GetHomeTimelineTask(final long[] accountIds, final long[] maxIds, final long[] sinceIds) { - super(AsyncTwitterWrapper.this, accountIds, maxIds, sinceIds, TASK_TAG_GET_HOME_TIMELINE); + private AsyncTwitterWrapper twitterWrapper; + + public GetHomeTimelineTask(AsyncTwitterWrapper asyncTwitterWrapper, final long[] accountIds, final long[] maxIds, final long[] sinceIds) { + super(asyncTwitterWrapper, accountIds, maxIds, sinceIds, TASK_TAG_GET_HOME_TIMELINE); + this.twitterWrapper = asyncTwitterWrapper; } @Override @@ -2152,13 +2153,12 @@ public class AsyncTwitterWrapper extends TwitterWrapper { @Override protected void onPostExecute(final List result) { super.onPostExecute(result); - mGetHomeTimelineTaskId = -1; } @Override protected void onPreExecute() { final Intent intent = new Intent(BROADCAST_RESCHEDULE_HOME_TIMELINE_REFRESHING); - mContext.sendBroadcast(intent); + twitterWrapper.getContext().sendBroadcast(intent); super.onPreExecute(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java index e07f569e3..c6f3ad475 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java @@ -23,10 +23,12 @@ import android.content.ContentResolver; import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; +import android.graphics.Color; import android.net.Uri; import android.support.annotation.NonNull; import android.support.v4.util.LongSparseArray; +import org.apache.commons.lang3.ArrayUtils; import org.mariotaku.sqliteqb.library.Columns; import org.mariotaku.sqliteqb.library.Expression; import org.mariotaku.sqliteqb.library.RawItemArray; @@ -70,6 +72,7 @@ import static android.text.TextUtils.isEmpty; */ public class DataStoreUtils implements Constants { static final UriMatcher CONTENT_PROVIDER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); + static LongSparseArray sAccountColors = new LongSparseArray<>(); static { CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Accounts.CONTENT_PATH, @@ -714,4 +717,122 @@ public class DataStoreUtils implements Constants { } return filterExpression; } + + public static int getAccountColor(final Context context, final long accountId) { + if (context == null) return Color.TRANSPARENT; + final Integer cached = sAccountColors.get(accountId); + if (cached != null) return cached; + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, + new String[]{Accounts.COLOR}, Expression.equals(Accounts.ACCOUNT_ID, accountId).getSQL(), + null, null); + if (cur == null) return Color.TRANSPARENT; + try { + if (cur.getCount() > 0 && cur.moveToFirst()) { + final int color = cur.getInt(0); + sAccountColors.put(accountId, color); + return color; + } + return Color.TRANSPARENT; + } finally { + cur.close(); + } + } + + public static int[] getAccountColors(final Context context, final long[] accountIds) { + if (context == null || accountIds == null) return new int[0]; + final String[] cols = new String[]{Accounts.ACCOUNT_ID, Accounts.COLOR}; + final String where = Expression.in(new Columns.Column(Accounts.ACCOUNT_ID), new RawItemArray(accountIds)).getSQL(); + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, cols, where, + null, null); + if (cur == null) return new int[0]; + try { + final int[] colors = new int[cur.getCount()]; + for (int i = 0, j = cur.getCount(); i < j; i++) { + cur.moveToPosition(i); + colors[ArrayUtils.indexOf(accountIds, cur.getLong(0))] = cur.getInt(1); + } + return colors; + } finally { + cur.close(); + } + } + + public static String getAccountDisplayName(final Context context, final long accountId, final boolean nameFirst) { + final String name; + if (nameFirst) { + name = getAccountName(context, accountId); + } else { + name = String.format("@%s", getAccountScreenName(context, accountId)); + } + return name; + } + + public static long getAccountId(final Context context, final String screenName) { + if (context == null || isEmpty(screenName)) return -1; + final Cursor cur = ContentResolverUtils + .query(context.getContentResolver(), Accounts.CONTENT_URI, new String[]{Accounts.ACCOUNT_ID}, + Expression.equalsArgs(Accounts.SCREEN_NAME).getSQL(), new String[]{screenName}, null); + if (cur == null) return -1; + try { + if (cur.getCount() > 0 && cur.moveToFirst()) return cur.getLong(0); + return -1; + } finally { + cur.close(); + } + } + + @NonNull + public static long[] getAccountIds(final Context context) { + if (context == null) return new long[0]; + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, + new String[]{Accounts.ACCOUNT_ID}, null, null, null); + if (cur == null) return new long[0]; + try { + cur.moveToFirst(); + final long[] ids = new long[cur.getCount()]; + int i = 0; + while (!cur.isAfterLast()) { + ids[i++] = cur.getLong(0); + cur.moveToNext(); + } + return ids; + } finally { + cur.close(); + } + } + + public static boolean hasAccount(final Context context) { + if (context == null) return false; + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, + new String[]{SQLFunctions.COUNT()}, null, null, null); + try { + cur.moveToFirst(); + return cur.getInt(0) > 0; + } finally { + cur.close(); + } + } + + public static String getAccountName(final Context context, final long accountId) { + if (context == null) return null; + final String cached = sAccountNames.get(accountId); + if (!isEmpty(cached)) return cached; + final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, + new String[]{Accounts.NAME}, Accounts.ACCOUNT_ID + " = " + accountId, null, null); + if (cur == null) return null; + try { + if (cur.getCount() > 0 && cur.moveToFirst()) { + final String name = cur.getString(0); + sAccountNames.put(accountId, name); + return name; + } + return null; + } finally { + cur.close(); + } + } + + public static String[] getAccountNames(final Context context) { + return getAccountScreenNames(context, null); + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/ParcelUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/ParcelUtils.java new file mode 100644 index 000000000..b6f7359e8 --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/util/ParcelUtils.java @@ -0,0 +1,33 @@ +package org.mariotaku.twidere.util; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +import java.lang.reflect.Field; + +/** + * Created by mariotaku on 16/1/3. + */ +public class ParcelUtils { + + @Nullable + public static T clone(@Nullable T object) { + if (object == null) return null; + final Parcel parcel = Parcel.obtain(); + try { + object.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final Field creatorField = object.getClass().getDeclaredField("CREATOR"); + //noinspection unchecked + final Parcelable.Creator creator = (Parcelable.Creator) creatorField.get(null); + return creator.createFromParcel(parcel); + } catch (NoSuchFieldException e) { + throw new NoSuchFieldError("Missing CREATOR field"); + } catch (IllegalAccessException e) { + throw new IllegalAccessError("Can't access CREATOR field"); + } finally { + parcel.recycle(); + } + } +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/TwitterAPIFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/TwitterAPIFactory.java index 9b3ec72a7..33303c0b0 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/TwitterAPIFactory.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/TwitterAPIFactory.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.database.Cursor; import android.net.SSLCertificateSocketFactory; import android.os.Build; import android.support.annotation.NonNull; @@ -25,6 +26,7 @@ import org.mariotaku.restfu.HttpRequestFactory; import org.mariotaku.restfu.Pair; import org.mariotaku.restfu.RequestInfoFactory; import org.mariotaku.restfu.RestAPIFactory; +import org.mariotaku.restfu.RestClient; import org.mariotaku.restfu.RestMethodInfo; import org.mariotaku.restfu.RestRequestInfo; import org.mariotaku.restfu.annotation.RestMethod; @@ -38,6 +40,7 @@ import org.mariotaku.restfu.http.RestHttpResponse; import org.mariotaku.restfu.http.mime.StringTypedData; import org.mariotaku.restfu.http.mime.TypedData; import org.mariotaku.restfu.okhttp.OkHttpRestClient; +import org.mariotaku.sqliteqb.library.Expression; import org.mariotaku.twidere.BuildConfig; import org.mariotaku.twidere.TwidereConstants; import org.mariotaku.twidere.api.twitter.Twitter; @@ -50,11 +53,13 @@ import org.mariotaku.twidere.api.twitter.auth.BasicAuthorization; import org.mariotaku.twidere.api.twitter.auth.EmptyAuthorization; import org.mariotaku.twidere.api.twitter.auth.OAuthAuthorization; import org.mariotaku.twidere.api.twitter.auth.OAuthEndpoint; +import org.mariotaku.twidere.api.twitter.auth.OAuthSupport; import org.mariotaku.twidere.api.twitter.auth.OAuthToken; import org.mariotaku.twidere.api.twitter.util.TwitterConverter; import org.mariotaku.twidere.model.ConsumerKeyType; import org.mariotaku.twidere.model.ParcelableCredentials; import org.mariotaku.twidere.model.RequestType; +import org.mariotaku.twidere.provider.TwidereDataStore; import org.mariotaku.twidere.util.dagger.DependencyHolder; import org.mariotaku.twidere.util.net.InetAddressUtils; import org.mariotaku.twidere.util.net.NetworkUsageUtils; @@ -483,6 +488,36 @@ public class TwitterAPIFactory implements TwidereConstants { return ('A' <= codePoint && codePoint <= 'Z') || ('a' <= codePoint && codePoint <= 'z') || '0' <= codePoint && codePoint <= '9'; } + public static boolean isOfficialKeyAccount(final Context context, final long accountId) { + return getOfficialKeyType(context, accountId) != ConsumerKeyType.UNKNOWN; + } + + @NonNull + public static ConsumerKeyType getOfficialKeyType(final Context context, final long accountId) { + if (context == null) return ConsumerKeyType.UNKNOWN; + final String[] projection = {TwidereDataStore.Accounts.CONSUMER_KEY, TwidereDataStore.Accounts.CONSUMER_SECRET}; + final String selection = Expression.equals(TwidereDataStore.Accounts.ACCOUNT_ID, accountId).getSQL(); + final Cursor c = context.getContentResolver().query(TwidereDataStore.Accounts.CONTENT_URI, projection, selection, null, null); + //noinspection TryFinallyCanBeTryWithResources + try { + if (c.moveToPosition(0)) + return TwitterContentUtils.getOfficialKeyType(context, c.getString(0), c.getString(1)); + } finally { + c.close(); + } + return ConsumerKeyType.UNKNOWN; + } + + public static boolean isOfficialTwitterInstance(final Context context, final Twitter twitter) { + if (context == null || twitter == null) return false; + final RestClient restClient = RestAPIFactory.getRestClient(twitter); + final Authorization auth = restClient.getAuthorization(); + if (!(auth instanceof OAuthSupport)) return false; + final String consumerKey = ((OAuthSupport) auth).getConsumerKey(); + final String consumerSecret = ((OAuthSupport) auth).getConsumerSecret(); + return TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret); + } + public static class Options { final HashMap extras = new HashMap<>(); diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index d7aa0e819..b051e8b54 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -19,6 +19,7 @@ package org.mariotaku.twidere.util; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; @@ -74,7 +75,6 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.ListFragment; import android.support.v4.content.ContextCompat; -import android.support.v4.util.LongSparseArray; import android.support.v4.util.Pair; import android.support.v4.view.ActionProvider; import android.support.v4.view.GravityCompat; @@ -116,16 +116,12 @@ import com.bluelinelabs.logansquare.LoganSquare; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.math.NumberUtils; import org.json.JSONException; -import org.mariotaku.restfu.RestAPIFactory; -import org.mariotaku.restfu.RestClient; -import org.mariotaku.restfu.http.Authorization; import org.mariotaku.sqliteqb.library.AllColumns; import org.mariotaku.sqliteqb.library.Columns; import org.mariotaku.sqliteqb.library.Columns.Column; import org.mariotaku.sqliteqb.library.Expression; import org.mariotaku.sqliteqb.library.OrderBy; import org.mariotaku.sqliteqb.library.RawItemArray; -import org.mariotaku.sqliteqb.library.SQLFunctions; import org.mariotaku.sqliteqb.library.Selectable; import org.mariotaku.sqliteqb.library.Tables; import org.mariotaku.sqliteqb.library.query.SQLSelectQuery; @@ -140,7 +136,6 @@ import org.mariotaku.twidere.adapter.iface.IBaseAdapter; import org.mariotaku.twidere.adapter.iface.IBaseCardAdapter; import org.mariotaku.twidere.api.twitter.Twitter; import org.mariotaku.twidere.api.twitter.TwitterException; -import org.mariotaku.twidere.api.twitter.auth.OAuthSupport; import org.mariotaku.twidere.api.twitter.model.DirectMessage; import org.mariotaku.twidere.api.twitter.model.GeoLocation; import org.mariotaku.twidere.api.twitter.model.RateLimitStatus; @@ -186,7 +181,6 @@ import org.mariotaku.twidere.graphic.ActionIconDrawable; import org.mariotaku.twidere.graphic.PaddingDrawable; import org.mariotaku.twidere.menu.SupportStatusShareProvider; import org.mariotaku.twidere.model.AccountPreferences; -import org.mariotaku.twidere.model.ConsumerKeyType; import org.mariotaku.twidere.model.ParcelableAccount; import org.mariotaku.twidere.model.ParcelableCredentials; import org.mariotaku.twidere.model.ParcelableCredentialsCursorIndices; @@ -303,8 +297,6 @@ public final class Utils implements Constants { } - private static LongSparseArray sAccountColors = new LongSparseArray<>(); - private Utils() { throw new AssertionError("You are trying to create an instance for this utility class!"); } @@ -457,7 +449,7 @@ public final class Utils implements Constants { final int itemLimit = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).getInt( KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT); - for (final long accountId : getAccountIds(context)) { + for (final long accountId : DataStoreUtils.getAccountIds(context)) { // Clean statuses. for (final Uri uri : STATUSES_URIS) { if (CachedStatuses.CONTENT_URI.equals(uri)) { @@ -500,7 +492,7 @@ public final class Utils implements Constants { } public static void clearAccountColor() { - sAccountColors.clear(); + DataStoreUtils.sAccountColors.clear(); } public static void clearAccountName() { @@ -909,7 +901,7 @@ public final class Utils implements Constants { } else { final String paramAccountName = uri.getQueryParameter(QUERY_PARAM_ACCOUNT_NAME); if (paramAccountName != null) { - args.putLong(EXTRA_ACCOUNT_ID, getAccountId(context, paramAccountName)); + args.putLong(EXTRA_ACCOUNT_ID, DataStoreUtils.getAccountId(context, paramAccountName)); } else { final long accountId = getDefaultAccountId(context); if (isMyAccount(context, accountId)) { @@ -1100,124 +1092,6 @@ public final class Utils implements Constants { return DateUtils.formatDateTime(context, timestamp, format_flags); } - public static int getAccountColor(final Context context, final long accountId) { - if (context == null) return Color.TRANSPARENT; - final Integer cached = sAccountColors.get(accountId); - if (cached != null) return cached; - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, - new String[]{Accounts.COLOR}, Expression.equals(Accounts.ACCOUNT_ID, accountId).getSQL(), - null, null); - if (cur == null) return Color.TRANSPARENT; - try { - if (cur.getCount() > 0 && cur.moveToFirst()) { - final int color = cur.getInt(0); - sAccountColors.put(accountId, color); - return color; - } - return Color.TRANSPARENT; - } finally { - cur.close(); - } - } - - public static int[] getAccountColors(final Context context, final long[] accountIds) { - if (context == null || accountIds == null) return new int[0]; - final String[] cols = new String[]{Accounts.ACCOUNT_ID, Accounts.COLOR}; - final String where = Expression.in(new Column(Accounts.ACCOUNT_ID), new RawItemArray(accountIds)).getSQL(); - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, cols, where, - null, null); - if (cur == null) return new int[0]; - try { - final int[] colors = new int[cur.getCount()]; - for (int i = 0, j = cur.getCount(); i < j; i++) { - cur.moveToPosition(i); - colors[ArrayUtils.indexOf(accountIds, cur.getLong(0))] = cur.getInt(1); - } - return colors; - } finally { - cur.close(); - } - } - - public static String getAccountDisplayName(final Context context, final long accountId, final boolean nameFirst) { - final String name; - if (nameFirst) { - name = getAccountName(context, accountId); - } else { - name = String.format("@%s", DataStoreUtils.getAccountScreenName(context, accountId)); - } - return name; - } - - public static long getAccountId(final Context context, final String screenName) { - if (context == null || isEmpty(screenName)) return -1; - final Cursor cur = ContentResolverUtils - .query(context.getContentResolver(), Accounts.CONTENT_URI, new String[]{Accounts.ACCOUNT_ID}, - Expression.equalsArgs(Accounts.SCREEN_NAME).getSQL(), new String[]{screenName}, null); - if (cur == null) return -1; - try { - if (cur.getCount() > 0 && cur.moveToFirst()) return cur.getLong(0); - return -1; - } finally { - cur.close(); - } - } - - @NonNull - public static long[] getAccountIds(final Context context) { - if (context == null) return new long[0]; - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, - new String[]{Accounts.ACCOUNT_ID}, null, null, null); - if (cur == null) return new long[0]; - try { - cur.moveToFirst(); - final long[] ids = new long[cur.getCount()]; - int i = 0; - while (!cur.isAfterLast()) { - ids[i++] = cur.getLong(0); - cur.moveToNext(); - } - return ids; - } finally { - cur.close(); - } - } - - public static boolean hasAccount(final Context context) { - if (context == null) return false; - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, - new String[]{SQLFunctions.COUNT()}, null, null, null); - try { - cur.moveToFirst(); - return cur.getInt(0) > 0; - } finally { - cur.close(); - } - } - - public static String getAccountName(final Context context, final long accountId) { - if (context == null) return null; - final String cached = DataStoreUtils.sAccountNames.get(accountId); - if (!isEmpty(cached)) return cached; - final Cursor cur = ContentResolverUtils.query(context.getContentResolver(), Accounts.CONTENT_URI, - new String[]{Accounts.NAME}, Accounts.ACCOUNT_ID + " = " + accountId, null, null); - if (cur == null) return null; - try { - if (cur.getCount() > 0 && cur.moveToFirst()) { - final String name = cur.getString(0); - DataStoreUtils.sAccountNames.put(accountId, name); - return name; - } - return null; - } finally { - cur.close(); - } - } - - public static String[] getAccountNames(final Context context) { - return DataStoreUtils.getAccountScreenNames(context, null); - } - public static int getAccountNotificationId(final int notificationType, final long accountId) { return Arrays.hashCode(new long[]{notificationType, accountId}); } @@ -1385,7 +1259,7 @@ public final class Utils implements Constants { if (context == null) return -1; final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); final long accountId = prefs.getLong(KEY_DEFAULT_ACCOUNT_ID, -1); - final long[] accountIds = Utils.getAccountIds(context); + final long[] accountIds = DataStoreUtils.getAccountIds(context); if (accountIds.length > 0 && !ArrayUtils.contains(accountIds, accountId) && accountIds.length > 0) { /* TODO: this is just a quick fix */ return accountIds[0]; @@ -1816,7 +1690,7 @@ public final class Utils implements Constants { } public static boolean hasAutoRefreshAccounts(final Context context) { - final long[] accountIds = getAccountIds(context); + final long[] accountIds = DataStoreUtils.getAccountIds(context); return !ArrayUtils.isEmpty(AccountPreferences.getAutoRefreshEnabledAccountIds(context, accountIds)); } @@ -1832,7 +1706,7 @@ public final class Utils implements Constants { final int id_idx = cur.getColumnIndex(Accounts.ACCOUNT_ID), color_idx = cur.getColumnIndex(Accounts.COLOR); cur.moveToFirst(); while (!cur.isAfterLast()) { - sAccountColors.put(cur.getLong(id_idx), cur.getInt(color_idx)); + DataStoreUtils.sAccountColors.put(cur.getLong(id_idx), cur.getInt(color_idx)); cur.moveToNext(); } cur.close(); @@ -1980,36 +1854,6 @@ public final class Utils implements Constants { return prefs.getBoolean("silent_notifications_at_" + now.get(Calendar.HOUR_OF_DAY), false); } - public static boolean isOfficialKeyAccount(final Context context, final long accountId) { - return getOfficialKeyType(context, accountId) != ConsumerKeyType.UNKNOWN; - } - - @NonNull - public static ConsumerKeyType getOfficialKeyType(final Context context, final long accountId) { - if (context == null) return ConsumerKeyType.UNKNOWN; - final String[] projection = {Accounts.CONSUMER_KEY, Accounts.CONSUMER_SECRET}; - final String selection = Expression.equals(Accounts.ACCOUNT_ID, accountId).getSQL(); - final Cursor c = context.getContentResolver().query(Accounts.CONTENT_URI, projection, selection, null, null); - //noinspection TryFinallyCanBeTryWithResources - try { - if (c.moveToPosition(0)) - return TwitterContentUtils.getOfficialKeyType(context, c.getString(0), c.getString(1)); - } finally { - c.close(); - } - return ConsumerKeyType.UNKNOWN; - } - - public static boolean isOfficialTwitterInstance(final Context context, final Twitter twitter) { - if (context == null || twitter == null) return false; - final RestClient restClient = RestAPIFactory.getRestClient(twitter); - final Authorization auth = restClient.getAuthorization(); - if (!(auth instanceof OAuthSupport)) return false; - final String consumerKey = ((OAuthSupport) auth).getConsumerKey(); - final String consumerSecret = ((OAuthSupport) auth).getConsumerSecret(); - return TwitterContentUtils.isOfficialKey(context, consumerKey, consumerSecret); - } - public static boolean isRedirected(final int code) { return code == 301 || code == 302 || code == 307; } @@ -2025,7 +1869,7 @@ public final class Utils implements Constants { public static boolean isUserLoggedIn(final Context context, final long accountId) { if (context == null) return false; - final long[] ids = getAccountIds(context); + final long[] ids = DataStoreUtils.getAccountIds(context); if (ids == null) return false; for (final long id : ids) { if (id == accountId) return true; @@ -3056,18 +2900,6 @@ public final class Utils implements Constants { return in.size() != out.size(); } - public static boolean truncateActivities(final List in, final List out, - final long sinceId) { - if (in == null) return false; - for (final org.mariotaku.twidere.api.twitter.model.Activity status : in) { - if (sinceId > 0 && status.getMaxPosition() <= sinceId) { - continue; - } - out.add(status); - } - return in.size() != out.size(); - } - public static void updateRelationship(Context context, Relationship relationship, long accountId) { final ContentResolver resolver = context.getContentResolver(); final ContentValues values = ContentValuesCreator.createCachedRelationship(relationship, accountId); @@ -3403,6 +3235,17 @@ public final class Utils implements Constants { return result; } + @SuppressLint("InlinedApi") + public static boolean isCharging(final Context context) { + if (context == null) return false; + final Intent intent = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + if (intent == null) return false; + final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return plugged == BatteryManager.BATTERY_PLUGGED_AC + || plugged == BatteryManager.BATTERY_PLUGGED_USB + || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; + } + static class UtilsL { @TargetApi(Build.VERSION_CODES.LOLLIPOP) diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/MessageEntryViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/MessageEntryViewHolder.java index 3dca8a5c7..de2336a58 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/MessageEntryViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/MessageEntryViewHolder.java @@ -31,9 +31,9 @@ import android.widget.TextView; import org.mariotaku.twidere.R; import org.mariotaku.twidere.adapter.MessageEntriesAdapter; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.ConversationEntries; +import org.mariotaku.twidere.util.DataStoreUtils; import org.mariotaku.twidere.util.MediaLoaderWrapper; import org.mariotaku.twidere.util.UserColorNameManager; -import org.mariotaku.twidere.util.Utils; import org.mariotaku.twidere.view.ShortTimeView; import org.mariotaku.twidere.view.iface.IColorLabelView; @@ -88,7 +88,7 @@ public class MessageEntryViewHolder extends ViewHolder implements OnClickListene screenNameView.setTypeface(null, isUnread && !isOutgoing ? Typeface.BOLD : Typeface.NORMAL); textView.setTypeface(null, isUnread && !isOutgoing ? Typeface.BOLD : Typeface.NORMAL); if (adapter.shouldShowAccountsColor()) { - content.drawEnd(Utils.getAccountColor(context, accountId)); + content.drawEnd(DataStoreUtils.getAccountColor(context, accountId)); } else { content.drawEnd(); } diff --git a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java index dfa82b5bd..cc8a27cbb 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java +++ b/twidere/src/main/java/org/mariotaku/twidere/view/holder/StatusViewHolder.java @@ -22,6 +22,7 @@ import org.mariotaku.twidere.model.ParcelableLocation; import org.mariotaku.twidere.model.ParcelableMedia; import org.mariotaku.twidere.model.ParcelableStatus; import org.mariotaku.twidere.util.AsyncTwitterWrapper; +import org.mariotaku.twidere.util.DataStoreUtils; import org.mariotaku.twidere.util.HtmlSpanBuilder; import org.mariotaku.twidere.util.MediaLoaderWrapper; import org.mariotaku.twidere.util.MediaLoadingHandler; @@ -236,7 +237,7 @@ public class StatusViewHolder extends ViewHolder implements Constants, OnClickLi } if (adapter.shouldShowAccountsColor()) { - itemContent.drawEnd(Utils.getAccountColor(context, status.account_id)); + itemContent.drawEnd(DataStoreUtils.getAccountColor(context, status.account_id)); } else { itemContent.drawEnd(); }