removed action mode view

limits disk cache to 384 mb, it will be configurable in the future
This commit is contained in:
Mariotaku Lee 2015-08-28 18:16:41 +08:00
parent 470ecc3726
commit 939c78e5cb
37 changed files with 741 additions and 250 deletions

View File

@ -7,10 +7,10 @@ android:
- tools
# The BuildTools version used by your project
- build-tools-22.0.1
- build-tools-23.0.0
# The SDK version used to compile your project
- android-22
- android-23
# Additional components
- extra-google-google_play_services

View File

@ -39,11 +39,11 @@ android {
dependencies {
apt 'com.bluelinelabs:logansquare-compiler:1.1.0'
apt 'com.hannesdorfmann.parcelableplease:processor:1.0.1'
compile 'com.android.support:support-annotations:22.2.1'
compile 'com.android.support:support-v4:22.2.1'
compile 'com.android.support:support-annotations:23.0.0'
compile 'com.android.support:support-v4:23.0.0'
compile 'com.bluelinelabs:logansquare:1.1.0'
compile 'org.apache.commons:commons-lang3:3.4'
compile 'com.github.mariotaku:RestFu:0.9'
compile 'com.github.mariotaku:RestFu:0.9.1'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
compile 'com.github.mariotaku:SQLiteQB:ef3f596199'
compile fileTree(dir: 'libs', include: ['*.jar'])

View File

@ -912,12 +912,14 @@ public interface TwidereDataStore {
String MUTING = "muting";
String RETWEET_ENABLED = "retweet_enabled";
String[] COLUMNS = {_ID, ACCOUNT_ID, USER_ID, FOLLOWING, FOLLOWED_BY, BLOCKING,
BLOCKED_BY, MUTING};
BLOCKED_BY, MUTING, RETWEET_ENABLED};
String[] TYPES = {TYPE_PRIMARY_KEY, TYPE_INT, TYPE_INT, TYPE_BOOLEAN_DEFAULT_FALSE,
TYPE_BOOLEAN_DEFAULT_FALSE, TYPE_BOOLEAN_DEFAULT_FALSE, TYPE_BOOLEAN_DEFAULT_FALSE,
TYPE_BOOLEAN_DEFAULT_FALSE};
TYPE_BOOLEAN_DEFAULT_FALSE, TYPE_BOOLEAN_DEFAULT_TRUE};
}
interface UnreadCounts extends BaseColumns {

View File

@ -19,27 +19,33 @@
package edu.tsinghua.hotmobi;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.text.TextUtils;
import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.util.Utils;
import java.io.IOException;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import edu.tsinghua.hotmobi.model.BaseEvent;
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.RefreshEvent;
import edu.tsinghua.hotmobi.model.ScrollRecord;
import edu.tsinghua.hotmobi.model.SessionEvent;
import edu.tsinghua.hotmobi.model.TweetEvent;
@ -50,15 +56,25 @@ public class HotMobiLogger {
public static final long ACCOUNT_ID_NOT_NEEDED = -1;
private static final String LOGTAG = "HotMobiLogger";
public static final String LOGTAG = "HotMobiLogger";
public static final long UPLOAD_INTERVAL_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
public static final String LAST_UPLOAD_TIME = "last_upload_time";
final static SimpleDateFormat DATE_FORMAT;
static {
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private final Application mApplication;
private final Executor mExecutor;
public HotMobiLogger() {
public HotMobiLogger(Application application) {
mApplication = application;
mExecutor = Executors.newSingleThreadExecutor();
}
private static String getLogFilename(BaseEvent event) {
public static String getLogFilename(Object event) {
if (event instanceof RefreshEvent) {
return "refresh";
} else if (event instanceof SessionEvent) {
@ -67,8 +83,14 @@ public class HotMobiLogger {
return "tweet";
} else if (event instanceof MediaEvent) {
return "media";
} else if (event instanceof LinkEvent) {
return "link";
} else if (event instanceof NetworkEvent) {
return "network";
} else if (event instanceof ScrollRecord) {
return "scroll";
}
return null;
throw new UnsupportedOperationException("Unknown event type " + event);
}
public static String getInstallationSerialId(Context context) {
@ -95,21 +117,39 @@ public class HotMobiLogger {
return new LatLng(location.getLatitude(), location.getLongitude());
}
public void log(long accountId, final Object event) {
public static File getLogFile(Context context, long accountId, String type) {
final File logsDir = getLogsDir(context);
final File todayLogDir = new File(logsDir, DATE_FORMAT.format(new Date()));
if (!todayLogDir.exists()) {
todayLogDir.mkdirs();
}
final String logFilename;
if (accountId > 0) {
logFilename = type + "_" + accountId + ".log";
} else {
logFilename = type + ".log";
}
return new File(todayLogDir, logFilename);
}
mExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Log.d(LOGTAG, LoganSquare.serialize(event));
} catch (IOException e) {
Log.w(LOGTAG, e);
}
}
});
public static File getLogsDir(Context context) {
return new File(context.getFilesDir(), "hotmobi");
}
public static long getLastUploadTime(final Context context) {
final SharedPreferences prefs = context.getSharedPreferences("spice_data_profiling", Context.MODE_PRIVATE);
return prefs.getLong(LAST_UPLOAD_TIME, -1);
}
public void log(long accountId, final Object event) {
mExecutor.execute(new WriteLogTask(mApplication, accountId, event));
}
public void log(Object event) {
log(ACCOUNT_ID_NOT_NEEDED, event);
}
public void logList(List<?> events, long accountId, String type) {
mExecutor.execute(new WriteLogTask(mApplication, accountId, type, events));
}
}

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.tsinghua.spice.Utilies;
package edu.tsinghua.hotmobi;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.util.TwidereLinkify;

View File

@ -1,5 +1,5 @@
/*
* Twidere - Twitter client for Android
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
@ -17,44 +17,37 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.view;
package edu.tsinghua.hotmobi;
import android.content.Context;
import android.support.v7.internal.widget.ActionBarContextView;
import android.support.v7.view.ActionMode;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.Preference;
import android.util.AttributeSet;
import org.mariotaku.twidere.util.ThemeUtils;
/**
* Created by mariotaku on 15/1/16.
* Created by mariotaku on 15/8/28.
*/
public class TwidereActionBarContextView extends ActionBarContextView {
public class UploadLogsPreferences extends Preference {
private int mItemColor;
public TwidereActionBarContextView(Context context) {
public UploadLogsPreferences(Context context) {
super(context);
}
public TwidereActionBarContextView(Context context, AttributeSet attrs) {
public UploadLogsPreferences(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TwidereActionBarContextView(Context context, AttributeSet attrs, int defStyleAttr) {
public UploadLogsPreferences(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void initForMode(ActionMode mode) {
super.initForMode(mode);
if (mItemColor != 0) {
ThemeUtils.setActionBarContextViewColor(this, mItemColor);
}
}
public void setItemColor(int itemColor) {
mItemColor = itemColor;
ThemeUtils.setActionBarContextViewColor(this, itemColor);
protected void onClick() {
final Context context = getContext();
final SharedPreferences prefs = context.getSharedPreferences("spice_data_profiling", Context.MODE_PRIVATE);
prefs.edit().remove(HotMobiLogger.LAST_UPLOAD_TIME).apply();
AsyncTask.execute(new UploadLogsTask(context.getApplicationContext()));
}
}

View File

@ -0,0 +1,124 @@
/*
* 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;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.util.Pair;
import org.mariotaku.restfu.annotation.method.POST;
import org.mariotaku.restfu.http.RestHttpClient;
import org.mariotaku.restfu.http.RestHttpRequest;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.FileTypedData;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.Utils;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
/**
* Created by mariotaku on 15/8/27.
*/
public class UploadLogsTask implements Runnable {
private final Context context;
private final RestHttpClient client;
public UploadLogsTask(Context context) {
this.context = context;
this.client = TwitterAPIFactory.getDefaultHttpClient(context);
}
@Override
public void run() {
final SharedPreferences prefs = context.getSharedPreferences("spice_data_profiling", Context.MODE_PRIVATE);
if (prefs.contains(HotMobiLogger.LAST_UPLOAD_TIME)) {
final long lastUpload = prefs.getLong(HotMobiLogger.LAST_UPLOAD_TIME, System.currentTimeMillis());
final double deltaDays = (System.currentTimeMillis() - lastUpload) /
(double) HotMobiLogger.UPLOAD_INTERVAL_MILLIS;
if (deltaDays < 1) {
SpiceProfilingUtil.log("Last uploaded was conducted in 1 day ago.");
return;
}
}
if (uploadLogs()) {
prefs.edit().putLong(HotMobiLogger.LAST_UPLOAD_TIME, System.currentTimeMillis()).apply();
}
}
private boolean uploadLogs() {
final String uuid = HotMobiLogger.getInstallationSerialId(context);
final File logsDir = HotMobiLogger.getLogsDir(context);
boolean hasErrors = false;
final String todayDir = HotMobiLogger.DATE_FORMAT.format(new Date());
final FilenameFilter filter = new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
return !filename.equalsIgnoreCase(todayDir);
}
};
for (File dayLogsDir : logsDir.listFiles(filter)) {
boolean succeeded = true;
for (File logFile : dayLogsDir.listFiles()) {
FileTypedData body = null;
RestHttpResponse response = null;
try {
final RestHttpRequest.Builder builder = new RestHttpRequest.Builder();
builder.method(POST.METHOD);
builder.url("http://oahu.hot-mobile.org/usage/upload");
final List<Pair<String, String>> headers = new ArrayList<>();
headers.add(Pair.create("X-HotMobi-UUID", uuid));
headers.add(Pair.create("X-HotMobi-Date", dayLogsDir.getName()));
headers.add(Pair.create("X-HotMobi-FileName", logFile.getName()));
builder.headers(headers);
body = new FileTypedData(logFile);
builder.body(body);
response = client.execute(builder.build());
if (response.isSuccessful()) {
succeeded &= logFile.delete();
}
response.close();
} catch (IOException e) {
Log.w(HotMobiLogger.LOGTAG, e);
succeeded = false;
hasErrors = true;
} finally {
Utils.closeSilently(body);
Utils.closeSilently(response);
}
}
if (succeeded) {
dayLogsDir.delete();
}
}
return hasErrors;
}
}

View File

@ -0,0 +1,101 @@
/*
* 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;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.Utils;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Collections;
import java.util.List;
/**
* Created by mariotaku on 15/8/23.
*/
public class WriteLogTask implements Runnable, Constants {
private static final byte[] LF = {'\n'};
private final Context context;
private final long accountId;
private final String type;
private final List<?> events;
public WriteLogTask(Context context, long accountId, Object event) {
this(context, accountId, HotMobiLogger.getLogFilename(event), Collections.singletonList(event));
}
public WriteLogTask(Context context, long accountId, String type, List<?> events) {
this.context = context;
this.accountId = accountId;
this.type = type;
this.events = events;
}
@Override
public void run() {
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
if (!prefs.getBoolean(KEY_USAGE_STATISTICS, false)) return;
RandomAccessFile raf = null;
FileChannel fc = null;
try {
raf = new RandomAccessFile(HotMobiLogger.getLogFile(context, accountId, type), "rw");
fc = raf.getChannel();
final FileLock lock = fc.lock();
for (Object event : events) {
final byte[] bytes = LoganSquare.serialize(event).getBytes("UTF-8");
final long start = raf.length();
final ByteBuffer bb;
if (start == 0) {
// Don't write line break
bb = ByteBuffer.allocate(bytes.length);
} else {
bb = ByteBuffer.allocate(bytes.length + LF.length);
bb.put(LF);
bb.position(LF.length);
}
bb.put(bytes);
bb.rewind();
fc.position(start);
while (bb.hasRemaining()) {
fc.write(bb);
}
}
lock.release();
} catch (IOException e) {
Log.w(HotMobiLogger.LOGTAG, e);
} finally {
Utils.closeSilently(fc);
Utils.closeSilently(raf);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* 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 com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import edu.tsinghua.hotmobi.TypeMappingUtil;
/**
* Created by mariotaku on 15/8/20.
*/
@JsonObject
public class LinkEvent extends BaseEvent {
@JsonField(name = "link")
String link;
@JsonField(name = "type")
String type;
public void setLink(String link) {
this.link = link;
}
public void setType(String type) {
this.type = type;
}
public static LinkEvent create(Context context, String link, int typeInt) {
final LinkEvent event = new LinkEvent();
event.markStart(context);
event.setLink(link);
event.setType(TypeMappingUtil.getLinkType(typeInt));
return event;
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.net.ConnectivityManager;
import android.net.NetworkInfo;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
/**
* Created by mariotaku on 15/8/20.
*/
@JsonObject
public class NetworkEvent extends BaseEvent {
@JsonField(name = "network_type")
int networkType;
public static NetworkEvent create(Context context) {
final NetworkEvent event = new NetworkEvent();
event.markStart(context);
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
if (activeNetworkInfo != null) {
event.setNetworkType(activeNetworkInfo.getType());
}
return event;
}
public void setNetworkType(int networkType) {
this.networkType = networkType;
}
}

View File

@ -29,6 +29,13 @@ import com.bluelinelabs.logansquare.annotation.JsonObject;
public class ScrollRecord {
@JsonField(name = "id")
long id;
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@JsonField(name = "account_id")
long accountId;
@JsonField(name = "timestamp")
long timestamp;
@JsonField(name = "scroll_state")
@ -46,9 +53,10 @@ public class ScrollRecord {
this.scrollState = scrollState;
}
public static ScrollRecord create(long id, long timestamp, int scrollState) {
public static ScrollRecord create(long id, long accountId, long timestamp, int scrollState) {
final ScrollRecord record = new ScrollRecord();
record.setId(id);
record.setAccountId(accountId);
record.setTimestamp(timestamp);
record.setScrollState(scrollState);
return record;

View File

@ -57,9 +57,6 @@ public class TweetEvent extends BaseEvent {
return event;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
public void setAction(Action action) {
this.action = action;
@ -85,9 +82,14 @@ public class TweetEvent extends BaseEvent {
return accountId;
}
public void setAccountId(long accountId) {
this.accountId = accountId;
}
public enum Action {
OPEN("open"), RETWEET("retweet"), FAVORITE("favorite"), UNFAVORITE("unfavorite"), UNKNOWN("unknown");
OPEN("open"), RETWEET("retweet"), FAVORITE("favorite"), UNFAVORITE("unfavorite"),
TWEET("tweet"), UNKNOWN("unknown");
private final String value;

View File

@ -20,8 +20,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import static org.mariotaku.twidere.util.Utils.copyStream;
@ -32,9 +32,6 @@ import static org.mariotaku.twidere.util.Utils.copyStream;
public class SpiceAsyUploadTask extends AsyncTask<Object, Object, Object> implements Constants {
public static final long UPLOAD_INTERVAL_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
public static final String LAST_UPLOAD_TIME = "last_upload_time";
private static final String PROFILE_SERVER_URL = "http://spice.hot-mobile.org/spice/usage";
private final Context context;
@ -91,9 +88,9 @@ public class SpiceAsyUploadTask extends AsyncTask<Object, Object, Object> implem
final SharedPreferences prefs = context.getSharedPreferences("spice_data_profiling", Context.MODE_PRIVATE);
if (prefs.contains(LAST_UPLOAD_TIME)) {
final long lastUpload = prefs.getLong(LAST_UPLOAD_TIME, System.currentTimeMillis());
final double deltaDays = (System.currentTimeMillis() - lastUpload) / (double) UPLOAD_INTERVAL_MILLIS;
if (prefs.contains(HotMobiLogger.LAST_UPLOAD_TIME)) {
final long lastUpload = prefs.getLong(HotMobiLogger.LAST_UPLOAD_TIME, System.currentTimeMillis());
final double deltaDays = (System.currentTimeMillis() - lastUpload) / (double) HotMobiLogger.UPLOAD_INTERVAL_MILLIS;
if (deltaDays < 1) {
SpiceProfilingUtil.log("Last uploaded was conducted in 1 day ago.");
return null;
@ -103,7 +100,7 @@ public class SpiceAsyUploadTask extends AsyncTask<Object, Object, Object> implem
final File root = context.getFilesDir();
final File[] spiceFiles = root.listFiles(new SpiceFileFilter());
uploadToServer(spiceFiles);
prefs.edit().putLong(LAST_UPLOAD_TIME, System.currentTimeMillis()).apply();
prefs.edit().putLong(HotMobiLogger.LAST_UPLOAD_TIME, System.currentTimeMillis()).apply();
return null;
}
@ -149,8 +146,4 @@ public class SpiceAsyUploadTask extends AsyncTask<Object, Object, Object> implem
}
}
public static long getLastUploadTime(final Context context) {
final SharedPreferences prefs = context.getSharedPreferences("spice_data_profiling", Context.MODE_PRIVATE);
return prefs.getLong(LAST_UPLOAD_TIME, -1);
}
}

View File

@ -4,19 +4,10 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.BatteryManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.UUID;
/**
* Created by Denny C. Ng on 2/20/15.
@ -54,43 +45,4 @@ public class SpiceProfilingUtil {
} else
return false;
}
public static void profile(final Context context, final long account_id, final String text) {
profile(context, account_id + "_" + FILE_NAME_PROFILE, text);
}
public static void profile(final Context context, final String name, final String text) {
if (context == null) return;
final SharedPreferences prefs = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
if (!prefs.getBoolean(Constants.KEY_USAGE_STATISTICS, false)) return;
final String persistedDeviceId = prefs.getString(Constants.KEY_DEVICE_SERIAL, null);
final String serial = String.valueOf(Build.SERIAL).replaceAll("[^\\w\\d]", "");
final String uuid;
if (!TextUtils.isEmpty(persistedDeviceId)) {
uuid = persistedDeviceId.replaceAll("[^\\w\\d]", "");
} else if (!TextUtils.isEmpty(serial)) {
uuid = serial;
prefs.edit().putString(Constants.KEY_DEVICE_SERIAL, serial).apply();
} else {
uuid = UUID.randomUUID().toString().replaceAll("[^\\w\\d]", "");
prefs.edit().putString(Constants.KEY_DEVICE_SERIAL, uuid).apply();
}
final String filename = uuid + "_" + name + ".spi";
new Thread() {
@Override
public void run() {
try {
final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_APPEND);
if (fos == null) return;
final BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
bw.write(text + "," + System.currentTimeMillis() + "\n");
bw.flush();
fos.close();
} catch (final Exception e) {
e.printStackTrace();
}
}
}.start();
}
}

View File

@ -33,7 +33,7 @@ import static org.mariotaku.twidere.annotation.Preference.Type.STRING;
public interface Constants extends TwidereConstants {
String DATABASES_NAME = "twidere.sqlite";
int DATABASES_VERSION = 105;
int DATABASES_VERSION = 107;
int MENU_GROUP_STATUS_EXTENSION = 10;
int MENU_GROUP_COMPOSE_EXTENSION = 11;

View File

@ -44,6 +44,7 @@ import org.mariotaku.restfu.http.Endpoint;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
import org.mariotaku.twidere.api.twitter.auth.OAuthAuthorization;
import org.mariotaku.twidere.api.twitter.auth.OAuthEndpoint;
import org.mariotaku.twidere.api.twitter.auth.OAuthToken;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
@ -237,7 +238,7 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity {
consumerSecret = defConsumerSecret;
}
try {
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", "oauth"));
final OAuthEndpoint endpoint = new OAuthEndpoint(TwitterAPIFactory.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", null));
final Authorization auth = new OAuthAuthorization(consumerKey, consumerSecret);
final TwitterOAuth twitter = TwitterAPIFactory.getInstance(mActivity, endpoint, auth, TwitterOAuth.class);
return twitter.getRequestToken(OAUTH_CALLBACK_OOB);

View File

@ -454,11 +454,7 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
assert bus != null;
bus.register(this);
// BEGIN HotMobi
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Launch" + "," + Build.MODEL
+ "," + "mediaPreview=" + mPreferences.getBoolean(KEY_MEDIA_PREVIEW, false));
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_ONLAUNCH, "App Launch"
+ "," + NetworkStateUtil.getConnectedType(this) + "," + Build.MODEL);
SessionEvent event = SessionEvent.create(this);
final SessionEvent event = SessionEvent.create(this);
mSessionEvent = event;
// END HotMobi
mReadStateManager.registerOnSharedPreferenceChangeListener(mReadStateChangeListener);
@ -497,10 +493,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
final SessionEvent event = mSessionEvent;
event.markEnd();
HotMobiLogger.getInstance(this).log(event);
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Stop");
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_ONLAUNCH, "App Stop" + ","
+ NetworkStateUtil.getConnectedType(this) + "," + Build.MODEL);
// END HotMobi
super.onStop();
}

View File

@ -35,7 +35,7 @@ import android.support.annotation.Nullable;
import android.support.multidex.MultiDexApplication;
import com.nostra13.universalimageloader.cache.disc.DiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.impl.ext.LruDiskCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
@ -67,11 +67,13 @@ import org.mariotaku.twidere.util.UserColorNameManager;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.VideoLoader;
import org.mariotaku.twidere.util.content.TwidereSQLiteOpenHelper;
import org.mariotaku.twidere.util.imageloader.ReadOnlyDiskLRUNameCache;
import org.mariotaku.twidere.util.imageloader.TwidereImageDownloader;
import org.mariotaku.twidere.util.imageloader.URLFileNameGenerator;
import org.mariotaku.twidere.util.net.TwidereHostAddressResolver;
import java.io.File;
import java.io.IOException;
import edu.tsinghua.hotmobi.HotMobiLogger;
@ -337,11 +339,13 @@ public class TwidereApplication extends MultiDexApplication implements Constants
private DiskCache createDiskCache(final String dirName) {
final File cacheDir = getBestCacheDir(this, dirName);
final File fallbackCacheDir = getInternalCacheDir(this, dirName);
// final LruDiscCache discCache = new LruDiscCache(cacheDir, new URLFileNameGenerator(), 384 *
// 1024 * 1024);
// discCache.setReserveCacheDir(fallbackCacheDir);
// return discCache;
return new UnlimitedDiskCache(cacheDir, fallbackCacheDir, new URLFileNameGenerator());
final URLFileNameGenerator fileNameGenerator = new URLFileNameGenerator();
try {
return new LruDiskCache(cacheDir, fallbackCacheDir, fileNameGenerator,
384 * 1024 * 1024, 0);
} catch (IOException e) {
return new ReadOnlyDiskLRUNameCache(cacheDir, fallbackCacheDir, fileNameGenerator);
}
}
private void initializeAsyncTask() {
@ -356,6 +360,6 @@ public class TwidereApplication extends MultiDexApplication implements Constants
@NonNull
public HotMobiLogger getHotMobiLogger() {
if (mHotMobiLogger != null) return mHotMobiLogger;
return mHotMobiLogger = new HotMobiLogger();
return mHotMobiLogger = new HotMobiLogger(this);
}
}

View File

@ -19,7 +19,6 @@
package org.mariotaku.twidere.fragment.support;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;

View File

@ -21,6 +21,8 @@ import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import com.desmond.asyncmanager.AsyncManager;
import com.desmond.asyncmanager.TaskRunnable;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;
@ -38,10 +40,15 @@ import org.mariotaku.twidere.util.ReadStateManager;
import org.mariotaku.twidere.util.RecyclerViewNavigationHelper;
import org.mariotaku.twidere.util.RecyclerViewUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.imageloader.PauseRecyclerViewOnScrollListener;
import org.mariotaku.twidere.util.message.StatusListChangedEvent;
import org.mariotaku.twidere.view.holder.GapViewHolder;
import org.mariotaku.twidere.view.holder.StatusViewHolder;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.MediaEvent;
import edu.tsinghua.hotmobi.model.ScrollRecord;
@ -77,7 +84,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
getFragmentManager(), getTwitterWrapper(), status, item);
}
};
private OnScrollListener mOnScrollListener = new OnScrollListener() {
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
@ -86,10 +93,12 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
}
}
};
private OnScrollListener mPauseOnScrollListener;
private OnScrollListener mHotMobiScrollTracker = new OnScrollListener() {
private final OnScrollListener mHotMobiScrollTracker = new OnScrollListener() {
private long mFirstVisibleId = -1;
public List<ScrollRecord> mRecords;
private long mFirstVisibleId = -1, mFirstVisibleAccountId = -1;
private int mFirstVisiblePosition = -1;
private int mScrollState;
@ -103,11 +112,13 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
final ParcelableStatus status = adapter.getStatus(firstVisiblePosition);
if (status != null) {
final long id = status.id, accountId = status.account_id;
if (id != mFirstVisibleId) {
final ScrollRecord record = ScrollRecord.create(id, System.currentTimeMillis(), mScrollState);
HotMobiLogger.getInstance(getActivity()).log(accountId, record);
if (id != mFirstVisibleId || accountId != mFirstVisibleAccountId) {
if (mRecords == null) mRecords = new ArrayList<>();
mRecords.add(ScrollRecord.create(id, accountId, System.currentTimeMillis(),
mScrollState));
}
mFirstVisibleId = id;
mFirstVisibleAccountId = accountId;
}
}
mFirstVisiblePosition = firstVisiblePosition;
@ -116,9 +127,17 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
mScrollState = newState;
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (mRecords != null) {
HotMobiLogger.getInstance(getActivity()).logList(mRecords, HotMobiLogger.ACCOUNT_ID_NOT_NEEDED, "scroll");
}
mRecords = null;
}
}
};
private OnScrollListener mActiveHotMobiScrollTracker;
protected AbsStatusesFragment() {
mStatusesBusCallback = createMessageBusCallback();
}
@ -213,6 +232,18 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
return onCreateStatusesLoader(getActivity(), args, fromUser);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
final LinearLayoutManager layoutManager = getLayoutManager();
if (layoutManager != null) {
saveReadPosition(layoutManager.findFirstVisibleItemPosition());
}
}
}
@Override
public final void onLoadFinished(Loader<Data> loader, Data data) {
final AbsStatusesAdapter<Data> adapter = getAdapter();
@ -380,7 +411,27 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
super.onStart();
final RecyclerView recyclerView = getRecyclerView();
recyclerView.addOnScrollListener(mOnScrollListener);
recyclerView.addOnScrollListener(mHotMobiScrollTracker);
recyclerView.addOnScrollListener(mPauseOnScrollListener);
final TaskRunnable<Object, Boolean, RecyclerView> task = new TaskRunnable<Object, Boolean, RecyclerView>() {
@Override
public Boolean doLongOperation(Object params) throws InterruptedException {
final Context context = getContext();
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
if (!prefs.getBoolean(KEY_USAGE_STATISTICS, false)) return false;
final File logFile = HotMobiLogger.getLogFile(context, HotMobiLogger.ACCOUNT_ID_NOT_NEEDED, "scroll");
return logFile.length() < 131072;
}
@Override
public void callback(RecyclerView recyclerView, Boolean result) {
if (result) {
recyclerView.addOnScrollListener(mActiveHotMobiScrollTracker = mHotMobiScrollTracker);
}
}
};
task.setResultHandler(recyclerView);
AsyncManager.runBackgroundTask(task);
final Bus bus = TwidereApplication.getInstance(getActivity()).getMessageBus();
assert bus != null;
bus.register(mStatusesBusCallback);
@ -392,7 +443,11 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
assert bus != null;
bus.unregister(mStatusesBusCallback);
final RecyclerView recyclerView = getRecyclerView();
recyclerView.removeOnScrollListener(mHotMobiScrollTracker);
if (mActiveHotMobiScrollTracker != null) {
recyclerView.removeOnScrollListener(mActiveHotMobiScrollTracker);
}
mActiveHotMobiScrollTracker = null;
recyclerView.removeOnScrollListener(mPauseOnScrollListener);
recyclerView.removeOnScrollListener(mOnScrollListener);
super.onStop();
}
@ -425,6 +480,7 @@ public abstract class AbsStatusesFragment<Data> extends AbsContentRecyclerViewFr
adapter, this);
adapter.setListener(this);
mPauseOnScrollListener = new PauseRecyclerViewOnScrollListener(adapter.getMediaLoader().getImageLoader(), false, true);
final Bundle loaderArgs = new Bundle(getArguments());
loaderArgs.putBoolean(EXTRA_FROM_USER, true);

View File

@ -61,7 +61,6 @@ public class StatusRepliesLoader extends UserMentionsLoader {
}
} else {
final List<Status> statuses = super.getStatuses(twitter, paging);
// TODO null check
for (final Status status : statuses) {
if (status.getInReplyToStatusId() == mInReplyToStatusId) {
result.add(status);

View File

@ -23,18 +23,18 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.ConnectivityManager;
import android.os.AsyncTask;
import android.util.Log;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.net.NetworkUsageUtils;
import edu.tsinghua.spice.Task.SpiceAsyUploadTask;
import edu.tsinghua.spice.Utilies.NetworkStateUtil;
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;
@ -53,13 +53,10 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME,
Context.MODE_PRIVATE);
if (prefs.getBoolean(KEY_USAGE_STATISTICS, false) && prefs.getBoolean(KEY_SETTINGS_WIZARD_COMPLETED, false)) {
//spice
SpiceProfilingUtil.profile(context, SpiceProfilingUtil.FILE_NAME_ONWIFI, NetworkStateUtil.getConnectedType(context));
final Location location = Utils.getCachedLocation(context);
if (location != null) {
SpiceProfilingUtil.profile(context, SpiceProfilingUtil.FILE_NAME_LOCATION, location.getTime() + ","
+ location.getLatitude() + "," + location.getLongitude() + "," + location.getProvider());
}
// BEGIN HotMobi
final NetworkEvent event = NetworkEvent.create(context);
HotMobiLogger.getInstance(context).log(event);
// END HotMobi
}
final int networkType = Utils.getActiveNetworkType(context.getApplicationContext());
NetworkUsageUtils.setNetworkType(networkType);
@ -67,10 +64,11 @@ public class ConnectivityStateReceiver extends BroadcastReceiver implements Cons
final boolean isCharging = SpiceProfilingUtil.isCharging(context.getApplicationContext());
if (isWifi && isCharging) {
final long currentTime = System.currentTimeMillis();
final long lastSuccessfulTime = SpiceAsyUploadTask.getLastUploadTime(context);
if ((currentTime - lastSuccessfulTime) > SpiceAsyUploadTask.UPLOAD_INTERVAL_MILLIS) {
AsyncTaskUtils.executeTask(new SpiceAsyUploadTask(context));
final long lastSuccessfulTime = HotMobiLogger.getLastUploadTime(context);
if ((currentTime - lastSuccessfulTime) > HotMobiLogger.UPLOAD_INTERVAL_MILLIS) {
AsyncTask.execute(new UploadLogsTask(context.getApplicationContext()));
}
}
}
}

View File

@ -35,14 +35,13 @@ public class SecretCodeBroadcastReceiver extends BroadcastReceiver implements In
public void onReceive(final Context context, final Intent intent) {
final Intent testIntent = new Intent(context, SettingsActivity.class);
final String cls = SettingsDetailsFragment.class.getName();
final String title = context.getString(R.string.hidden_settings);
final Bundle args = new Bundle();
args.putInt(EXTRA_RESID, R.xml.preferences_hidden);
args.putString(EXTRA_SETTINGS_INTENT_ACTION, INTENT_ACTION_HIDDEN_SETTINGS_ENTRY);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, cls);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, title);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_SHORT_TITLE, title);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE, R.string.hidden_settings);
testIntent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_SHORT_TITLE, R.string.hidden_settings);
testIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(testIntent);
}

View File

@ -64,7 +64,6 @@ import org.mariotaku.twidere.model.MediaUploadResult;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableDirectMessage;
import org.mariotaku.twidere.model.ParcelableLocation;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.model.ParcelableMediaUpdate;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.ParcelableStatusUpdate;
@ -97,8 +96,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.tsinghua.spice.Utilies.TypeMappingUtil;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.TimelineType;
import edu.tsinghua.hotmobi.model.TweetEvent;
import static android.text.TextUtils.isEmpty;
import static org.mariotaku.twidere.util.ContentValuesCreator.createMessageDraft;
@ -330,28 +330,20 @@ public class BackgroundOperationService extends IntentService implements Constan
final List<Long> failedAccountIds = ListUtils.fromArray(ParcelableAccount.getAccountIds(item.accounts));
for (final SingleResponse<ParcelableStatus> response : result) {
if (response.getData() == null) {
final ParcelableStatus data = response.getData();
if (data == null) {
failed = true;
if (exception == null) {
exception = response.getException();
}
} else if (response.getData().account_id > 0) {
failedAccountIds.remove(response.getData().account_id);
//spice
if (response.getData().media == null) {
SpiceProfilingUtil.log(response.getData().id + ",Tweet," + response.getData().account_id + ","
+ response.getData().in_reply_to_user_id + "," + response.getData().in_reply_to_status_id);
SpiceProfilingUtil.profile(this.getBaseContext(), response.getData().account_id, response.getData().id + ",Tweet," + response.getData().account_id + ","
+ response.getData().in_reply_to_user_id + "," + response.getData().in_reply_to_status_id);
} else
for (final ParcelableMedia spiceMedia : response.getData().media) {
SpiceProfilingUtil.log(response.getData().id + ",Media," + response.getData().account_id + ","
+ response.getData().in_reply_to_user_id + "," + response.getData().in_reply_to_status_id + "," + spiceMedia.media_url + "," + TypeMappingUtil.getMediaType(spiceMedia.type));
SpiceProfilingUtil.profile(this.getBaseContext(), response.getData().account_id, response.getData().id + ",Media," + response.getData().account_id + ","
+ response.getData().in_reply_to_user_id + "," + response.getData().in_reply_to_status_id + "," + spiceMedia.media_url + "," + TypeMappingUtil.getMediaType(spiceMedia.type));
}
//end
} else if (data.account_id > 0) {
failedAccountIds.remove(data.account_id);
// BEGIN HotMobi
final TweetEvent event = TweetEvent.create(BackgroundOperationService.this, data,
TimelineType.OTHER);
event.setAction(TweetEvent.Action.TWEET);
HotMobiLogger.getInstance(BackgroundOperationService.this).log(data.account_id, event);
// END HotMobi
}
}

View File

@ -72,7 +72,7 @@ import org.mariotaku.twidere.model.ParcelableUser;
import org.mariotaku.twidere.model.ParcelableUserList;
import org.mariotaku.twidere.model.SingleResponse;
import org.mariotaku.twidere.provider.TwidereDataStore;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags;
import org.mariotaku.twidere.provider.TwidereDataStore.*;
import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox;
@ -837,9 +837,11 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
}
// I bet you don't want to see this user in your auto complete list.
//TODO insert to blocked users data
// final Expression where = Expression.equals(CachedUsers.USER_ID, user_id);
// mResolver.delete(CachedUsers.CONTENT_URI, where.getSQL(), null);
final ContentValues values = new ContentValues();
values.put(CachedRelationships.ACCOUNT_ID, account_id);
values.put(CachedRelationships.USER_ID, user_id);
values.put(CachedRelationships.BLOCKING, true);
mResolver.insert(CachedRelationships.CONTENT_URI, values);
return SingleResponse.getInstance(new ParcelableUser(user, account_id), null);
} catch (final TwitterException e) {
return SingleResponse.getInstance(null, e);

View File

@ -82,6 +82,10 @@ public class MediaLoaderWrapper implements Constants {
mDashboardProfileImageDisplayOptions = dashboardProfileOptsBuilder.build();
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
public void clearFileCache() {
mImageLoader.clearDiskCache();
}

View File

@ -31,8 +31,8 @@ import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.tsinghua.spice.Utilies.TypeMappingUtil;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.LinkEvent;
import static org.mariotaku.twidere.util.Utils.openStatus;
import static org.mariotaku.twidere.util.Utils.openTweetSearch;
@ -56,9 +56,10 @@ public class OnLinkClickHandler implements OnLinkClickListener, Constants {
final boolean sensitive, int start, int end) {
if (manager != null && manager.isActive()) return;
if (!isPrivateData()) {
//spice
SpiceProfilingUtil.profile(context, accountId, accountId + ",Visit," + link + "," + TypeMappingUtil.getLinkType(type));
//end
// BEGIN HotMobi
final LinkEvent event = LinkEvent.create(context, link, type);
HotMobiLogger.getInstance(context).log(accountId, event);
// END HotMobi
}
switch (type) {

View File

@ -37,7 +37,6 @@ import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
import android.widget.PopupWindow;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.iface.IThemedActivity;
import org.mariotaku.twidere.view.TintedStatusNativeActionModeAwareLayout;
@ -49,8 +48,6 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
private final IThemedActivity mThemed;
private final AppCompatCallback mAppCompatCallback;
private final Window mWindow;
private final boolean mIsFloating;
private final boolean mUsePopup;
private ActionMode mActionMode;
public ActionBarContextView mActionModeView;
@ -62,8 +59,6 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
mThemed = activity;
mWindow = mActivity.getWindow();
mAppCompatCallback = callback;
mIsFloating = ThemeUtils.isWindowFloating(mActivity, activity.getCurrentThemeResourceId());
mUsePopup = usePopup;
}
@Override
@ -104,39 +99,35 @@ public class TwidereActionModeForChildListener implements TintedStatusNativeActi
final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
if (mActionModeView == null) {
if (mIsFloating && mUsePopup) {
// Use the action bar theme.
final Context actionBarContext;
actionBarContext = ThemeUtils.getActionBarThemedContext(mActivity, mThemed.getCurrentThemeResourceId(),
mThemed.getCurrentThemeColor());
// Use the action bar theme.
final Context actionBarContext;
actionBarContext = ThemeUtils.getActionBarThemedContext(mActivity, mThemed.getCurrentThemeResourceId(),
mThemed.getCurrentThemeColor());
mActionModeView = new ActionBarContextView(actionBarContext);
mActionModePopup = new PopupWindow(actionBarContext, null,
android.support.v7.appcompat.R.attr.actionModePopupWindowStyle);
mActionModePopup.setContentView(mActionModeView);
mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
mActionModeView = new ActionBarContextView(actionBarContext);
mActionModePopup = new PopupWindow(actionBarContext, null,
android.support.v7.appcompat.R.attr.actionModePopupWindowStyle);
mActionModePopup.setContentView(mActionModeView);
mActionModePopup.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
final TypedValue outValue = new TypedValue();
actionBarContext.getTheme().resolveAttribute(
android.support.v7.appcompat.R.attr.actionBarSize, outValue, true);
final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
actionBarContext.getResources().getDisplayMetrics());
mActionModeView.setContentHeight(height);
ThemeUtils.setActionBarContextViewBackground(mActionModeView,
mThemed.getCurrentThemeResourceId(), mThemed.getCurrentThemeColor(),
mThemed.getCurrentThemeBackgroundOption(), false);
mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mShowActionModePopup = new Runnable() {
@Override
public void run() {
mActionModePopup.showAtLocation(
mWindow.getDecorView(),
Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
}
};
} else {
mActionModeView = (ActionBarContextView) mWindow.findViewById(R.id.action_context_bar);
}
final TypedValue outValue = new TypedValue();
actionBarContext.getTheme().resolveAttribute(
android.support.v7.appcompat.R.attr.actionBarSize, outValue, true);
final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
actionBarContext.getResources().getDisplayMetrics());
mActionModeView.setContentHeight(height);
ThemeUtils.setActionBarContextViewBackground(mActionModeView,
mThemed.getCurrentThemeResourceId(), mThemed.getCurrentThemeColor(),
mThemed.getCurrentThemeBackgroundOption(), false);
mActionModePopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
mShowActionModePopup = new Runnable() {
@Override
public void run() {
mActionModePopup.showAtLocation(
mWindow.getDecorView(),
Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
}
};
}
if (mActionModeView != null) {

View File

@ -90,7 +90,8 @@ public final class DatabaseUpgradeHelper {
}
public static void safeUpgrade(final SQLiteDatabase db, final String table, final String[] newColNames,
final String[] newColTypes, final boolean dropDirectly, final Map<String, String> colAliases, final Constraint... constraints) {
final String[] newColTypes, final boolean dropDirectly,
final Map<String, String> colAliases, final Constraint... constraints) {
safeUpgrade(db, table, newColNames, newColTypes, dropDirectly, colAliases, OnConflict.REPLACE, constraints);
}

View File

@ -84,7 +84,8 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
db.execSQL(createTable(CachedUsers.TABLE_NAME, CachedUsers.COLUMNS, CachedUsers.TYPES, true));
db.execSQL(createTable(CachedStatuses.TABLE_NAME, CachedStatuses.COLUMNS, CachedStatuses.TYPES, true));
db.execSQL(createTable(CachedHashtags.TABLE_NAME, CachedHashtags.COLUMNS, CachedHashtags.TYPES, true));
db.execSQL(createTable(CachedRelationships.TABLE_NAME, CachedRelationships.COLUMNS, CachedRelationships.TYPES, true));
db.execSQL(createTable(CachedRelationships.TABLE_NAME, CachedRelationships.COLUMNS, CachedRelationships.TYPES, true,
createConflictReplaceConstraint(CachedRelationships.ACCOUNT_ID, CachedRelationships.USER_ID)));
db.execSQL(createTable(Filters.Users.TABLE_NAME, Filters.Users.COLUMNS, Filters.Users.TYPES, true));
db.execSQL(createTable(Filters.Keywords.TABLE_NAME, Filters.Keywords.COLUMNS, Filters.Keywords.TYPES, true));
db.execSQL(createTable(Filters.Sources.TABLE_NAME, Filters.Sources.COLUMNS, Filters.Sources.TYPES, true));
@ -98,7 +99,8 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
db.execSQL(createTable(Tabs.TABLE_NAME, Tabs.COLUMNS, Tabs.TYPES, true));
db.execSQL(createTable(SavedSearches.TABLE_NAME, SavedSearches.COLUMNS, SavedSearches.TYPES, true));
db.execSQL(createTable(SearchHistory.TABLE_NAME, SearchHistory.COLUMNS, SearchHistory.TYPES, true));
db.execSQL(createTable(NetworkUsages.TABLE_NAME, NetworkUsages.COLUMNS, NetworkUsages.TYPES, true, createNetworkUsagesConstraint()));
db.execSQL(createTable(NetworkUsages.TABLE_NAME, NetworkUsages.COLUMNS, NetworkUsages.TYPES, true,
createConflictReplaceConstraint(NetworkUsages.TIME_IN_HOURS, NetworkUsages.REQUEST_NETWORK, NetworkUsages.REQUEST_TYPE)));
createViews(db);
createTriggers(db);
@ -108,8 +110,8 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
db.endTransaction();
}
private Constraint createNetworkUsagesConstraint() {
return Constraint.unique(new Columns(NetworkUsages.TIME_IN_HOURS, NetworkUsages.REQUEST_NETWORK, NetworkUsages.REQUEST_TYPE), OnConflict.IGNORE);
private Constraint createConflictReplaceConstraint(String... columns) {
return Constraint.unique(new Columns(columns), OnConflict.IGNORE);
}
private void createIndices(SQLiteDatabase db) {
@ -234,7 +236,8 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
safeUpgrade(db, CachedUsers.TABLE_NAME, CachedUsers.COLUMNS, CachedUsers.TYPES, true, null);
safeUpgrade(db, CachedStatuses.TABLE_NAME, CachedStatuses.COLUMNS, CachedStatuses.TYPES, true, null);
safeUpgrade(db, CachedHashtags.TABLE_NAME, CachedHashtags.COLUMNS, CachedHashtags.TYPES, true, null);
safeUpgrade(db, CachedRelationships.TABLE_NAME, CachedRelationships.COLUMNS, CachedRelationships.TYPES, true, null);
safeUpgrade(db, CachedRelationships.TABLE_NAME, CachedRelationships.COLUMNS, CachedRelationships.TYPES, true, null,
createConflictReplaceConstraint(CachedRelationships.ACCOUNT_ID, CachedRelationships.USER_ID));
safeUpgrade(db, Filters.Users.TABLE_NAME, Filters.Users.COLUMNS, Filters.Users.TYPES,
oldVersion < 49, null);
safeUpgrade(db, Filters.Keywords.TABLE_NAME, Filters.Keywords.COLUMNS, Filters.Keywords.TYPES,
@ -253,7 +256,7 @@ public final class TwidereSQLiteOpenHelper extends SQLiteOpenHelper implements C
safeUpgrade(db, SavedSearches.TABLE_NAME, SavedSearches.COLUMNS, SavedSearches.TYPES, true, null);
safeUpgrade(db, SearchHistory.TABLE_NAME, SearchHistory.COLUMNS, SearchHistory.TYPES, true, null);
safeUpgrade(db, NetworkUsages.TABLE_NAME, NetworkUsages.COLUMNS, NetworkUsages.TYPES, true, null,
createNetworkUsagesConstraint());
createConflictReplaceConstraint(NetworkUsages.TIME_IN_HOURS, NetworkUsages.REQUEST_NETWORK, NetworkUsages.REQUEST_TYPE));
db.beginTransaction();
createViews(db);
createTriggers(db);

View File

@ -0,0 +1,56 @@
/*
* 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.imageloader;
import android.support.v7.widget.RecyclerView;
import com.nostra13.universalimageloader.core.ImageLoader;
public class PauseRecyclerViewOnScrollListener extends RecyclerView.OnScrollListener {
private ImageLoader imageLoader;
private final boolean pauseOnScroll;
private final boolean pauseOnFling;
public PauseRecyclerViewOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {
this.imageLoader = imageLoader;
this.pauseOnScroll = pauseOnScroll;
this.pauseOnFling = pauseOnFling;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
this.imageLoader.resume();
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
if (this.pauseOnScroll) {
this.imageLoader.pause();
}
break;
case RecyclerView.SCROLL_STATE_SETTLING:
if (this.pauseOnFling) {
this.imageLoader.pause();
}
break;
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.imageloader;
import android.graphics.Bitmap;
import com.nostra13.universalimageloader.cache.disc.impl.BaseDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.utils.IoUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by mariotaku on 15/8/28.
*/
public class ReadOnlyDiskLRUNameCache extends BaseDiskCache {
public ReadOnlyDiskLRUNameCache(File cacheDir) {
super(cacheDir);
}
public ReadOnlyDiskLRUNameCache(File cacheDir, File reserveCacheDir) {
super(cacheDir, reserveCacheDir);
}
@Override
public boolean save(String imageUri, InputStream imageStream, IoUtils.CopyListener listener) throws IOException {
return false;
}
@Override
public boolean save(String imageUri, Bitmap bitmap) throws IOException {
return false;
}
@Override
public boolean remove(String imageUri) {
return false;
}
@Override
public void clear() {
// No-op
}
@Override
protected File getFile(String imageUri) {
String fileName = fileNameGenerator.generate(imageUri) + ".0";
File dir = cacheDir;
if ((!cacheDir.exists()) && (!cacheDir.mkdirs()) &&
(reserveCacheDir != null) && ((reserveCacheDir.exists()) || (reserveCacheDir.mkdirs()))) {
dir = reserveCacheDir;
}
return new File(dir, fileName);
}
public ReadOnlyDiskLRUNameCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) {
super(cacheDir, reserveCacheDir, fileNameGenerator);
}
}

View File

@ -29,6 +29,7 @@ import org.mariotaku.twidere.activity.iface.IThemedActivity;
import org.mariotaku.twidere.util.ThemeUtils;
/**
* Wraps context with ActionBar context
* Created by mariotaku on 15/4/28.
*/
public class TwidereActionBarContainer extends ActionBarContainer {

View File

@ -1,19 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<org.mariotaku.twidere.view.TwidereToolbar
android:id="@+id/action_bar"
style="?attr/toolbarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationContentDescription="@string/abc_action_bar_up_description" />
app:navigationContentDescription="@string/abc_action_bar_up_description"
tools:ignore="PrivateResource" />
<org.mariotaku.twidere.view.TwidereActionBarContextView
android:id="@+id/action_context_bar"
style="?attr/actionModeStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme"
android:visibility="gone" />
</merge>

View File

@ -22,11 +22,4 @@
app:tabHorizontalPadding="@dimen/element_spacing_normal" />
</org.mariotaku.twidere.view.TwidereToolbar>
<org.mariotaku.twidere.view.TwidereActionBarContextView
android:id="@+id/action_context_bar"
style="?attr/actionModeStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="?attr/actionBarTheme"
android:visibility="gone" />
</merge>

View File

@ -772,4 +772,5 @@
<string name="set_consumer_key_secret_message">Twidere is reaching token limit, you will have to create an app at https://apps.twitter.com/ and paste consumer key and secret below.\nIf you continue using default key, you may not be able to sign in.</string>
<string name="send_at">Send at</string>
<string name="scheduled_statuses">Scheduled tweets</string>
<string name="report_usage_statistics_now">Report usage statistics now</string>
</resources>

View File

@ -28,6 +28,8 @@
android:key="status_text_limit"
android:title="@string/status_text_limit" />
<edu.tsinghua.hotmobi.UploadLogsPreferences android:title="@string/report_usage_statistics_now" />
<Preference android:title="@string/settings_wizard">
<intent
android:targetClass="org.mariotaku.twidere.activity.SettingsWizardActivity"