redesigning dashboard

This commit is contained in:
Mariotaku Lee 2015-05-28 00:31:48 +08:00
parent 73619da1fb
commit 57f94e2978
98 changed files with 1062 additions and 1811 deletions

View File

@ -23,4 +23,15 @@ android {
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
}

View File

@ -7,6 +7,5 @@ include ':twidere.donate.nyanwp'
include ':twidere.donate.nyanwp.wear'
include ':twidere.component.nyan'
include ':twidere.extension.twitlonger'
include ':twidere.extension.streaming'
include ':twidere.extension.push.xiaomi'
include ':twidere.extension.launcher.compose'

View File

@ -45,7 +45,6 @@ dependencies {
compile 'org.apache.commons:commons-lang3:3.4'
compile 'com.github.mariotaku:RestFu:6ef0913'
compile 'com.hannesdorfmann.parcelableplease:annotation:1.0.1'
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.4'
compile project(':twidere.component.querybuilder')
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -189,6 +189,8 @@ public interface SharedPreferenceConstants {
String KEY_MENTIONS_NOTIFICATION = "mentions_notification";
@Preference(type = BOOLEAN)
String KEY_DIRECT_MESSAGES_NOTIFICATION = "direct_messages_notification";
@Preference(type = BOOLEAN)
String KEY_ENABLE_STREAMING = "enable_streaming";
@Preference(type = INT)
String KEY_LOCAL_TRENDS_WOEID = "local_trends_woeid";
String KEY_NOTIFICATION_RINGTONE = "notification_ringtone";

View File

@ -26,14 +26,17 @@ import android.text.TextUtils;
import com.bluelinelabs.logansquare.LoganSquare;
import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonObject;
import java.io.IOException;
import java.util.List;
import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease;
import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.api.twitter.model.UserMentionEntity;
import java.io.IOException;
import java.util.List;
@JsonObject
@ParcelablePlease(allFields = false)
public class ParcelableUserMention implements Parcelable {
public static final Parcelable.Creator<ParcelableUserMention> CREATOR = new Parcelable.Creator<ParcelableUserMention>() {
@ -48,10 +51,13 @@ public class ParcelableUserMention implements Parcelable {
}
};
@ParcelableThisPlease
@JsonField(name = "id")
public long id;
@ParcelableThisPlease
@JsonField(name = "name")
public String name;
@ParcelableThisPlease
@JsonField(name = "screen_name")
public String screen_name;
@ -87,8 +93,15 @@ public class ParcelableUserMention implements Parcelable {
}
public static ParcelableUserMention[] fromSerializedJson(String string) {
return new ParcelableUserMention[0];
if (string == null) return null;
final List<ParcelableUserMention> list;
try {
list = LoganSquare.parseList(string, ParcelableUserMention.class);
} catch (IOException e) {
return null;
}
if (list == null) return null;
return list.toArray(new ParcelableUserMention[list.size()]);
}
@Override

View File

@ -1,279 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Pair;
import org.mariotaku.restfu.ExceptionFactory;
import org.mariotaku.restfu.HttpRequestFactory;
import org.mariotaku.restfu.RequestInfoFactory;
import org.mariotaku.restfu.RestMethodInfo;
import org.mariotaku.restfu.RestRequestInfo;
import org.mariotaku.restfu.annotation.RestMethod;
import org.mariotaku.restfu.http.Authorization;
import org.mariotaku.restfu.http.Endpoint;
import org.mariotaku.restfu.http.FileValue;
import org.mariotaku.restfu.http.RestHttpRequest;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.StringTypedData;
import org.mariotaku.restfu.http.mime.TypedData;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
import org.mariotaku.twidere.api.twitter.TwitterUpload;
import org.mariotaku.twidere.api.twitter.TwitterUserStream;
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.OAuthToken;
import org.mariotaku.twidere.api.twitter.util.TwitterConverter;
import org.mariotaku.twidere.model.ConsumerKeyType;
import org.mariotaku.twidere.model.ParcelableCredentials;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static android.text.TextUtils.isEmpty;
/**
* Created by mariotaku on 15/5/26.
*/
public class TwitterAPIUtils implements TwidereConstants {
public static Endpoint getEndpoint(ParcelableCredentials credentials, Class<?> cls) {
final String apiUrlFormat;
final boolean sameOAuthSigningUrl = credentials.same_oauth_signing_url;
final boolean noVersionSuffix = credentials.no_version_suffix;
if (!isEmpty(credentials.api_url_format)) {
apiUrlFormat = credentials.api_url_format;
} else {
apiUrlFormat = DEFAULT_TWITTER_API_URL_FORMAT;
}
final String domain, versionSuffix;
if (Twitter.class.isAssignableFrom(cls)) {
domain = "api";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else if (TwitterUpload.class.isAssignableFrom(cls)) {
domain = "upload";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else if (TwitterOAuth.class.isAssignableFrom(cls)) {
domain = "api";
versionSuffix = "oauth";
} else if (TwitterUserStream.class.isAssignableFrom(cls)) {
domain = "userstream";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else {
throw new TwitterConverter.UnsupportedTypeException(cls);
}
final String endpointUrl;
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
if (credentials.auth_type == ParcelableCredentials.AUTH_TYPE_XAUTH || credentials.auth_type == ParcelableCredentials.AUTH_TYPE_OAUTH) {
final String signEndpointUrl;
if (!sameOAuthSigningUrl) {
signEndpointUrl = getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain, versionSuffix);
} else {
signEndpointUrl = endpointUrl;
}
return new OAuthEndpoint(endpointUrl, signEndpointUrl);
}
return new Endpoint(endpointUrl);
}
public static Authorization getAuthorization(ParcelableCredentials credentials) {
switch (credentials.auth_type) {
case ParcelableCredentials.AUTH_TYPE_OAUTH:
case ParcelableCredentials.AUTH_TYPE_XAUTH: {
final String consumerKey = TextUtils.isEmpty(credentials.consumer_key) ?
TWITTER_CONSUMER_KEY_LEGACY : credentials.consumer_key;
final String consumerSecret = TextUtils.isEmpty(credentials.consumer_secret) ?
TWITTER_CONSUMER_SECRET_LEGACY : credentials.consumer_secret;
final OAuthToken accessToken = new OAuthToken(credentials.oauth_token, credentials.oauth_token_secret);
return new OAuthAuthorization(consumerKey, consumerSecret, accessToken);
}
case ParcelableCredentials.AUTH_TYPE_BASIC: {
final String screenName = credentials.screen_name;
final String username = credentials.basic_auth_username;
final String loginName = username != null ? username : screenName;
final String password = credentials.basic_auth_password;
if (isEmpty(loginName) || isEmpty(password)) return null;
return new BasicAuthorization(loginName, password);
}
}
return new EmptyAuthorization();
}
private static void addParameter(List<Pair<String, String>> params, String name, Object value) {
params.add(Pair.create(name, String.valueOf(value)));
}
private static void addPart(List<Pair<String, TypedData>> params, String name, Object value) {
final TypedData typedData = new StringTypedData(String.valueOf(value), Charset.defaultCharset());
params.add(Pair.create(name, typedData));
}
public static String getApiBaseUrl(String format, final String domain) {
if (format == null) return null;
final Matcher matcher = Pattern.compile("\\[(\\.?)DOMAIN(\\.?)\\]").matcher(format);
if (!matcher.find()) {
// For backward compatibility
format = substituteLegacyApiBaseUrl(format, domain);
if (!format.endsWith("/1.1") && !format.endsWith("/1.1/")) {
return format;
}
final String versionSuffix = "/1.1";
final int suffixLength = versionSuffix.length();
final int lastIndex = format.lastIndexOf(versionSuffix);
return format.substring(0, lastIndex) + format.substring(lastIndex + suffixLength);
}
if (TextUtils.isEmpty(domain)) return matcher.replaceAll("");
return matcher.replaceAll(String.format("$1%s$2", domain));
}
private static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
final int startOfHost = format.indexOf("://") + 3, endOfHost = format.indexOf('/', startOfHost);
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);
if (!host.equalsIgnoreCase("api.twitter.com")) return format;
return format.substring(0, startOfHost) + domain + ".twitter.com" + format.substring(endOfHost);
}
public static String getApiUrl(final String pattern, final String domain, final String appendPath) {
final String urlBase = getApiBaseUrl(pattern, domain);
if (urlBase == null) return null;
if (appendPath == null) return urlBase.endsWith("/") ? urlBase : urlBase + "/";
final StringBuilder sb = new StringBuilder(urlBase);
if (urlBase.endsWith("/")) {
sb.append(appendPath.startsWith("/") ? appendPath.substring(1) : appendPath);
} else {
if (appendPath.startsWith("/")) {
sb.append(appendPath);
} else {
sb.append('/');
sb.append(appendPath);
}
}
return sb.toString();
}
public static String getUserAgentName(ConsumerKeyType type) {
switch (type) {
case TWITTER_FOR_ANDROID: {
return "TwitterAndroid";
}
case TWITTER_FOR_IPHONE: {
return "Twitter-iPhone";
}
case TWITTER_FOR_IPAD: {
return "Twitter-iPad";
}
case TWITTER_FOR_MAC: {
return "Twitter-Mac";
}
}
return "Twitter";
}
public static String getTwidereUserAgent(final Context context) {
final PackageManager pm = context.getPackageManager();
try {
final PackageInfo pi = pm.getPackageInfo(TWIDERE_PACKAGE_NAME, 0);
return TWIDERE_APP_NAME + " " + TWIDERE_PROJECT_URL + " / " + pi.versionName;
} catch (final PackageManager.NameNotFoundException e) {
return TWIDERE_APP_NAME + " " + TWIDERE_PROJECT_URL;
}
}
public static class TwidereRequestInfoFactory implements RequestInfoFactory {
@Override
public RestRequestInfo create(RestMethodInfo methodInfo) {
final RestMethod method = methodInfo.getMethod();
final String path = methodInfo.getPath();
final List<Pair<String, String>> queries = new ArrayList<>(methodInfo.getQueries());
final List<Pair<String, String>> forms = new ArrayList<>(methodInfo.getForms());
final List<Pair<String, String>> headers = methodInfo.getHeaders();
final List<Pair<String, TypedData>> parts = methodInfo.getParts();
final FileValue file = methodInfo.getFile();
final Map<String, Object> extras = methodInfo.getExtras();
if (parts.isEmpty()) {
final List<Pair<String, String>> params = method.hasBody() ? forms : queries;
addParameter(params, "include_cards", true);
addParameter(params, "cards_platform", "Android-12");
addParameter(params, "include_entities", true);
addParameter(params, "include_my_retweet", 1);
addParameter(params, "include_rts", 1);
addParameter(params, "include_reply_count", true);
addParameter(params, "include_descendent_reply_count", true);
} else {
addPart(parts, "include_cards", true);
addPart(parts, "cards_platform", "Android-12");
addPart(parts, "include_entities", true);
addPart(parts, "include_my_retweet", 1);
addPart(parts, "include_rts", 1);
addPart(parts, "include_reply_count", true);
addPart(parts, "include_descendent_reply_count", true);
}
return new RestRequestInfo(method.value(), path, queries, forms, headers, parts, file,
methodInfo.getBody(), extras);
}
}
public static class TwidereHttpRequestFactory implements HttpRequestFactory {
private final String userAgent;
public TwidereHttpRequestFactory(final String userAgent) {
this.userAgent = userAgent;
}
@Override
public RestHttpRequest create(@NonNull Endpoint endpoint, @NonNull RestRequestInfo info,
@Nullable Authorization authorization) {
final String restMethod = info.getMethod();
final String url = Endpoint.constructUrl(endpoint.getUrl(), info);
final ArrayList<Pair<String, String>> headers = new ArrayList<>(info.getHeaders());
if (authorization != null && authorization.hasAuthorization()) {
headers.add(Pair.create("Authorization", authorization.getHeader(endpoint, info)));
}
headers.add(Pair.create("User-Agent", userAgent));
return new RestHttpRequest(restMethod, url, headers, info.getBody(), null);
}
}
public static class TwidereExceptionFactory implements ExceptionFactory {
@Override
public Exception newException(Throwable cause, RestHttpRequest request, RestHttpResponse response) {
final TwitterException te = new TwitterException(cause);
te.setResponse(response);
return te;
}
}
}

View File

@ -1 +0,0 @@
/build

View File

@ -1,58 +0,0 @@
/*
* 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/>.
*/
apply plugin: 'com.android.application'
apply from: rootProject.file('global.gradle')
apply from: rootProject.file('signing.gradle')
android {
defaultConfig {
applicationId "org.mariotaku.twidere.extension.streaming"
minSdkVersion 14
targetSdkVersion 22
versionCode 14
versionName "1.12 (0.3.0-dev)"
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}
dependencies {
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile project(':twidere.library.extension')
compile fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/mariotaku/Tools/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mariotaku.twidere.extension.streaming">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
<meta-data
android:name="org.mariotaku.twidere.extension"
android:value="true"/>
<meta-data
android:name="org.mariotaku.twidere.extension.permissions"
android:value="read|write|direct_messages|accounts|refresh|preferences"/>
<meta-data
android:name="org.mariotaku.twidere.extension.settings"
android:value="org.mariotaku.twidere.extension.streaming.SettingsActivity"/>
<receiver android:name="TwidereLaunchReceiver">
<intent-filter>
<action android:name="org.mariotaku.twidere.HOME_ACTIVITY_ONCREATE"/>
<action android:name="org.mariotaku.twidere.HOME_ACTIVITY_ONDESTROY"/>
</intent-filter>
</receiver>
<service android:name="StreamingService"/>
<activity
android:name="SettingsActivity"
android:exported="true">
</activity>
</application>
</manifest>

View File

@ -1,12 +0,0 @@
package org.mariotaku.twidere.extension.streaming;
import org.mariotaku.twidere.TwidereConstants;
public interface Constants extends TwidereConstants {
public static final String LOGTAG = "Twidere.Streaming";
public static final String PREFERENCE_KEY_ACCOUNT_IDS = "account_ids";
public static final String PREFERENCE_KEY_ENABLE_STREAMING = "enable_streaming";
}

View File

@ -1,10 +0,0 @@
package org.mariotaku.twidere.extension.streaming;
public interface PrivateConstants {
public static final String TWITTER_CONSUMER_KEY = "uAFVpMhBntJutfVj6abfA";
public static final String TWITTER_CONSUMER_SECRET = "JARXkJTfxo0F8MyctYy9bUmrLISjo8vXAHsZHYuk2E";
public static final String MAPS_API_KEY_RELEASE = "0kjPwJOe_zwYjzGc9uYak7vhm_Sf3eob-2L3Xzw";
public static final String MAPS_API_KEY_DEBUG = "0kjPwJOe_zwY9p6kT-kygu4mxwysyOOpfkaXqTA";
}

View File

@ -1,69 +0,0 @@
package org.mariotaku.twidere.extension.streaming;
import org.mariotaku.twidere.Twidere;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
public class SettingsActivity extends PreferenceActivity implements Constants, OnSharedPreferenceChangeListener {
private static final int REQUEST_REQUEST_PERMISSIONS = 101;
private SharedPreferences mPreferences;
@Override
public void onSharedPreferenceChanged(final SharedPreferences preferences, final String key) {
if (PREFERENCE_KEY_ENABLE_STREAMING.equals(key)) {
final Intent intent = new Intent(this, StreamingService.class);
if (preferences.getBoolean(key, true)) {
startService(intent);
} else {
stopService(intent);
}
}
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_REQUEST_PERMISSIONS: {
if (resultCode != RESULT_OK) {
finish();
return;
}
}
}
super.onActivityResult(requestCode, resultCode, data);
}
@SuppressWarnings("deprecation")
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final boolean granted;
try {
granted = Twidere.isPermissionGranted(this);
} catch (final SecurityException e) {
// TODO show error
finish();
return;
}
if (!granted) {
final Intent intent = new Intent(Twidere.INTENT_ACTION_REQUEST_PERMISSIONS);
intent.setPackage("org.mariotaku.twidere");
try {
startActivityForResult(intent, REQUEST_REQUEST_PERMISSIONS);
} catch (final ActivityNotFoundException e) {
}
}
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
addPreferencesFromResource(R.xml.settings);
mPreferences.registerOnSharedPreferenceChangeListener(this);
}
}

View File

@ -1,22 +0,0 @@
package org.mariotaku.twidere.extension.streaming;
import org.mariotaku.twidere.Twidere;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class TwidereLaunchReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
final Intent service_intent = new Intent(context, StreamingService.class);
if (Twidere.BROADCAST_HOME_ACTIVITY_ONCREATE.equals(action)) {
context.startService(service_intent);
} else if (Twidere.BROADCAST_HOME_ACTIVITY_ONDESTROY.equals(action)) {
context.stopService(service_intent);
}
}
}

View File

@ -1,70 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012 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.extension.streaming.util;
import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.os.Build;
public final class ActivityAccessor {
public static void onBackPressed(final Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) return;
OnBackPressedAccessor.onBackPressed(activity);
}
public static void overridePendingTransition(final Activity activity, final int enter_anim, final int exit_anim) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) return;
OverridePendingTransitionAccessor.overridePendingTransition(activity, enter_anim, exit_anim);
}
public static void setHomeButtonEnabled(final Activity activity, final boolean enabled) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return;
SetHomeButtonEnabledAccessor.setHomeButtonEnabled(activity, enabled);
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
private static class OnBackPressedAccessor {
private static void onBackPressed(final Activity activity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) return;
activity.onBackPressed();
}
}
@TargetApi(Build.VERSION_CODES.ECLAIR)
private static class OverridePendingTransitionAccessor {
private static void overridePendingTransition(final Activity activity, final int enter_anim, final int exit_anim) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR) return;
activity.overridePendingTransition(enter_anim, exit_anim);
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private static class SetHomeButtonEnabledAccessor {
private static void setHomeButtonEnabled(final Activity activity, final boolean enabled) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) return;
final ActionBar action_bar = activity.getActionBar();
action_bar.setHomeButtonEnabled(enabled);
}
}
}

View File

@ -1,197 +0,0 @@
/*
* 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.extension.streaming.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import org.mariotaku.restfu.Utils;
import org.mariotaku.restfu.http.ContentType;
import org.mariotaku.restfu.http.RestHttpClient;
import org.mariotaku.restfu.http.RestHttpRequest;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.TypedData;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import okio.BufferedSink;
/**
* Created by mariotaku on 15/5/5.
*/
public class OkHttpRestClient implements RestHttpClient {
private final OkHttpClient client;
public OkHttpRestClient() {
this(new OkHttpClient());
}
public OkHttpRestClient(OkHttpClient client) {
this.client = client;
}
@NonNull
@Override
public RestHttpResponse execute(RestHttpRequest restHttpRequest) throws IOException {
final Request.Builder builder = new Request.Builder();
builder.method(restHttpRequest.getMethod(), RestToOkBody.wrap(restHttpRequest.getBody()));
builder.url(restHttpRequest.getUrl());
final List<Pair<String, String>> headers = restHttpRequest.getHeaders();
if (headers != null) {
for (Pair<String, String> header : headers) {
builder.addHeader(header.first, header.second);
}
}
final Call call = client.newCall(builder.build());
return new OkRestHttpResponse(call.execute());
}
private static class RestToOkBody extends RequestBody {
private final TypedData body;
public RestToOkBody(TypedData body) {
this.body = body;
}
@Override
public MediaType contentType() {
final ContentType contentType = body.contentType();
if (contentType == null) return null;
return MediaType.parse(contentType.toHeader());
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
body.writeTo(sink.outputStream());
}
@Nullable
public static RequestBody wrap(@Nullable TypedData body) {
if (body == null) return null;
return new RestToOkBody(body);
}
}
private static class OkRestHttpResponse extends RestHttpResponse {
private final Response response;
private TypedData body;
public OkRestHttpResponse(Response response) {
this.response = response;
}
@Override
public int getStatus() {
return response.code();
}
@Override
public List<Pair<String, String>> getHeaders() {
final Headers headers = response.headers();
final ArrayList<Pair<String, String>> headersList = new ArrayList<>();
for (int i = 0, j = headers.size(); i < j; i++) {
headersList.add(Pair.create(headers.name(i), headers.value(i)));
}
return headersList;
}
@Override
public String getHeader(String name) {
return response.header(name);
}
@Override
public String[] getHeaders(String name) {
final List<String> values = response.headers(name);
return values.toArray(new String[values.size()]);
}
@Override
public TypedData getBody() {
if (body != null) return body;
return body = new OkToRestBody(response.body());
}
@Override
public void close() throws IOException {
if (body != null) {
body.close();
body = null;
}
}
}
private static class OkToRestBody implements TypedData {
private final ResponseBody body;
public OkToRestBody(ResponseBody body) {
this.body = body;
}
@Override
public ContentType contentType() {
final MediaType mediaType = body.contentType();
if (mediaType == null) return null;
return ContentType.parse(mediaType.toString());
}
@Override
public String contentEncoding() {
return null;
}
@Override
public long length() throws IOException {
return body.contentLength();
}
@Override
public void writeTo(@NonNull OutputStream os) throws IOException {
Utils.copyStream(stream(), os);
}
@NonNull
@Override
public InputStream stream() throws IOException {
return body.byteStream();
}
@Override
public void close() throws IOException {
body.close();
}
}
}

View File

@ -1,82 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012 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.extension.streaming.util;
import android.content.Context;
import android.util.Log;
import com.squareup.okhttp.internal.Network;
import org.mariotaku.twidere.Twidere;
import org.mariotaku.twidere.extension.streaming.BuildConfig;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.LinkedHashMap;
public class TwidereHostAddressResolver implements Network {
private static final String RESOLVER_LOGTAG = "Twidere.Streaming.Host";
private static TwidereHostAddressResolver sInstance;
private final HostCache mHostCache = new HostCache(512);
private final Context mContext;
public TwidereHostAddressResolver(final Context context) {
mContext = context;
}
@Override
public InetAddress[] resolveInetAddresses(final String host) throws UnknownHostException {
if (host == null) return null;
// First, I'll try to load address cached.
final InetAddress[] cached = mHostCache.get(host);
if (cached != null) {
if (BuildConfig.DEBUG) {
Log.d(RESOLVER_LOGTAG, "Got cached " + Arrays.toString(cached));
}
return cached;
}
final InetAddress[] resolved = Twidere.resolveHost(mContext, host);
mHostCache.put(host, resolved);
return resolved;
}
public static Network getInstance(final Context context) {
if (sInstance != null) return sInstance;
return sInstance = new TwidereHostAddressResolver(context);
}
private static class HostCache extends LinkedHashMap<String, InetAddress[]> {
private static final long serialVersionUID = -9216545511009449147L;
HostCache(final int initialCapacity) {
super(initialCapacity);
}
@Override
public InetAddress[] put(final String key, final InetAddress[] value) {
if (value == null) return null;
return super.put(key, value);
}
}
}

View File

@ -1,144 +0,0 @@
package org.mariotaku.twidere.extension.streaming.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.SSLCertificateSocketFactory;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.internal.Internal;
import org.mariotaku.restfu.RestAPIFactory;
import org.mariotaku.restfu.http.Authorization;
import org.mariotaku.restfu.http.Endpoint;
import org.mariotaku.restfu.http.RestHttpClient;
import org.mariotaku.twidere.Twidere;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.TwidereSharedPreferences;
import org.mariotaku.twidere.api.twitter.auth.OAuthAuthorization;
import org.mariotaku.twidere.api.twitter.util.TwitterConverter;
import org.mariotaku.twidere.model.ConsumerKeyType;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.util.ParseUtils;
import org.mariotaku.twidere.util.TwitterAPIUtils;
import org.mariotaku.twidere.util.TwitterContentUtils;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit;
import static android.text.TextUtils.isEmpty;
public class Utils implements TwidereConstants {
public static void closeSilently(Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignore) {
}
}
public static RestHttpClient getDefaultHttpClient(final Context context) {
if (context == null) return null;
final SharedPreferences prefs = Twidere.getSharedPreferences(context);
return createHttpClient(context, prefs);
}
public static <T> T getInstance(final Context context, final Endpoint endpoint, final Authorization auth, Class<T> cls) {
final RestAPIFactory factory = new RestAPIFactory();
final String userAgent;
if (auth instanceof OAuthAuthorization) {
final String consumerKey = ((OAuthAuthorization) auth).getConsumerKey();
final String consumerSecret = ((OAuthAuthorization) auth).getConsumerSecret();
final ConsumerKeyType officialKeyType = TwitterContentUtils.getOfficialKeyType(context, consumerKey, consumerSecret);
if (officialKeyType != ConsumerKeyType.UNKNOWN) {
userAgent = TwitterAPIUtils.getUserAgentName(officialKeyType);
} else {
userAgent = TwitterAPIUtils.getTwidereUserAgent(context);
}
} else {
userAgent = TwitterAPIUtils.getTwidereUserAgent(context);
}
factory.setClient(getDefaultHttpClient(context));
factory.setConverter(new TwitterConverter());
factory.setEndpoint(endpoint);
factory.setAuthorization(auth);
factory.setRequestInfoFactory(new TwitterAPIUtils.TwidereRequestInfoFactory());
factory.setHttpRequestFactory(new TwitterAPIUtils.TwidereHttpRequestFactory(userAgent));
factory.setExceptionFactory(new TwitterAPIUtils.TwidereExceptionFactory());
return factory.build(cls);
}
public static RestHttpClient createHttpClient(final Context context, final SharedPreferences prefs) {
final int connectionTimeout = prefs.getInt(KEY_CONNECTION_TIMEOUT, 10);
final boolean ignoreSslError = prefs.getBoolean(KEY_IGNORE_SSL_ERROR, false);
final boolean enableProxy = prefs.getBoolean(KEY_ENABLE_PROXY, false);
final OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(connectionTimeout, TimeUnit.SECONDS);
if (ignoreSslError) {
client.setSslSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null));
} else {
client.setSslSocketFactory(SSLCertificateSocketFactory.getDefault(0, null));
}
if (enableProxy) {
client.setProxy(getProxy(prefs));
}
Internal.instance.setNetwork(client, TwidereHostAddressResolver.getInstance(context));
return new OkHttpRestClient(client);
}
public static Proxy getProxy(final SharedPreferences prefs) {
final String proxyHost = prefs.getString(KEY_PROXY_HOST, null);
final int proxyPort = ParseUtils.parseInt(prefs.getString(KEY_PROXY_PORT, "-1"));
if (!isEmpty(proxyHost) && proxyPort >= 0 && proxyPort < 65535) {
final SocketAddress addr = InetSocketAddress.createUnresolved(proxyHost, proxyPort);
return new Proxy(Proxy.Type.HTTP, addr);
}
return Proxy.NO_PROXY;
}
public static long[] getActivatedAccountIds(final Context context) {
long[] accounts = new long[0];
if (context == null) return accounts;
final String[] cols = new String[]{Accounts.ACCOUNT_ID};
final Cursor cur = context.getContentResolver().query(Accounts.CONTENT_URI, cols, Accounts.IS_ACTIVATED + "=1",
null, Accounts.ACCOUNT_ID);
if (cur != null) {
final int idx = cur.getColumnIndexOrThrow(Accounts.ACCOUNT_ID);
cur.moveToFirst();
accounts = new long[cur.getCount()];
int i = 0;
while (!cur.isAfterLast()) {
accounts[i] = cur.getLong(idx);
i++;
cur.moveToNext();
}
cur.close();
}
return accounts;
}
public static String getNonEmptyString(final TwidereSharedPreferences pref, final String key, final String def) {
if (pref == null) return def;
final String val = pref.getString(key, def);
return isEmpty(val) ? def : val;
}
public static String replaceLast(final String text, final String regex, final String replacement) {
if (text == null || regex == null || replacement == null) return text;
return text.replaceFirst("(?s)" + regex + "(?!.*?" + regex + ")", replacement);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<bool name="hires_profile_image">true</bool>
</resources>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<bool name="hires_profile_image">false</bool>
</resources>

View File

@ -1,9 +0,0 @@
<resources>
<string name="app_name">Twidere Streaming Extension</string>
<string name="select_accounts">Select accounts</string>
<string name="enable_streaming">Enable streaming</string>
<string name="streaming_service_running">Streaming service is running</string>
<string name="request_permission">Permission request to access Twidere</string>
</resources>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:defaultValue="true"
android:key="enable_streaming"
android:title="@string/enable_streaming"/>
</PreferenceScreen>

View File

@ -22,16 +22,6 @@ android {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
productFlavors {
google {
}
@ -95,11 +85,14 @@ dependencies {
compile 'com.bluelinelabs:logansquare:1.1.0'
compile 'ch.acra:acra:4.6.2'
compile 'org.jraf:android-switch-backport:2.0.1'
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.4'
googleCompile 'com.google.android.gms:play-services-maps:7.3.0'
googleCompile 'com.google.maps.android:android-maps-utils:0.3.4'
fdroidCompile 'org.osmdroid:osmdroid-android:4.3'
fdroidCompile 'org.slf4j:slf4j-simple:1.7.12'
debugCompile 'im.dino:dbinspector:3.1.0@aar'
debugCompile 'com.facebook.stetho:stetho:1.1.1'
debugCompile 'com.facebook.stetho:stetho-okhttp:1.1.1'
compile project(':twidere.component.common')
compile project(':twidere.component.nyan')
compile fileTree(dir: 'libs/main', include: ['*.jar'])

View File

@ -0,0 +1,43 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util;
import android.app.Application;
import com.facebook.stetho.Stetho;
import com.facebook.stetho.okhttp.StethoInterceptor;
import com.squareup.okhttp.OkHttpClient;
/**
* Created by mariotaku on 15/5/27.
*/
public class DebugModeUtils {
public static void initForHttpClient(final OkHttpClient client) {
client.networkInterceptors().add(new StethoInterceptor());
}
public static void initForApplication(final Application application) {
Stetho.initialize(Stetho.newInitializerBuilder(application)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(application))
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(application))
.build());
}
}

View File

@ -1,65 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.mariotaku.twidere"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.mariotaku.twidere"
android:installLocation="auto">
<uses-sdk />
<uses-sdk/>
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.location"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.location.network"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
android:required="false"/>
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
android:required="true"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS" />
<uses-permission android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA" />
<uses-permission android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"/>
<uses-permission android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA"/>
<uses-permission android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE"/>
<permission-group
android:name="org.mariotaku.twidere.permission.PERMISSION_GROUP"
android:label="@string/app_name" />
android:label="@string/app_name"/>
<permission
android:name="org.mariotaku.twidere.permission.SHORTEN_STATUS"
android:description="@string/permission_description_shorten_status"
android:label="@string/permission_label_shorten_status"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP"/>
<permission
android:name="org.mariotaku.twidere.permission.UPLOAD_MEDIA"
android:description="@string/permission_description_upload_media"
android:label="@string/permission_label_upload_media"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP"/>
<permission
android:name="org.mariotaku.twidere.permission.SYNC_TIMELINE"
android:description="@string/permission_description_sync_timeline"
android:label="@string/permission_label_sync_timeline"
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP" />
android:permissionGroup="org.mariotaku.twidere.permission.PERMISSION_GROUP"/>
<application
android:name=".app.TwidereApplication"
@ -73,29 +73,29 @@
tools:ignore="UnusedAttribute">
<uses-library
android:name="com.sec.android.app.multiwindow"
android:required="false" />
android:required="false"/>
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAIKbKATV1AGbLB4kem3w8QaPVJSPVVumbMHxkfwA" />
android:value="AEdPqrEAAAAIKbKATV1AGbLB4kem3w8QaPVJSPVVumbMHxkfwA"/>
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
android:value="true"/>
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W"
android:value="480dp" />
android:value="480dp"/>
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H"
android:value="640dp" />
android:value="640dp"/>
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W"
android:value="240dp" />
android:value="240dp"/>
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H"
android:value="320dp" />
android:value="320dp"/>
<meta-data
android:name="override_tinted_status_bar_defaults"
android:value="true" />
android:value="true"/>
<activity
android:name=".activity.MainActivity"
@ -105,11 +105,11 @@
android:theme="@style/Theme.Launcher"
android:windowSoftInputMode="adjustNothing">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -121,11 +121,11 @@
android:theme="@style/Theme.Launcher"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -135,22 +135,22 @@
android:theme="@style/Theme.Twidere.Dark.NoActionBar"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.HOME" />
<action android:name="org.mariotaku.twidere.HOME"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.SEARCH"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
android:resource="@xml/searchable"/>
<meta-data
android:name="android.app.default_searchable"
android:value=".activity.support.HomeActivity" />
android:value=".activity.support.HomeActivity"/>
</activity>
<activity
android:name=".activity.support.ComposeActivity"
@ -160,29 +160,29 @@
android:theme="@style/Theme.Twidere.Dark.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/compose">
<action android:name="android.intent.action.MAIN" />
<action android:name="org.mariotaku.twidere.COMPOSE" />
<action android:name="org.mariotaku.twidere.REPLY" />
<action android:name="org.mariotaku.twidere.QUOTE" />
<action android:name="org.mariotaku.twidere.EDIT_DRAFT" />
<action android:name="org.mariotaku.twidere.MENTION" />
<action android:name="org.mariotaku.twidere.REPLY_MULTIPLE" />
<action android:name="android.intent.action.MAIN"/>
<action android:name="org.mariotaku.twidere.COMPOSE"/>
<action android:name="org.mariotaku.twidere.REPLY"/>
<action android:name="org.mariotaku.twidere.QUOTE"/>
<action android:name="org.mariotaku.twidere.EDIT_DRAFT"/>
<action android:name="org.mariotaku.twidere.MENTION"/>
<action android:name="org.mariotaku.twidere.REPLY_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*" />
<data android:mimeType="text/plain" />
<data android:mimeType="image/*"/>
<data android:mimeType="text/plain"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
android:value=".activity.support.HomeActivity"/>
</activity>
<activity
android:name=".activity.support.QuickSearchBarActivity"
@ -191,9 +191,9 @@
android:theme="@style/Theme.Twidere.Dark.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.GLOBAL_SEARCH" />
<action android:name="org.mariotaku.twidere.GLOBAL_SEARCH"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -202,9 +202,9 @@
android:theme="@style/Theme.Twidere.Dark.DialogWhenLarge.NoActionBar"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.TWITTER_LOGIN" />
<action android:name="org.mariotaku.twidere.TWITTER_LOGIN"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -213,9 +213,9 @@
android:theme="@style/Theme.Twidere.Dark.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.TWITTER_BROWSER_LOGIN" />
<action android:name="org.mariotaku.twidere.TWITTER_BROWSER_LOGIN"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -224,29 +224,29 @@
android:theme="@style/Theme.Twidere.Dark"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<action android:name="org.mariotaku.twidere.SETTINGS" />
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
<action android:name="org.mariotaku.twidere.SETTINGS"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
android:value=".activity.support.HomeActivity"/>
</activity>
<activity
android:name=".activity.support.APIEditorActivity"
android:label="@string/edit_api"
android:theme="@style/Theme.Twidere.Dark.Dialog"
android:windowSoftInputMode="adjustResize" />
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.support.AccountSelectorActivity"
android:label="@string/select_account"
android:theme="@style/Theme.Twidere.Dark.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.SELECT_ACCOUNT" />
<action android:name="org.mariotaku.twidere.SELECT_ACCOUNT"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -254,34 +254,34 @@
android:exported="false"
android:label="@string/browser">
<intent-filter>
<action android:name="org.mariotaku.twidere.VIEW_WEBPAGE" />
<action android:name="org.mariotaku.twidere.VIEW_WEBPAGE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="file"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
</intent-filter>
</activity>
<activity
android:name=".activity.support.ColorPickerDialogActivity"
android:label="@string/set_color"
android:theme="@style/Theme.Twidere.Light.NoDisplay" />
android:theme="@style/Theme.Twidere.Light.NoDisplay"/>
<activity
android:name=".activity.support.LinkHandlerActivity"
android:theme="@style/Theme.Twidere.Dark.DialogWhenLarge.NoActionBar"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.support.HomeActivity" />
android:value=".activity.support.HomeActivity"/>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="twidere" />
<data android:scheme="twidere"/>
</intent-filter>
</activity>
<activity
@ -292,14 +292,14 @@
android:launchMode="singleTop"
android:theme="@style/Theme.Twidere.Viewer">
<intent-filter>
<action android:name="org.mariotaku.twidere.VIEW_MEDIA" />
<action android:name="org.mariotaku.twidere.VIEW_MEDIA"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="file" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="file"/>
</intent-filter>
</activity>
<activity
@ -309,10 +309,10 @@
android:theme="@style/Theme.Twidere.Light.NoDisplay"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.PICK_FILE" />
<action android:name="org.mariotaku.twidere.PICK_DIRECTORY" />
<action android:name="org.mariotaku.twidere.PICK_FILE"/>
<action android:name="org.mariotaku.twidere.PICK_DIRECTORY"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -321,11 +321,11 @@
android:theme="@style/Theme.Twidere.Light.NoDisplay"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="org.mariotaku.twidere.PICK_IMAGE" />
<action android:name="org.mariotaku.twidere.TAKE_PHOTO" />
<action android:name="android.intent.action.MAIN"/>
<action android:name="org.mariotaku.twidere.PICK_IMAGE"/>
<action android:name="org.mariotaku.twidere.TAKE_PHOTO"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -335,10 +335,10 @@
android:theme="@style/Theme.Twidere.Dark.Dialog"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="org.mariotaku.twidere.ADD_TAB" />
<action android:name="org.mariotaku.twidere.EDIT_TAB" />
<action android:name="org.mariotaku.twidere.ADD_TAB"/>
<action android:name="org.mariotaku.twidere.EDIT_TAB"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -346,9 +346,9 @@
android:label="@string/compose"
android:theme="@style/Theme.Twidere.Dark.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<action android:name="android.intent.action.CREATE_SHORTCUT"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -356,18 +356,18 @@
android:label="@string/permissions_request"
android:theme="@style/Theme.Twidere.Dark.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.REQUEST_PERMISSIONS" />
<action android:name="org.mariotaku.twidere.REQUEST_PERMISSIONS"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".activity.CameraCropActivity"
android:exported="false">
<intent-filter>
<action android:name="org.mariotaku.twidere.CAMERA_CROP" />
<action android:name="org.mariotaku.twidere.CAMERA_CROP"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -375,31 +375,31 @@
android:label="@string/select_user_list"
android:theme="@style/Theme.Twidere.Dark.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.SELECT_USER" />
<action android:name="org.mariotaku.twidere.SELECT_USER_LIST" />
<action android:name="org.mariotaku.twidere.SELECT_USER"/>
<action android:name="org.mariotaku.twidere.SELECT_USER_LIST"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".activity.SettingsWizardActivity"
android:label="@string/settings_wizard"
android:theme="@style/Theme.Twidere.Wizard" />
android:theme="@style/Theme.Twidere.Wizard"/>
<activity
android:name=".activity.support.DataExportActivity"
android:label="@string/export_settings"
android:theme="@android:style/Theme.NoDisplay" />
android:theme="@android:style/Theme.NoDisplay"/>
<activity
android:name=".activity.support.DataImportActivity"
android:label="@string/import_settings"
android:theme="@android:style/Theme.NoDisplay" />
android:theme="@android:style/Theme.NoDisplay"/>
<activity
android:name=".activity.support.ActivityPickerActivity"
android:theme="@style/Theme.Twidere.Dark.Dialog">
<intent-filter>
<action android:name="org.mariotaku.twidere.PICK_ACTIVITY" />
<action android:name="org.mariotaku.twidere.PICK_ACTIVITY"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
@ -411,33 +411,33 @@
<data
android:host="twitter.com"
android:pathPrefix="/"
android:scheme="http" />
android:scheme="http"/>
<data
android:host="twitter.com"
android:pathPrefix="/"
android:scheme="https" />
android:scheme="https"/>
<data
android:host="www.twitter.com"
android:pathPrefix="/"
android:scheme="http" />
android:scheme="http"/>
<data
android:host="www.twitter.com"
android:pathPrefix="/"
android:scheme="https" />
android:scheme="https"/>
<data
android:host="mobile.twitter.com"
android:pathPrefix="/"
android:scheme="http" />
android:scheme="http"/>
<data
android:host="mobile.twitter.com"
android:pathPrefix="/"
android:scheme="https" />
android:scheme="https"/>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
<activity
@ -447,18 +447,18 @@
android:taskAffinity=":twidere_assist_launcher"
android:theme="@style/Theme.Launcher">
<intent-filter>
<action android:name="android.intent.action.ASSIST" />
<action android:name="android.intent.action.ASSIST"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="com.android.systemui.action_assist_icon"
android:resource="@drawable/ic_assist_twidere" />
android:resource="@drawable/ic_assist_twidere"/>
</activity>
<activity
android:name=".activity.KeyboardShortcutPreferenceCompatActivity"
android:theme="@style/Theme.Twidere.Dark.Dialog" />
android:theme="@style/Theme.Twidere.Dark.Dialog"/>
<activity
android:name=".activity.TestActivity"
android:enabled="false"
@ -466,27 +466,30 @@
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".activity.NyanActivity"
android:launchMode="singleTop"
android:theme="@style/Theme.Nyan"
android:windowSoftInputMode="stateAlwaysHidden" />
android:windowSoftInputMode="stateAlwaysHidden"/>
<activity
android:name=".activity.UsageStatisticsActivity"
android:label="@string/usage_statistics"
android:theme="@android:style/Theme.NoDisplay" />
android:theme="@android:style/Theme.NoDisplay"/>
<service
android:name=".service.RefreshService"
android:label="@string/label_refresh_service" />
android:label="@string/label_refresh_service"/>
<service
android:name=".service.StreamingService"
android:label="@string/label_streaming_service"/>
<service
android:name=".service.BackgroundOperationService"
android:label="@string/label_background_operation_service" />
android:label="@string/label_background_operation_service"/>
<service
android:name=".nyan.NyanWallpaperService"
android:enabled="false"
@ -496,12 +499,12 @@
android:permission="android.permission.BIND_WALLPAPER"
android:process=":wallpaper">
<intent-filter android:priority="1">
<action android:name="android.service.wallpaper.WallpaperService" />
<action android:name="android.service.wallpaper.WallpaperService"/>
</intent-filter>
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/nyan_wallpaper" />
android:resource="@xml/nyan_wallpaper"/>
</service>
<service
android:name=".nyan.NyanDaydreamService"
@ -512,7 +515,7 @@
android:process=":daydream"
tools:ignore="ExportedService">
<intent-filter android:priority="1">
<action android:name="android.service.dreams.DreamService" />
<action android:name="android.service.dreams.DreamService"/>
</intent-filter>
</service>
@ -522,53 +525,53 @@
android:exported="true"
android:grantUriPermissions="true"
android:label="@string/label_data_provider"
tools:ignore="ExportedContentProvider" />
tools:ignore="ExportedContentProvider"/>
<provider
android:name=".provider.TwidereCommandProvider"
android:authorities="twidere.command"
android:exported="true"
tools:ignore="ExportedContentProvider" />
tools:ignore="ExportedContentProvider"/>
<provider
android:name=".provider.RecentSearchProvider"
android:authorities="org.mariotaku.twidere.provider.SearchRecentSuggestions"
tools:ignore="ExportedContentProvider" />
tools:ignore="ExportedContentProvider"/>
<receiver android:name=".receiver.ConnectivityStateReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<receiver
android:name=".receiver.NotificationReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.mariotaku.twidere.NOTIFICATION_DELETED" />
<action android:name="org.mariotaku.twidere.NOTIFICATION_DELETED"/>
</intent-filter>
</receiver>
<receiver
android:name=".receiver.SecretCodeBroadcastReceiver"
android:label="@string/twidere_test">
<intent-filter>
<action android:name="android.provider.Telephony.SECRET_CODE" />
<action android:name="android.provider.Telephony.SECRET_CODE"/>
<data
android:host="8943373"
android:scheme="android_secret_code" />
android:scheme="android_secret_code"/>
</intent-filter>
</receiver>
<!-- Begin third Party components -->
<!-- SPICE -->
<service android:name="edu.tsinghua.spice.SpiceService" />
<service android:name="edu.tsinghua.spice.SpiceService"/>
<receiver
android:name="edu.tsinghua.spice.SpiceUploadReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<action android:name="edu.tsinghua.spice.UPLOAD_PROFILE" />
<action android:name="android.net.wifi.supplicant.CONNECTION_CHANGE"/>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
<action android:name="edu.tsinghua.spice.UPLOAD_PROFILE"/>
</intent-filter>
</receiver>

View File

@ -1,19 +0,0 @@
package edu.ucdavis.earlybird;
import java.io.File;
import java.io.FileFilter;
public final class CSVFileFilter implements FileFilter {
@Override
public boolean accept(final File file) {
return file.isFile() && "csv".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);
}
}

View File

@ -1,70 +0,0 @@
package edu.ucdavis.earlybird;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.BatteryManager;
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;
public class ProfilingUtil {
public static final String FILE_NAME_PROFILE = "Profile";
public static final String FILE_NAME_LOCATION = "Location";
public static final String FILE_NAME_APP = "App";
public static final String FILE_NAME_WIFI = "Wifi";
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;
}
public static boolean log(final Context context, 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 static void profile(final Context context, final long accountID, final String text) {
profile(context, accountID + "_" + 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 filename = name + ".csv";
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("[" + System.currentTimeMillis() + "], " + text + "\n");
bw.flush();
fos.close();
} catch (final Exception e) {
e.printStackTrace();
}
}
}.start();
}
}

View File

@ -1,42 +0,0 @@
package edu.ucdavis.earlybird;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
/**
* Request location ONCE per WAKE_PERIOD_IN_MILLI.
*/
public class UCDService extends Service {
public static final String ACTION_GET_LOCATION = "edu.ucdavis.earlybird.GET_LOCATION";
private AlarmManager mAlarmManager;
private PendingIntent uploadIntent;
@Override
public IBinder onBind(final Intent intent) {
throw new IllegalStateException("Not implemented.");
}
@Override
public void onCreate() {
super.onCreate();
ProfilingUtil.log(this, "onCreate");
mAlarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
// Upload Service
final Intent i = new Intent(UploadReceiver.ACTION_UPLOAD_PROFILE);
uploadIntent = PendingIntent.getBroadcast(this, 0, i, 0);
mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 12 * 60 * 60 * 1000, uploadIntent);
}
@Override
public void onDestroy() {
mAlarmManager.cancel(uploadIntent);
super.onDestroy();
}
}

View File

@ -1,27 +0,0 @@
package edu.ucdavis.earlybird;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import org.mariotaku.twidere.util.Utils;
public class UploadReceiver extends BroadcastReceiver {
public static final String ACTION_UPLOAD_PROFILE = "edu.ucdavis.earlybird.UPLOAD_PROFILE";
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
final boolean isWifi = Utils.isOnWifi(context.getApplicationContext());
final boolean isCharging = ProfilingUtil.isCharging(context.getApplicationContext());
if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
final boolean wifi = intent.getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
ProfilingUtil.profile(context, ProfilingUtil.FILE_NAME_WIFI, wifi ? "connected" : "disconnected");
}
if (isWifi && isCharging) {
new UploadTask(context).execute();
}
}
}

View File

@ -1,146 +0,0 @@
package edu.ucdavis.earlybird;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.provider.Settings.Secure;
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.restfu.http.mime.MultipartTypedBody;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import static org.mariotaku.twidere.util.Utils.copyStream;
public class UploadTask extends AsyncTask<Object, Object, Object> {
private static final String LAST_UPLOAD_DATE = "last_upload_time";
private static final double MILLSECS_HALF_DAY = 1000 * 60 * 60 * 12;
private final String device_id;
private final Context context;
private final RestHttpClient client;
private static final String PROFILE_SERVER_URL = "http://weik.metaisle.com/profiles";
// private static final String PROFILE_SERVER_URL =
// "http://192.168.0.105:3000/profiles";
public UploadTask(final Context context) {
this.context = context;
this.client = TwitterAPIFactory.getDefaultHttpClient(context);
device_id = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
}
public void uploadMultipart(final String url, final File file) {
final String app_root = file.getParent();
final File tmp_dir = new File(app_root + "/tmp");
if (!tmp_dir.exists()) {
if (!tmp_dir.mkdirs()) {
ProfilingUtil.log(context, "cannot create tmp, do nothing.");
return;
}
}
final File tmp = new File(tmp_dir, file.getName());
file.renameTo(tmp);
try {
final RestHttpRequest.Builder builder = new RestHttpRequest.Builder();
builder.url(PROFILE_SERVER_URL);
builder.method(POST.METHOD);
final MultipartTypedBody body = new MultipartTypedBody();
body.add("upload", new FileTypedData(tmp));
builder.body(body);
final RestHttpResponse response = client.execute(builder.build());
// Responses from the server (code and message)
final int serverResponseCode = response.getStatus();
ProfilingUtil.log(context, "server response code " + serverResponseCode);
if (serverResponseCode / 100 == 2) {
tmp.delete();
} else {
putBackProfile(context, tmp, file);
}
} catch (final IOException e) {
e.printStackTrace();
putBackProfile(context, tmp, file);
}
}
@Override
protected Object doInBackground(final Object... params) {
final SharedPreferences prefs = context.getSharedPreferences("ucd_data_profiling", Context.MODE_PRIVATE);
if (prefs.contains(LAST_UPLOAD_DATE)) {
final long lastUpload = prefs.getLong(LAST_UPLOAD_DATE, System.currentTimeMillis());
final double deltaDays = (System.currentTimeMillis() - lastUpload) / (MILLSECS_HALF_DAY * 2);
if (deltaDays < 1) {
ProfilingUtil.log(context, "Uploaded less than 1 day ago.");
return null;
}
}
final File root = context.getFilesDir();
final File[] files = root.listFiles(new CSVFileFilter());
uploadToNode(files);
prefs.edit().putLong(LAST_UPLOAD_DATE, System.currentTimeMillis()).apply();
return null;
}
private boolean uploadToNode(final File... files) {
for (final File file : files) {
if (file.isDirectory()) {
continue;
}
final String url = PROFILE_SERVER_URL + "/" + device_id + "/"
+ file.getName().replaceFirst("[.][^.]+$", "");
ProfilingUtil.log(context, url);
uploadMultipart(url, file);
}
return false;
}
public static void putBackProfile(final Context context, final File tmp, final File profile) {
boolean success;
if (profile.exists()) {
try {
final FileOutputStream os = new FileOutputStream(tmp, true);
final FileInputStream is = new FileInputStream(profile);
copyStream(is, os);
is.close();
os.close();
success = true;
} catch (final IOException e) {
e.printStackTrace();
success = false;
}
if (success && tmp.renameTo(profile) && tmp.delete()) {
ProfilingUtil.log(context, "put profile back success");
} else {
ProfilingUtil.log(context, "put profile back failed");
}
} else {
if (tmp.renameTo(profile)) {
ProfilingUtil.log(context, "put profile back success");
} else {
ProfilingUtil.log(context, "put profile back failed");
}
}
}
}

View File

@ -50,7 +50,6 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.OAuthPasswordAuthenticator;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.TwitterAPIUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
@ -238,7 +237,7 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity {
consumerSecret = defConsumerSecret;
}
try {
final Endpoint endpoint = new Endpoint(TwitterAPIUtils.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", "oauth"));
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", "oauth"));
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

@ -87,6 +87,7 @@ import org.mariotaku.twidere.model.SupportTabSpec;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.service.StreamingService;
import org.mariotaku.twidere.util.AsyncTaskUtils;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.CustomTabUtils;
@ -120,7 +121,6 @@ import java.util.Map.Entry;
import edu.tsinghua.spice.Utilies.NetworkStateUtil;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.ucdavis.earlybird.ProfilingUtil;
import static org.mariotaku.twidere.util.CompareUtils.classEquals;
import static org.mariotaku.twidere.util.Utils.cleanDatabasesByItemLimit;
@ -428,6 +428,8 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
final int initialTabPosition = handleIntent(intent, savedInstanceState == null);
setTabPosition(initialTabPosition);
startService(new Intent(this, StreamingService.class));
}
@Override
@ -440,8 +442,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
final Bus bus = TwidereApplication.getInstance(this).getMessageBus();
assert bus != null;
bus.register(this);
// UCD
ProfilingUtil.profile(this, ProfilingUtil.FILE_NAME_APP, "App onStart");
// spice
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Launch" + "," + Build.MODEL
+ "," + "mediaPreview=" + mPreferences.getBoolean(KEY_MEDIA_PREVIEW, false));
@ -480,8 +480,6 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
mPreferences.edit().putInt(KEY_SAVED_TAB_POSITION, mViewPager.getCurrentItem()).apply();
sendBroadcast(new Intent(BROADCAST_HOME_ACTIVITY_ONSTOP));
// UCD
ProfilingUtil.profile(this, ProfilingUtil.FILE_NAME_APP, "App onStop");
// spice
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_APP, "App Stop");
SpiceProfilingUtil.profile(this, SpiceProfilingUtil.FILE_NAME_ONLAUNCH, "App Stop" + "," + NetworkStateUtil.getConnectedType(this) + "," + Build.MODEL);
@ -621,6 +619,9 @@ public class HomeActivity extends BaseAppCompatActivity implements OnClickListen
@Override
protected void onDestroy() {
stopService(new Intent(this, StreamingService.class));
// Delete unused items in databases.
cleanDatabasesByItemLimit(this);
sendBroadcast(new Intent(BROADCAST_HOME_ACTIVITY_ONDESTROY));

View File

@ -91,7 +91,6 @@ import org.mariotaku.twidere.util.ThemeUtils;
import org.mariotaku.twidere.util.TwidereActionModeForChildListener;
import org.mariotaku.twidere.util.TwidereColorUtils;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.TwitterAPIUtils;
import org.mariotaku.twidere.util.support.ViewSupport;
import org.mariotaku.twidere.util.support.view.ViewOutlineProviderCompat;
import org.mariotaku.twidere.view.iface.TintedStatusLayout;
@ -599,7 +598,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
protected SignInResponse doInBackground(final Object... params) {
try {
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(context, endpoint,
new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret()), TwitterOAuth.class);
final OAuthToken accessToken = oauth.getAccessToken(requestToken, oauthVerifier);
@ -684,7 +683,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
private SignInResponse authBasic() throws TwitterException {
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Authorization auth = new BasicAuthorization(username, password);
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint, auth, Twitter.class);
final User user = twitter.verifyCredentials();
@ -698,9 +697,9 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
private SignInResponse authOAuth() throws AuthenticationException, TwitterException {
String endpointUrl, signEndpointUrl;
endpointUrl = TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", null);
endpointUrl = TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", null);
if (!sameOAuthSigningUrl) {
signEndpointUrl = TwitterAPIUtils.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", null);
signEndpointUrl = TwitterAPIFactory.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", null);
} else {
signEndpointUrl = endpointUrl;
}
@ -712,9 +711,9 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
final long userId = accessToken.getUserId();
if (userId <= 0) return new SignInResponse(false, false, null);
final String versionSuffix = noVersionSuffix ? null : "1.1";
endpointUrl = TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", versionSuffix);
endpointUrl = TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix);
if (!sameOAuthSigningUrl) {
signEndpointUrl = TwitterAPIUtils.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", versionSuffix);
signEndpointUrl = TwitterAPIFactory.getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, "api", versionSuffix);
} else {
signEndpointUrl = endpointUrl;
}
@ -730,7 +729,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
private SignInResponse authTwipOMode() throws TwitterException {
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Authorization auth = new EmptyAuthorization();
final Twitter twitter = TwitterAPIFactory.getInstance(context, endpoint, auth, Twitter.class);
final User user = twitter.verifyCredentials();
@ -742,7 +741,7 @@ public class SignInActivity extends BaseAppCompatActivity implements OnClickList
private SignInResponse authxAuth() throws TwitterException {
final String versionSuffix = noVersionSuffix ? null : "1.1";
final Endpoint endpoint = new Endpoint(TwitterAPIUtils.getApiUrl(apiUrlFormat, "api", versionSuffix));
final Endpoint endpoint = new Endpoint(TwitterAPIFactory.getApiUrl(apiUrlFormat, "api", versionSuffix));
OAuthAuthorization auth = new OAuthAuthorization(consumerKey.getOauthToken(), consumerKey.getOauthTokenSecret());
final TwitterOAuth oauth = TwitterAPIFactory.getInstance(context, endpoint, auth, TwitterOAuth.class);
final OAuthToken accessToken = oauth.getAccessToken(username, password, TwitterOAuth.XAuthMode.CLIENT);

View File

@ -19,6 +19,8 @@
package org.mariotaku.twidere.api.twitter;
import android.util.Log;
import com.bluelinelabs.logansquare.LoganSquare;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -42,6 +44,8 @@ import java.io.InputStreamReader;
*/
public abstract class UserStreamCallback implements RawCallback {
private boolean connected;
private boolean disconnected;
@Override
@ -54,78 +58,93 @@ public abstract class UserStreamCallback implements RawCallback {
}
final ObjectMapper mapper = new ObjectMapper(LoganSquare.JSON_FACTORY);
final CRLFLineReader reader = new CRLFLineReader(new InputStreamReader(response.getBody().stream(), "UTF-8"));
for (String line; (line = reader.readLine()) != null && !disconnected; ) {
if (line.isEmpty()) continue;
JsonNode rootNode = mapper.readTree(line);
switch (JSONObjectType.determine(rootNode)) {
case SENDER: {
break;
try {
for (String line; (line = reader.readLine()) != null && !disconnected; ) {
if (!connected) {
onConnected();
connected = true;
}
case STATUS: {
onStatus(LoganSquare.mapperFor(Status.class).parse(rootNode.traverse()));
break;
if (line.isEmpty()) continue;
JsonNode rootNode = mapper.readTree(line);
switch (JSONObjectType.determine(rootNode)) {
case SENDER: {
break;
}
case STATUS: {
onStatus(LoganSquare.mapperFor(Status.class).parse(rootNode.traverse()));
break;
}
case DIRECT_MESSAGE: {
onDirectMessage(LoganSquare.mapperFor(DirectMessage.class).parse(rootNode.traverse()));
break;
}
case DELETE: {
break;
}
case LIMIT:
break;
case STALL_WARNING:
break;
case SCRUB_GEO:
break;
case FRIENDS:
break;
case FAVORITE: {
onFavorite(parse(User.class, rootNode.get("source")),
parse(User.class, rootNode.get("target")),
parse(Status.class, rootNode.get("target_object")));
break;
}
case UNFAVORITE: {
onUnfavorite(parse(User.class, rootNode.get("source")),
parse(User.class, rootNode.get("target")),
parse(Status.class, rootNode.get("target_object")));
break;
}
case FOLLOW:
break;
case UNFOLLOW:
break;
case USER_LIST_MEMBER_ADDED:
break;
case USER_LIST_MEMBER_DELETED:
break;
case USER_LIST_SUBSCRIBED:
break;
case USER_LIST_UNSUBSCRIBED:
break;
case USER_LIST_CREATED:
break;
case USER_LIST_UPDATED:
break;
case USER_LIST_DESTROYED:
break;
case USER_UPDATE:
break;
case USER_DELETE:
break;
case USER_SUSPEND:
break;
case BLOCK:
break;
case UNBLOCK:
break;
case DISCONNECTION:
break;
case UNKNOWN:
break;
}
case DIRECT_MESSAGE: {
onDirectMessage(LoganSquare.mapperFor(DirectMessage.class).parse(rootNode.traverse()));
break;
}
case DELETE: {
break;
}
case LIMIT:
break;
case STALL_WARNING:
break;
case SCRUB_GEO:
break;
case FRIENDS:
break;
case FAVORITE: {
onFavorite(parse(User.class, rootNode.get("source")),
parse(User.class, rootNode.get("target")),
parse(Status.class, rootNode.get("target_object")));
break;
}
case UNFAVORITE:
break;
case FOLLOW:
break;
case UNFOLLOW:
break;
case USER_LIST_MEMBER_ADDED:
break;
case USER_LIST_MEMBER_DELETED:
break;
case USER_LIST_SUBSCRIBED:
break;
case USER_LIST_UNSUBSCRIBED:
break;
case USER_LIST_CREATED:
break;
case USER_LIST_UPDATED:
break;
case USER_LIST_DESTROYED:
break;
case USER_UPDATE:
break;
case USER_DELETE:
break;
case USER_SUSPEND:
break;
case BLOCK:
break;
case UNBLOCK:
break;
case DISCONNECTION:
break;
case UNKNOWN:
break;
}
} catch (IOException e) {
onException(e);
} finally {
Log.d("Twidere.Stream", "Cleaning up...");
reader.close();
response.close();
}
reader.close();
response.close();
}
private static <T> T parse(final Class<T> cls, final JsonNode json) throws IOException {
return LoganSquare.mapperFor(cls).parse(json.traverse());
}
@ -139,6 +158,8 @@ public abstract class UserStreamCallback implements RawCallback {
disconnected = true;
}
public abstract void onConnected();
public abstract void onBlock(User source, User blockedUser);
public abstract void onDeletionNotice(long directMessageId, long userId);

View File

@ -55,6 +55,7 @@ import org.mariotaku.twidere.activity.MainHondaJOJOActivity;
import org.mariotaku.twidere.service.RefreshService;
import org.mariotaku.twidere.util.AsyncTaskManager;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.DebugModeUtils;
import org.mariotaku.twidere.util.ErrorLogger;
import org.mariotaku.twidere.util.KeyboardShortcutsHandler;
import org.mariotaku.twidere.util.MediaLoaderWrapper;
@ -73,7 +74,6 @@ import org.mariotaku.twidere.util.net.TwidereHostAddressResolver;
import java.io.File;
import edu.tsinghua.spice.SpiceService;
import edu.ucdavis.earlybird.UCDService;
import static org.mariotaku.twidere.util.Utils.getBestCacheDir;
import static org.mariotaku.twidere.util.Utils.getInternalCacheDir;
@ -236,6 +236,7 @@ public class TwidereApplication extends MultiDexApplication implements Constants
StrictModeUtils.detectAllVmPolicy();
}
super.onCreate();
initDebugMode();
initBugReport();
mDefaultUserAgent = UserAgentUtils.getDefaultUserAgentString(this);
mHandler = new Handler();
@ -269,6 +270,10 @@ public class TwidereApplication extends MultiDexApplication implements Constants
reloadConnectivitySettings();
}
private void initDebugMode() {
DebugModeUtils.initForApplication(this);
}
private void initBugReport() {
ACRA.init(this);
ErrorLogger.setEnabled(BuildConfig.DEBUG);
@ -313,7 +318,6 @@ public class TwidereApplication extends MultiDexApplication implements Constants
|| KEY_PROXY_PORT.equals(key)) {
reloadConnectivitySettings();
} else if (KEY_USAGE_STATISTICS.equals(key)) {
stopService(new Intent(this, UCDService.class));
//spice
stopService(new Intent(this, SpiceService.class));
startUsageStatisticsServiceIfNeeded(this);

View File

@ -19,6 +19,8 @@
package org.mariotaku.twidere.fragment;
import android.support.annotation.Nullable;
import org.mariotaku.twidere.R;
public class AccountRefreshSettingsFragment extends BaseAccountPreferenceFragment {
@ -34,7 +36,8 @@ public class AccountRefreshSettingsFragment extends BaseAccountPreferenceFragmen
}
@Override
@Nullable
protected String getSwitchPreferenceKey() {
return KEY_AUTO_REFRESH;
return null;
}
}

View File

@ -28,6 +28,8 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
@ -78,12 +80,15 @@ public abstract class BaseAccountPreferenceFragment extends PreferenceFragment i
@Override
public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.menu_switch_preference, menu);
final View actionView = menu.findItem(MENU_TOGGLE).getActionView();
final CompoundButton toggle = (CompoundButton) actionView.findViewById(android.R.id.toggle);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
toggle.setOnCheckedChangeListener(this);
toggle.setChecked(prefs.getBoolean(getSwitchPreferenceKey(), getSwitchPreferenceDefault()));
final String switchKey = getSwitchPreferenceKey();
if (!TextUtils.isEmpty(switchKey)) {
inflater.inflate(R.menu.menu_switch_preference, menu);
final View actionView = menu.findItem(MENU_TOGGLE).getActionView();
final CompoundButton toggle = (CompoundButton) actionView.findViewById(android.R.id.toggle);
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
toggle.setOnCheckedChangeListener(this);
toggle.setChecked(prefs.getBoolean(switchKey, getSwitchPreferenceDefault()));
}
super.onCreateOptionsMenu(menu, inflater);
}
@ -104,6 +109,7 @@ public abstract class BaseAccountPreferenceFragment extends PreferenceFragment i
protected abstract boolean getSwitchPreferenceDefault();
@Nullable
protected abstract String getSwitchPreferenceKey();
private void updatePreferenceScreen() {

View File

@ -136,6 +136,7 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
private ActionMenuView mAccountsToggleMenu;
private View mAccountProfileContainer;
private View mNoAccountContainer;
private ActionMenuView mActionMenuView;
private Context mThemedContext;
private MediaLoaderWrapper mImageLoader;
@ -313,30 +314,6 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
break;
}
}
} else if (adapter instanceof AppMenuAdapter) {
if (!(item instanceof OptionItem)) return;
final OptionItem option = (OptionItem) item;
switch (option.id) {
case MENU_ACCOUNTS: {
Utils.openAccountsManager(getActivity());
break;
}
case MENU_DRAFTS: {
Utils.openDrafts(getActivity());
break;
}
case MENU_FILTERS: {
Utils.openFilters(getActivity());
break;
}
case MENU_SETTINGS: {
final Intent intent = new Intent(getActivity(), SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityForResult(intent, REQUEST_SETTINGS);
break;
}
}
closeAccountsDrawer();
}
}
@ -431,11 +408,41 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
mAdapter.addView(mAccountSelectorView, true);
mAdapter.addAdapter(mAccountOptionsAdapter);
mAdapter.addView(mAppMenuSectionView, false);
mAdapter.addAdapter(mAppMenuAdapter);
// mAdapter.addView(mAppMenuSectionView, false);
// mAdapter.addAdapter(mAppMenuAdapter);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(this);
mPreferences.registerOnSharedPreferenceChangeListener(this);
menuInflater.inflate(R.menu.menu_dashboard, mActionMenuView.getMenu());
mActionMenuView.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem item) {
switch (item.getItemId()) {
case MENU_ACCOUNTS: {
Utils.openAccountsManager(getActivity());
return true;
}
case MENU_DRAFTS: {
Utils.openDrafts(getActivity());
return true;
}
case MENU_FILTERS: {
Utils.openFilters(getActivity());
return true;
}
case MENU_SETTINGS: {
final Intent intent = new Intent(getActivity(), SettingsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivityForResult(intent, REQUEST_SETTINGS);
return true;
}
}
return false;
}
});
ThemeUtils.resetCheatSheet(mActionMenuView);
getLoaderManager().initLoader(0, null, this);
}
@ -448,6 +455,7 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
public void onBaseViewCreated(View view, Bundle savedInstanceState) {
super.onBaseViewCreated(view, savedInstanceState);
mListView = (ListView) view.findViewById(android.R.id.list);
mActionMenuView = (ActionMenuView) view.findViewById(R.id.dashboard_menu);
}
@Override
@ -849,7 +857,7 @@ public class AccountsDashboardFragment extends BaseSupportFragment implements Lo
private final int mActionIconColor;
public OptionItemsAdapter(final Context context) {
super(context, R.layout.list_item_menu);
super(context, R.layout.list_item_dashboard_menu);
mActionIconColor = ThemeUtils.getThemeForegroundColor(context);
}

View File

@ -19,6 +19,7 @@
package org.mariotaku.twidere.fragment.support;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
@ -295,6 +296,16 @@ public class DirectMessagesFragment extends AbsContentRecyclerViewFragment<Messa
return super.onOptionsItemSelected(item);
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
final FragmentActivity activity = getActivity();
if (isVisibleToUser && activity != null) {
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID_MENTIONS_TIMELINE);
}
}
protected long getAccountId() {
final Bundle args = getArguments();
return args != null ? args.getLong(EXTRA_ACCOUNT_ID, -1) : -1;

View File

@ -19,7 +19,10 @@
package org.mariotaku.twidere.fragment.support;
import android.app.NotificationManager;
import android.content.Context;
import android.net.Uri;
import android.support.v4.app.FragmentActivity;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
@ -65,8 +68,19 @@ public class HomeTimelineFragment extends CursorStatusesFragment {
return twitter.getHomeTimelineAsync(accountIds, maxIds, sinceIds);
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
final FragmentActivity activity = getActivity();
if (isVisibleToUser && activity != null) {
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID_HOME_TIMELINE);
}
}
@Override
protected String getReadPositionTag() {
return TAB_TYPE_HOME_TIMELINE;
}
}

View File

@ -19,9 +19,11 @@
package org.mariotaku.twidere.fragment.support;
import android.app.NotificationManager;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import org.mariotaku.twidere.adapter.CursorStatusesAdapter;
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
@ -75,6 +77,16 @@ public class MentionsTimelineFragment extends CursorStatusesFragment {
return twitter.getMentionsTimelineAsync(accountIds, maxIds, sinceIds);
}
@Override
public void setUserVisibleHint(final boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
final FragmentActivity activity = getActivity();
if (isVisibleToUser && activity != null) {
final NotificationManager nm = (NotificationManager) activity.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID_MENTIONS_TIMELINE);
}
}
@Override
protected String getReadPositionTag() {
return TAB_TYPE_MENTIONS_TIMELINE;

View File

@ -93,6 +93,10 @@ public class AccountPreferences implements Constants {
return mPreferences.getBoolean(KEY_AUTO_REFRESH_TRENDS, DEFAULT_AUTO_REFRESH_TRENDS);
}
public boolean isStreamingEnabled() {
return mPreferences.getBoolean(KEY_ENABLE_STREAMING, false);
}
public boolean isDirectMessagesNotificationEnabled() {
return mPreferences.getBoolean(KEY_DIRECT_MESSAGES_NOTIFICATION, DEFAULT_DIRECT_MESSAGES_NOTIFICATION);
}

View File

@ -27,16 +27,12 @@ import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;
import com.nostra13.universalimageloader.core.assist.FailReason;
@ -55,6 +51,7 @@ import java.util.List;
public abstract class AccountsListPreference extends PreferenceCategory implements Constants {
private static final int[] ATTRS = {R.attr.switchKey, R.attr.switchDefault};
@Nullable
private final String mSwitchKey;
private final boolean mSwitchDefault;
@ -77,8 +74,8 @@ public abstract class AccountsListPreference extends PreferenceCategory implemen
public void setAccountsData(final List<ParcelableAccount> accounts) {
removeAll();
for (final ParcelableAccount account : accounts) {
final AccountItemPreference preference = new AccountItemPreference(getContext(), account, mSwitchKey,
mSwitchDefault);
final AccountItemPreference preference = new AccountItemPreference(getContext(), account,
mSwitchKey, mSwitchDefault);
setupPreference(preference, account);
addPreference(preference);
}
@ -96,39 +93,23 @@ public abstract class AccountsListPreference extends PreferenceCategory implemen
protected abstract void setupPreference(AccountItemPreference preference, ParcelableAccount account);
public static final class AccountItemPreference extends Preference implements ImageLoadingListener,
OnCheckedChangeListener, OnSharedPreferenceChangeListener, OnPreferenceClickListener, OnClickListener {
OnSharedPreferenceChangeListener {
private final ParcelableAccount mAccount;
private final SharedPreferences mSwitchPreference;
private final MediaLoaderWrapper mImageLoader;
private final String mSwitchKey;
private final boolean mSwitchDefault;
private CompoundButton mToggle;
public AccountItemPreference(final Context context, final ParcelableAccount account, final String switchKey,
final boolean switchDefault) {
super(context);
setWidgetLayoutResource(R.layout.preference_widget_account_preference_item);
setOnPreferenceClickListener(this);
final String switchPreferenceName = ACCOUNT_PREFERENCES_NAME_PREFIX + account.account_id;
mAccount = account;
mSwitchPreference = context.getSharedPreferences(switchPreferenceName, Context.MODE_PRIVATE);
final TwidereApplication app = TwidereApplication.getInstance(context);
mImageLoader = app.getMediaLoaderWrapper();
mSwitchKey = switchKey;
mSwitchDefault = switchDefault;
mSwitchPreference.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
if (mSwitchKey == null) return;
final SharedPreferences.Editor editor = mSwitchPreference.edit();
editor.putBoolean(mSwitchKey, isChecked);
editor.apply();
}
@Override
public void onLoadingCancelled(final String imageUri, final View view) {
// setIcon(R.drawable.ic_profile_image_default);
@ -164,16 +145,6 @@ public abstract class AccountsListPreference extends PreferenceCategory implemen
mImageLoader.loadProfileImage(mAccount.profile_image_url, this);
}
@Override
protected View onCreateView(ViewGroup parent) {
final View view = super.onCreateView(parent);
view.findViewById(R.id.settings).setOnClickListener(this);
final CompoundButton toggle = (CompoundButton) view.findViewById(android.R.id.toggle);
toggle.setOnCheckedChangeListener(this);
mToggle = toggle;
return view;
}
@Override
protected void onBindView(@NonNull final View view) {
super.onBindView(view);
@ -190,26 +161,6 @@ public abstract class AccountsListPreference extends PreferenceCategory implemen
if (summaryView instanceof TextView) {
((TextView) summaryView).setSingleLine(true);
}
final CompoundButton toggle = (CompoundButton) view.findViewById(android.R.id.toggle);
if (mSwitchKey != null) {
toggle.setChecked(mSwitchPreference.getBoolean(mSwitchKey, mSwitchDefault));
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (mToggle != null) {
mToggle.toggle();
}
return true;
}
@Override
public void onClick(View v) {
final Context context = getContext();
if (!(context instanceof PreferenceActivity)) return;
final PreferenceActivity activity = (PreferenceActivity) context;
activity.startPreferencePanel(getFragment(), getExtras(), getTitleRes(), getTitle(), null, 0);
}
}

View File

@ -28,8 +28,6 @@ import org.jraf.android.backport.switchwidget.SwitchPreference;
public class AutoFixSwitchPreference extends SwitchPreference {
private View mCachedView;
public AutoFixSwitchPreference(final Context context) {
super(context);
}

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 org.mariotaku.twidere.preference;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.AttributeSet;
import org.jraf.android.backport.switchwidget.SwitchPreference;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.util.ThemeUtils;
public class DarkLightThemeTogglePreference extends SwitchPreference implements Constants {
public DarkLightThemeTogglePreference(final Context context) {
this(context, null);
}
public DarkLightThemeTogglePreference(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.asb_switchPreferenceStyle);
}
public DarkLightThemeTogglePreference(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected boolean getPersistedBoolean(final boolean defaultReturnValue) {
final SharedPreferences preferences = getSharedPreferences();
return ThemeUtils.isDarkTheme(getPersistedString(VALUE_THEME_NAME_TWIDERE));
}
@Override
protected boolean persistBoolean(final boolean value) {
return persistString(value ? VALUE_THEME_NAME_DARK : VALUE_THEME_NAME_TWIDERE);
}
}

View File

@ -1,6 +1,5 @@
package org.mariotaku.twidere.extension.streaming;
package org.mariotaku.twidere.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
@ -13,14 +12,19 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.LongSparseArray;
import android.util.Log;
import android.widget.Toast;
import org.mariotaku.restfu.http.Authorization;
import org.mariotaku.restfu.http.ContentType;
import org.mariotaku.restfu.http.Endpoint;
import org.mariotaku.twidere.Twidere;
import org.mariotaku.twidere.TwidereSharedPreferences;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.TypedData;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.activity.SettingsActivity;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.TwitterUserStream;
import org.mariotaku.twidere.api.twitter.UserStreamCallback;
@ -30,7 +34,7 @@ import org.mariotaku.twidere.api.twitter.model.StatusDeletionNotice;
import org.mariotaku.twidere.api.twitter.model.User;
import org.mariotaku.twidere.api.twitter.model.UserList;
import org.mariotaku.twidere.api.twitter.model.Warning;
import org.mariotaku.twidere.extension.streaming.util.Utils;
import org.mariotaku.twidere.model.AccountPreferences;
import org.mariotaku.twidere.model.ParcelableAccount;
import org.mariotaku.twidere.model.ParcelableCredentials;
import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
@ -38,19 +42,21 @@ import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages;
import org.mariotaku.twidere.provider.TwidereDataStore.Mentions;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import org.mariotaku.twidere.util.TwitterAPIUtils;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.Utils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
public class StreamingService extends Service implements Constants, PrivateConstants {
public class StreamingService extends Service implements Constants {
private static final int NOTIFICATION_SERVICE_STARTED = 1;
private static final int NOTIFICATION_REQUEST_PERMISSION = 2;
private final List<WeakReference<TwidereUserStreamCallback>> mTwitterInstances = new ArrayList<>();
private final LongSparseArray<UserStreamCallback> mCallbacks = new LongSparseArray<>();
private ContentResolver mResolver;
private SharedPreferences mPreferences;
@ -86,11 +92,11 @@ public class StreamingService extends Service implements Constants, PrivateConst
@Override
public void onCreate() {
super.onCreate();
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mPreferences = SharedPreferencesWrapper.getInstance(this, SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
mResolver = getContentResolver();
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, "Stream service started.");
Log.d(Constants.LOGTAG, "Stream service started.");
}
initStreaming();
mResolver.registerContentObserver(Accounts.CONTENT_URI, true, mAccountChangeObserver);
@ -101,103 +107,93 @@ public class StreamingService extends Service implements Constants, PrivateConst
clearTwitterInstances();
mResolver.unregisterContentObserver(mAccountChangeObserver);
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, "Stream service stopped.");
Log.d(Constants.LOGTAG, "Stream service stopped.");
}
super.onDestroy();
}
private void clearTwitterInstances() {
for (final WeakReference<TwidereUserStreamCallback> reference : mTwitterInstances) {
final TwidereUserStreamCallback twitter = reference.get();
new Thread(new ShutdownStreamTwitterRunnable(twitter)).start();
for (int i = 0, j = mCallbacks.size(); i < j; i++) {
new Thread(new ShutdownStreamTwitterRunnable(mCallbacks.valueAt(i))).start();
}
mTwitterInstances.clear();
mCallbacks.clear();
mNotificationManager.cancel(NOTIFICATION_SERVICE_STARTED);
}
@SuppressWarnings("deprecation")
private void initStreaming() {
if (!mPreferences.getBoolean(PREFERENCE_KEY_ENABLE_STREAMING, true)) return;
final boolean granted;
try {
granted = Twidere.isPermissionGranted(this);
} catch (final SecurityException e) {
stopSelf();
return;
}
if (granted) {
final TwidereSharedPreferences prefs = Twidere.getSharedPreferences(this);
if (setTwitterInstances(prefs)) {
final Intent intent = new Intent(this, SettingsActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
final CharSequence contentTitle = getString(R.string.app_name);
final CharSequence contentText = getString(R.string.streaming_service_running);
final Notification notification = new Notification();
notification.flags = Notification.FLAG_ONGOING_EVENT;
notification.icon = R.drawable.ic_stat_twidere;
notification.tickerText = getString(R.string.streaming_service_running);
notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent);
mNotificationManager.notify(NOTIFICATION_SERVICE_STARTED, notification);
} else {
mNotificationManager.cancel(NOTIFICATION_SERVICE_STARTED);
}
} else {
final Intent intent = new Intent(this, SettingsActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
final CharSequence contentTitle = getString(R.string.app_name);
final CharSequence contentText = getString(R.string.request_permission);
final Notification notification = new Notification();
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.icon = R.drawable.ic_stat_login;
notification.tickerText = getString(R.string.request_permission);
notification.setLatestEventInfo(this, contentTitle, contentText, contentIntent);
mNotificationManager.notify(NOTIFICATION_REQUEST_PERMISSION, notification);
}
if (!BuildConfig.DEBUG) return;
setTwitterInstances();
updateStreamState();
}
private boolean setTwitterInstances(final TwidereSharedPreferences prefs) {
if (prefs == null) return false;
private boolean setTwitterInstances() {
final List<ParcelableCredentials> accountsList = ParcelableAccount.getCredentialsList(this, true);
if (BuildConfig.DEBUG) {
Log.d(LOGTAG, "Setting up twitter stream instances");
final long[] accountIds = new long[accountsList.size()];
for (int i = 0, j = accountIds.length; i < j; i++) {
accountIds[i] = accountsList.get(i).account_id;
}
mAccountIds = new long[accountsList.size()];
final AccountPreferences[] activitedPreferences = AccountPreferences.getAccountPreferences(this, accountIds);
if (BuildConfig.DEBUG) {
Log.d(Constants.LOGTAG, "Setting up twitter stream instances");
}
mAccountIds = accountIds;
clearTwitterInstances();
boolean result = false;
for (int i = 0, j = accountsList.size(); i < j; i++) {
final AccountPreferences preferences = activitedPreferences[i];
if (!preferences.isStreamingEnabled()) continue;
final ParcelableCredentials account = accountsList.get(i);
final Endpoint endpoint = TwitterAPIUtils.getEndpoint(account, TwitterUserStream.class);
final Authorization authorization = TwitterAPIUtils.getAuthorization(account);
final TwitterUserStream twitter = Utils.getInstance(this, endpoint, authorization, TwitterUserStream.class);
final long account_id = account.account_id;
mAccountIds[i] = account_id;
final Endpoint endpoint = TwitterAPIFactory.getEndpoint(account, TwitterUserStream.class);
final Authorization authorization = TwitterAPIFactory.getAuthorization(account);
final TwitterUserStream twitter = TwitterAPIFactory.getInstance(this, endpoint, authorization, TwitterUserStream.class);
final TwidereUserStreamCallback callback = new TwidereUserStreamCallback(this, account);
mTwitterInstances.add(new WeakReference<>(callback));
mCallbacks.put(account.account_id, callback);
new Thread() {
@Override
public void run() {
twitter.getUserStream(callback);
Log.d(LOGTAG, "Stream disconnected");
Log.d(Constants.LOGTAG, String.format("Stream %d disconnected", account.account_id));
mCallbacks.remove(account.account_id);
updateStreamState();
}
}.start();
result |= true;
}
return result;
}
private void updateStreamState() {
if (mCallbacks.size() > 0) {
final Intent intent = new Intent(this, SettingsActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
final CharSequence contentTitle = getString(R.string.app_name);
final CharSequence contentText = getString(R.string.timeline_streaming_running);
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setOngoing(true);
builder.setSmallIcon(R.drawable.ic_stat_refresh);
builder.setContentTitle(contentTitle);
builder.setContentText(contentText);
builder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_SERVICE_STARTED, builder.build());
} else {
mNotificationManager.cancel(NOTIFICATION_SERVICE_STARTED);
}
return true;
}
static class ShutdownStreamTwitterRunnable implements Runnable {
private final TwidereUserStreamCallback twitter;
private final UserStreamCallback callback;
ShutdownStreamTwitterRunnable(final TwidereUserStreamCallback twitter) {
this.twitter = twitter;
ShutdownStreamTwitterRunnable(final UserStreamCallback callback) {
this.callback = callback;
}
@Override
public void run() {
if (twitter == null) return;
Log.d(LOGTAG, "Disconnecting stream");
twitter.disconnect();
if (callback == null) return;
Log.d(Constants.LOGTAG, "Disconnecting stream");
callback.disconnect();
}
}
@ -216,10 +212,15 @@ public class StreamingService extends Service implements Constants, PrivateConst
resolver = context.getContentResolver();
}
@Override
public void onConnected() {
}
@Override
public void onBlock(final User source, final User blockedUser) {
final String message = String.format("%s blocked %s", source.getScreenName(), blockedUser.getScreenName());
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
Log.d(LOGTAG, message);
}
@Override
@ -259,7 +260,7 @@ public class StreamingService extends Service implements Constants, PrivateConst
final ContentValues values = ContentValuesCreator.createDirectMessage(directMessage,
account.account_id, false);
final Uri.Builder builder = DirectMessages.Inbox.CONTENT_URI.buildUpon();
builder.appendQueryParameter(Twidere.QUERY_PARAM_NOTIFY, "true");
builder.appendQueryParameter(QUERY_PARAM_NOTIFY, "true");
if (values != null) {
resolver.insert(builder.build(), values);
}
@ -271,8 +272,35 @@ public class StreamingService extends Service implements Constants, PrivateConst
public void onException(final Throwable ex) {
if (ex instanceof TwitterException) {
Log.w(LOGTAG, String.format("Error %d", ((TwitterException) ex).getStatusCode()), ex);
final RestHttpResponse response = ((TwitterException) ex).getHttpResponse();
if (response != null) {
try {
final TypedData body = response.getBody();
final ByteArrayOutputStream os = new ByteArrayOutputStream();
body.writeTo(os);
final String charsetName;
if (body != null) {
final ContentType contentType = body.contentType();
if (contentType != null) {
final Charset charset = contentType.getCharset();
if (charset != null) {
charsetName = charset.name();
} else {
charsetName = Charset.defaultCharset().name();
}
} else {
charsetName = Charset.defaultCharset().name();
}
} else {
charsetName = Charset.defaultCharset().name();
}
Log.w(LOGTAG, os.toString(charsetName));
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
Log.w(LOGTAG, ex);
Log.w(Constants.LOGTAG, ex);
}
}
@ -280,14 +308,14 @@ public class StreamingService extends Service implements Constants, PrivateConst
public void onFavorite(final User source, final User target, final Status favoritedStatus) {
final String message = String.format("%s favorited %s's tweet: %s", source.getScreenName(),
target.getScreenName(), favoritedStatus.getText());
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
Log.d(LOGTAG, message);
}
@Override
public void onFollow(final User source, final User followedUser) {
final String message = String
.format("%s followed %s", source.getScreenName(), followedUser.getScreenName());
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
Log.d(LOGTAG, message);
}
@Override
@ -339,15 +367,14 @@ public class StreamingService extends Service implements Constants, PrivateConst
public void onUnblock(final User source, final User unblockedUser) {
final String message = String.format("%s unblocked %s", source.getScreenName(),
unblockedUser.getScreenName());
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
Log.d(LOGTAG, message);
}
@Override
public void onUnfavorite(final User source, final User target, final Status unfavoritedStatus) {
final String message = String.format("%s unfavorited %s's tweet: %s", source.getScreenName(),
target.getScreenName(), unfavoritedStatus.getText());
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
Log.d(LOGTAG, message);
}
@Override

View File

@ -105,7 +105,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.tsinghua.spice.Utilies.TypeMappingUtil;
import edu.ucdavis.earlybird.ProfilingUtil;
import static org.mariotaku.twidere.provider.TwidereDataStore.STATUSES_URIS;
import static org.mariotaku.twidere.util.ContentValuesCreator.createDirectMessage;
@ -2207,8 +2206,6 @@ public class AsyncTwitterWrapper extends TwitterWrapper {
rowsDeleted = 0;
}
countCur.close();
// UCD
ProfilingUtil.profile(mContext, accountId, "Download tweets, " + TwidereArrayUtils.toString(statusIds, ',', true));
//spice
SpiceProfilingUtil.profile(mContext, accountId, accountId + ",Refresh," + TwidereArrayUtils.toString(statusIds, ',', true));
//end

View File

@ -33,7 +33,6 @@ import org.mariotaku.twidere.util.TwidereLinkify.OnLinkClickListener;
import edu.tsinghua.spice.Utilies.SpiceProfilingUtil;
import edu.tsinghua.spice.Utilies.TypeMappingUtil;
import edu.ucdavis.earlybird.ProfilingUtil;
import static org.mariotaku.twidere.util.Utils.openStatus;
import static org.mariotaku.twidere.util.Utils.openTweetSearch;
@ -57,8 +56,6 @@ public class OnLinkClickHandler implements OnLinkClickListener, Constants {
final boolean sensitive, int start, int end) {
if (manager != null && manager.isActive()) return;
if (!isPrivateData()) {
// UCD
ProfilingUtil.profile(context, accountId, "Click, " + link + ", " + type);
//spice
SpiceProfilingUtil.profile(context, accountId, accountId + ",Visit," + link + "," + TypeMappingUtil.getLinkType(type));
//end

View File

@ -1111,6 +1111,10 @@ public class ThemeUtils implements Constants {
return context.getResources().getColor(R.color.background_color_action_bar_dark);
}
public static boolean isDarkTheme(final String name) {
return VALUE_THEME_NAME_DARK.equals(name);
}
public static final class ActionBarContextThemeWrapper extends android.support.v7.internal.view.ContextThemeWrapper {
public ActionBarContextThemeWrapper(Context base, int themeres) {

View File

@ -2,19 +2,43 @@ package org.mariotaku.twidere.util;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.SSLCertificateSocketFactory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Pair;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.internal.Internal;
import org.mariotaku.restfu.ExceptionFactory;
import org.mariotaku.restfu.HttpRequestFactory;
import org.mariotaku.restfu.RequestInfoFactory;
import org.mariotaku.restfu.RestAPIFactory;
import org.mariotaku.restfu.RestMethodInfo;
import org.mariotaku.restfu.RestRequestInfo;
import org.mariotaku.restfu.annotation.RestMethod;
import org.mariotaku.restfu.http.Authorization;
import org.mariotaku.restfu.http.Endpoint;
import org.mariotaku.restfu.http.FileValue;
import org.mariotaku.restfu.http.RestHttpClient;
import org.mariotaku.restfu.http.RestHttpRequest;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.StringTypedData;
import org.mariotaku.restfu.http.mime.TypedData;
import org.mariotaku.twidere.TwidereConstants;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.TwitterOAuth;
import org.mariotaku.twidere.api.twitter.TwitterUpload;
import org.mariotaku.twidere.api.twitter.TwitterUserStream;
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.OAuthToken;
import org.mariotaku.twidere.api.twitter.util.TwitterConverter;
import org.mariotaku.twidere.app.TwidereApplication;
import org.mariotaku.twidere.model.ConsumerKeyType;
@ -24,7 +48,13 @@ import org.mariotaku.twidere.util.net.OkHttpRestClient;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static android.text.TextUtils.isEmpty;
@ -109,29 +139,237 @@ public class TwitterAPIFactory implements TwidereConstants {
final String consumerSecret = ((OAuthAuthorization) auth).getConsumerSecret();
final ConsumerKeyType officialKeyType = TwitterContentUtils.getOfficialKeyType(context, consumerKey, consumerSecret);
if (officialKeyType != ConsumerKeyType.UNKNOWN) {
userAgent = TwitterAPIUtils.getUserAgentName(officialKeyType);
userAgent = getUserAgentName(officialKeyType);
} else {
userAgent = TwitterAPIUtils.getTwidereUserAgent(context);
userAgent = getTwidereUserAgent(context);
}
} else {
userAgent = TwitterAPIUtils.getTwidereUserAgent(context);
userAgent = getTwidereUserAgent(context);
}
factory.setClient(getDefaultHttpClient(context));
factory.setConverter(new TwitterConverter());
factory.setEndpoint(endpoint);
factory.setAuthorization(auth);
factory.setRequestInfoFactory(new TwitterAPIUtils.TwidereRequestInfoFactory());
factory.setHttpRequestFactory(new TwitterAPIUtils.TwidereHttpRequestFactory(userAgent));
factory.setExceptionFactory(new TwitterAPIUtils.TwidereExceptionFactory());
factory.setRequestInfoFactory(new TwidereRequestInfoFactory());
factory.setHttpRequestFactory(new TwidereHttpRequestFactory(userAgent));
factory.setExceptionFactory(new TwidereExceptionFactory());
return factory.build(cls);
}
public static <T> T getInstance(final Context context, final Endpoint endpoint, final ParcelableCredentials credentials, Class<T> cls) {
return TwitterAPIFactory.getInstance(context, endpoint, TwitterAPIUtils.getAuthorization(credentials), cls);
return TwitterAPIFactory.getInstance(context, endpoint, getAuthorization(credentials), cls);
}
static <T> T getInstance(final Context context, final ParcelableCredentials credentials, final Class<T> cls) {
if (credentials == null) return null;
return TwitterAPIFactory.getInstance(context, TwitterAPIUtils.getEndpoint(credentials, cls), credentials, cls);
return TwitterAPIFactory.getInstance(context, getEndpoint(credentials, cls), credentials, cls);
}
public static Endpoint getEndpoint(ParcelableCredentials credentials, Class<?> cls) {
final String apiUrlFormat;
final boolean sameOAuthSigningUrl = credentials.same_oauth_signing_url;
final boolean noVersionSuffix = credentials.no_version_suffix;
if (!isEmpty(credentials.api_url_format)) {
apiUrlFormat = credentials.api_url_format;
} else {
apiUrlFormat = DEFAULT_TWITTER_API_URL_FORMAT;
}
final String domain, versionSuffix;
if (Twitter.class.isAssignableFrom(cls)) {
domain = "api";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else if (TwitterUpload.class.isAssignableFrom(cls)) {
domain = "upload";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else if (TwitterOAuth.class.isAssignableFrom(cls)) {
domain = "api";
versionSuffix = "oauth";
} else if (TwitterUserStream.class.isAssignableFrom(cls)) {
domain = "userstream";
versionSuffix = noVersionSuffix ? null : "/1.1/";
} else {
throw new TwitterConverter.UnsupportedTypeException(cls);
}
final String endpointUrl;
endpointUrl = getApiUrl(apiUrlFormat, domain, versionSuffix);
if (credentials.auth_type == ParcelableCredentials.AUTH_TYPE_XAUTH || credentials.auth_type == ParcelableCredentials.AUTH_TYPE_OAUTH) {
final String signEndpointUrl;
if (!sameOAuthSigningUrl) {
signEndpointUrl = getApiUrl(DEFAULT_TWITTER_API_URL_FORMAT, domain, versionSuffix);
} else {
signEndpointUrl = endpointUrl;
}
return new OAuthEndpoint(endpointUrl, signEndpointUrl);
}
return new Endpoint(endpointUrl);
}
public static Authorization getAuthorization(ParcelableCredentials credentials) {
switch (credentials.auth_type) {
case ParcelableCredentials.AUTH_TYPE_OAUTH:
case ParcelableCredentials.AUTH_TYPE_XAUTH: {
final String consumerKey = TextUtils.isEmpty(credentials.consumer_key) ?
TWITTER_CONSUMER_KEY_LEGACY : credentials.consumer_key;
final String consumerSecret = TextUtils.isEmpty(credentials.consumer_secret) ?
TWITTER_CONSUMER_SECRET_LEGACY : credentials.consumer_secret;
final OAuthToken accessToken = new OAuthToken(credentials.oauth_token, credentials.oauth_token_secret);
return new OAuthAuthorization(consumerKey, consumerSecret, accessToken);
}
case ParcelableCredentials.AUTH_TYPE_BASIC: {
final String screenName = credentials.screen_name;
final String username = credentials.basic_auth_username;
final String loginName = username != null ? username : screenName;
final String password = credentials.basic_auth_password;
if (isEmpty(loginName) || isEmpty(password)) return null;
return new BasicAuthorization(loginName, password);
}
}
return new EmptyAuthorization();
}
private static void addParameter(List<Pair<String, String>> params, String name, Object value) {
params.add(Pair.create(name, String.valueOf(value)));
}
private static void addPart(List<Pair<String, TypedData>> params, String name, Object value) {
final TypedData typedData = new StringTypedData(String.valueOf(value), Charset.defaultCharset());
params.add(Pair.create(name, typedData));
}
public static String getApiBaseUrl(String format, final String domain) {
if (format == null) return null;
final Matcher matcher = Pattern.compile("\\[(\\.?)DOMAIN(\\.?)\\]").matcher(format);
if (!matcher.find()) {
// For backward compatibility
format = substituteLegacyApiBaseUrl(format, domain);
if (!format.endsWith("/1.1") && !format.endsWith("/1.1/")) {
return format;
}
final String versionSuffix = "/1.1";
final int suffixLength = versionSuffix.length();
final int lastIndex = format.lastIndexOf(versionSuffix);
return format.substring(0, lastIndex) + format.substring(lastIndex + suffixLength);
}
if (TextUtils.isEmpty(domain)) return matcher.replaceAll("");
return matcher.replaceAll(String.format("$1%s$2", domain));
}
private static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
final int startOfHost = format.indexOf("://") + 3, endOfHost = format.indexOf('/', startOfHost);
final String host = endOfHost != -1 ? format.substring(startOfHost, endOfHost) : format.substring(startOfHost);
if (!host.equalsIgnoreCase("api.twitter.com")) return format;
return format.substring(0, startOfHost) + domain + ".twitter.com" + format.substring(endOfHost);
}
public static String getApiUrl(final String pattern, final String domain, final String appendPath) {
final String urlBase = getApiBaseUrl(pattern, domain);
if (urlBase == null) return null;
if (appendPath == null) return urlBase.endsWith("/") ? urlBase : urlBase + "/";
final StringBuilder sb = new StringBuilder(urlBase);
if (urlBase.endsWith("/")) {
sb.append(appendPath.startsWith("/") ? appendPath.substring(1) : appendPath);
} else {
if (appendPath.startsWith("/")) {
sb.append(appendPath);
} else {
sb.append('/');
sb.append(appendPath);
}
}
return sb.toString();
}
public static String getUserAgentName(ConsumerKeyType type) {
switch (type) {
case TWITTER_FOR_ANDROID: {
return "TwitterAndroid";
}
case TWITTER_FOR_IPHONE: {
return "Twitter-iPhone";
}
case TWITTER_FOR_IPAD: {
return "Twitter-iPad";
}
case TWITTER_FOR_MAC: {
return "Twitter-Mac";
}
}
return "Twitter";
}
public static String getTwidereUserAgent(final Context context) {
final PackageManager pm = context.getPackageManager();
try {
final PackageInfo pi = pm.getPackageInfo(TWIDERE_PACKAGE_NAME, 0);
return TWIDERE_APP_NAME + " " + TWIDERE_PROJECT_URL + " / " + pi.versionName;
} catch (final PackageManager.NameNotFoundException e) {
return TWIDERE_APP_NAME + " " + TWIDERE_PROJECT_URL;
}
}
public static class TwidereRequestInfoFactory implements RequestInfoFactory {
@Override
public RestRequestInfo create(RestMethodInfo methodInfo) {
final RestMethod method = methodInfo.getMethod();
final String path = methodInfo.getPath();
final List<Pair<String, String>> queries = new ArrayList<>(methodInfo.getQueries());
final List<Pair<String, String>> forms = new ArrayList<>(methodInfo.getForms());
final List<Pair<String, String>> headers = methodInfo.getHeaders();
final List<Pair<String, TypedData>> parts = methodInfo.getParts();
final FileValue file = methodInfo.getFile();
final Map<String, Object> extras = methodInfo.getExtras();
if (parts.isEmpty()) {
final List<Pair<String, String>> params = method.hasBody() ? forms : queries;
addParameter(params, "include_cards", true);
addParameter(params, "cards_platform", "Android-12");
addParameter(params, "include_entities", true);
addParameter(params, "include_my_retweet", 1);
addParameter(params, "include_rts", 1);
addParameter(params, "include_reply_count", true);
addParameter(params, "include_descendent_reply_count", true);
} else {
addPart(parts, "include_cards", true);
addPart(parts, "cards_platform", "Android-12");
addPart(parts, "include_entities", true);
addPart(parts, "include_my_retweet", 1);
addPart(parts, "include_rts", 1);
addPart(parts, "include_reply_count", true);
addPart(parts, "include_descendent_reply_count", true);
}
return new RestRequestInfo(method.value(), path, queries, forms, headers, parts, file,
methodInfo.getBody(), extras);
}
}
public static class TwidereHttpRequestFactory implements HttpRequestFactory {
private final String userAgent;
public TwidereHttpRequestFactory(final String userAgent) {
this.userAgent = userAgent;
}
@Override
public RestHttpRequest create(@NonNull Endpoint endpoint, @NonNull RestRequestInfo info,
@Nullable Authorization authorization) {
final String restMethod = info.getMethod();
final String url = Endpoint.constructUrl(endpoint.getUrl(), info);
final ArrayList<Pair<String, String>> headers = new ArrayList<>(info.getHeaders());
if (authorization != null && authorization.hasAuthorization()) {
headers.add(Pair.create("Authorization", authorization.getHeader(endpoint, info)));
}
headers.add(Pair.create("User-Agent", userAgent));
return new RestHttpRequest(restMethod, url, headers, info.getBody(), null);
}
}
public static class TwidereExceptionFactory implements ExceptionFactory {
@Override
public Exception newException(Throwable cause, RestHttpRequest request, RestHttpResponse response) {
final TwitterException te = new TwitterException(cause);
te.setResponse(response);
return te;
}
}
}

View File

@ -252,7 +252,6 @@ import java.util.zip.CRC32;
import javax.net.ssl.SSLException;
import edu.tsinghua.spice.SpiceService;
import edu.ucdavis.earlybird.UCDService;
import static android.text.TextUtils.isEmpty;
import static android.text.format.DateUtils.getRelativeTimeSpanString;
@ -3418,15 +3417,12 @@ public final class Utils implements Constants {
public static void startUsageStatisticsServiceIfNeeded(final Context context) {
final SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
final Intent profilingServiceIntent = new Intent(context, UCDService.class);
//spice
final Intent spiceProfilingServiceIntent = new Intent(context, SpiceService.class);
if (prefs.getBoolean(KEY_USAGE_STATISTICS, false)) {
context.startService(profilingServiceIntent);
//spice
context.startService(spiceProfilingServiceIntent);
} else {
context.stopService(profilingServiceIntent);
//spice
context.stopService(spiceProfilingServiceIntent);
}

View File

@ -51,7 +51,6 @@ import org.mariotaku.twidere.model.ParcelableMedia;
import org.mariotaku.twidere.util.MediaPreviewUtils;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.TwidereLinkify;
import org.mariotaku.twidere.util.TwitterAPIUtils;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.Utils;
@ -129,7 +128,7 @@ public class TwidereImageDownloader extends BaseImageDownloader implements Const
final String host = uri.getHost();
final String domain = host.substring(0, host.lastIndexOf(".twitter.com"));
final String path = uri.getPath();
sb.append(TwitterAPIUtils.getApiUrl(apiUrlFormat, domain, path));
sb.append(TwitterAPIFactory.getApiUrl(apiUrlFormat, domain, path));
final String query = uri.getQuery();
if (!TextUtils.isEmpty(query)) {
sb.append("?");
@ -152,7 +151,7 @@ public class TwidereImageDownloader extends BaseImageDownloader implements Const
if (isTwitterAuthRequired(uri) && extras instanceof AccountExtra) {
final AccountExtra accountExtra = (AccountExtra) extras;
account = ParcelableAccount.getCredentials(mContext, accountExtra.account_id);
auth = TwitterAPIUtils.getAuthorization(account);
auth = TwitterAPIFactory.getAuthorization(account);
} else {
account = null;
auth = null;

View File

@ -38,6 +38,7 @@ import org.mariotaku.restfu.http.RestHttpClient;
import org.mariotaku.restfu.http.RestHttpRequest;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.TypedData;
import org.mariotaku.twidere.util.DebugModeUtils;
import java.io.IOException;
import java.io.InputStream;
@ -60,6 +61,7 @@ public class OkHttpRestClient implements RestHttpClient {
public OkHttpRestClient(OkHttpClient client) {
this.client = client;
DebugModeUtils.initForHttpClient(client);
}
@NonNull

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<Space xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -18,12 +18,29 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ListView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:listSelector="?selectableItemBackground"
tools:context=".fragment.support.AccountsDashboardFragment"/>
android:divider="?dividerVertical"
android:orientation="vertical"
android:showDividers="middle">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="@null"
android:dividerHeight="0dp"
android:focusable="true"
android:listSelector="?selectableItemBackground"
tools:context=".fragment.support.AccountsDashboardFragment"/>
<android.support.v7.widget.ActionMenuView
android:id="@+id/dashboard_menu"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_weight="0"/>
</LinearLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
android:minHeight="?listPreferredItemHeightSmall"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_weight="0"
android:contentDescription="@string/icon"
android:scaleType="centerInside"/>
<TextView
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:textColorPrimary"
android:textStyle="bold"/>
</LinearLayout>

View File

@ -22,7 +22,7 @@
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
android:gravity="center_vertical"
android:minHeight="48dp"
android:minHeight="?listPreferredItemHeightSmall"
android:orientation="horizontal">
<ImageView

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@id/accounts"
android:layout_weight="0"
android:icon="@drawable/ic_action_accounts"
android:title="@string/accounts"
app:showAsAction="always"/>
<item
android:id="@id/filters"
android:layout_weight="0"
android:icon="@drawable/ic_action_speaker_muted"
android:title="@string/filters"
app:showAsAction="always"/>
<item
android:id="@id/settings"
android:layout_weight="0"
android:icon="@drawable/ic_action_settings"
android:title="@string/settings"
app:showAsAction="always"/>
<item
android:layout_weight="1"
android:enabled="false"
android:title=""
app:showAsAction="always|withText"/>
<item
android:layout_weight="0"
android:enabled="false"
android:icon="@android:color/transparent"
android:title="@null"
app:showAsAction="always"/>
</menu>

View File

@ -44,6 +44,7 @@
<string name="password">Password</string>
<string name="label_data_provider">Twidere database provider</string>
<string name="label_refresh_service">Refresh service</string>
<string name="label_streaming_service">Streaming service</string>
<string name="label_background_operation_service">Background operation service</string>
<string name="open_in_browser">Open in browser</string>
<string name="tap_to_load_more">Tap to load more</string>
@ -754,4 +755,8 @@
<string name="play">Play</string>
<string name="pause">Pause</string>
<string name="jump_to_top">Jump to top</string>
<string name="timeline_streaming_running">Timeline streaming running</string>
<string name="streaming">Streaming</string>
<string name="enable_streaming">Enable streaming</string>
<string name="dark_theme">Dark theme</string>
</resources>

View File

@ -3,7 +3,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/auto_refresh">
<org.mariotaku.twidere.preference.AutoFixSwitchPreference
android:disableDependentsState="false"
android:key="auto_refresh"
android:title="@string/auto_refresh"/>
<PreferenceCategory
android:dependency="auto_refresh"
android:key="cat_refresh_content"
android:title="@string/content_to_refresh">
<CheckBoxPreference
@ -24,4 +30,15 @@
android:title="@string/trends"/>
</PreferenceCategory>
<PreferenceCategory
android:enabled="false"
android:key="cat_streaming"
android:title="@string/streaming">
<org.mariotaku.twidere.preference.AutoFixSwitchPreference
android:key="enable_streaming"
android:title="@string/enable_streaming"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -18,12 +18,6 @@
android:summary="@string/silent_notifications_summary"
android:title="@string/silent_notifications"/>
<org.mariotaku.twidere.preference.AutoFixCheckBoxPreference
android:defaultValue="false"
android:key="background_toast_notification"
android:summary="@string/background_toast_notification_summary"
android:title="@string/background_toast_notification"/>
<org.mariotaku.twidere.preference.AutoFixCheckBoxPreference
android:defaultValue="false"
android:key="pebble_notifications"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/theme">
@ -7,20 +8,18 @@
android:key="cat_theme_preview"
android:order="11"
android:title="@string/preview">
<org.mariotaku.twidere.preference.ThemePreviewPreference android:key="theme_preview" />
<org.mariotaku.twidere.preference.ThemePreviewPreference
android:key="theme_preview"/>
</PreferenceCategory>
<org.mariotaku.twidere.preference.SummaryListPreference
android:defaultValue="twidere"
android:entries="@array/entries_theme"
android:entryValues="@array/values_theme"
android:key="theme"
<org.mariotaku.twidere.preference.DarkLightThemeTogglePreference
android:order="21"
android:title="@string/style">
android:key="theme"
android:title="@string/dark_theme">
<extra
android:name="notify_change"
android:value="true" />
</org.mariotaku.twidere.preference.SummaryListPreference>
android:value="true"/>
</org.mariotaku.twidere.preference.DarkLightThemeTogglePreference>
<org.mariotaku.twidere.preference.ThemeBackgroundPreference
android:defaultValue="default"
@ -28,7 +27,7 @@
android:title="@string/background">
<extra
android:name="notify_change"
android:value="true" />
android:value="true"/>
</org.mariotaku.twidere.preference.ThemeBackgroundPreference>
<org.mariotaku.twidere.preference.ColorPickerPreference
@ -38,7 +37,7 @@
app:defaultColor="@color/branding_color">
<extra
android:name="notify_change"
android:value="true" />
android:value="true"/>
</org.mariotaku.twidere.preference.ColorPickerPreference>

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="white" d="M12 6v3l4-4-4-4v3c-4.42 0-8 3.58-8 8 0 1.57.46 3.03 1.24 4.26l1.46-1.46c-.45-.83-.7-1.79-.7-2.8 0-3.31 2.69-6 6-6zm6.76 1.74l-1.46 1.46c.44.84.7 1.79.7 2.8 0 3.31-2.69 6-6 6v-3l-4 4 4 4v-3c4.42 0 8-3.58 8-8 0-1.57-.46-3.03-1.24-4.26z"/>
<path fill="none" d="M0 0h24v24h-24z"/>
</svg>

After

Width:  |  Height:  |  Size: 398 B

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,16 +17,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.extension.streaming;
package org.mariotaku.twidere.util;
import android.app.Application;
import android.test.ApplicationTestCase;
import com.squareup.okhttp.OkHttpClient;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
* Created by mariotaku on 15/5/27.
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
public class DebugModeUtils {
public static void initForHttpClient(final OkHttpClient client) {
// No-op
}
}
public static void initForApplication(final Application application) {
// No-op
}
}