removed closed source components

This commit is contained in:
Mariotaku Lee 2017-03-01 09:27:46 +08:00
parent 983913967c
commit 5d02ff277d
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
39 changed files with 1 additions and 2181 deletions

1
twidere/src/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
google/

View File

@ -1,46 +0,0 @@
<?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">
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<application
android:fullBackupContent="true"
tools:ignore="GoogleAppIndexingWarning">
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<meta-data
android:name="com.google.android.maps.v2.API_KEY"
android:value="AIzaSyCVdCIMFFxdNqHnCPrJ9yKUzoTfs8jhYGc"/>
<activity
android:name=".activity.sync.DropboxAuthStarterActivity"
android:theme="@style/Theme.Twidere.NoDisplay"/>
<activity
android:name=".activity.sync.GoogleDriveAuthActivity"
android:theme="@style/Theme.Twidere.NoDisplay"/>
<activity
android:name="com.dropbox.core.android.AuthActivity"
android:configChanges="orientation|keyboard"
android:launchMode="singleTask">
<intent-filter>
<data android:scheme="db-lflrwypk2e5pjm6"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name=".activity.GooglePlayInAppPurchaseActivity"
android:exported="false"
android:theme="@style/Theme.Twidere.NoDisplay"/>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

View File

@ -1,77 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Google Maps</title>
<meta charset="UTF-8">
<meta name="viewport" user-scalable="no"/>
<style type="text/css">
html, body, #map_canvas {
background-image: url('images/loading_tile.png');
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
var map;
function initialize() {
var latitude = 0;
var longitude = 0;
if (window.android) {
latitude = window.android.getLatitude();
longitude = window.android.getLongitude();
}
setupMap(latitude, longitude, 12);
setCenter(latitude, longitude);
setMark(latitude, longitude);
}
function setupMap(latitude, longitude, default_zoom) {
var options = {
zoom: default_zoom,
center: getLatLng(latitude, longitude),
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map_canvas'), options);
}
function getLatLng(latitude, longitude) {
return new google.maps.LatLng(latitude, longitude);
}
function setCenter(latitude, longitude) {
map.panTo(getLatLng(latitude, longitude));
}
function center() {
var latitude = 0;
var longitude = 0;
if (window.android) {
latitude = window.android.getLatitude();
longitude = window.android.getLongitude();
}
setCenter(latitude, longitude);
}
function setMark(latitude, longitude) {
var latlng = getLatLng(latitude, longitude);
var marker = new google.maps.Marker({
position: latlng,
map: map
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map_canvas"></div>
</body>
</html>

View File

@ -1,145 +0,0 @@
package org.mariotaku.twidere.activity
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.DialogFragment
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.Constants.*
import com.anjlab.android.iab.v3.SkuDetails
import com.anjlab.android.iab.v3.TransactionDetails
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.alwaysUi
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.activity.premium.AbsExtraFeaturePurchaseActivity
import org.mariotaku.twidere.fragment.ProgressDialogFragment
import org.mariotaku.twidere.model.premium.PurchaseResult
import org.mariotaku.twidere.util.premium.GooglePlayExtraFeaturesService
import java.lang.ref.WeakReference
/**
* Created by mariotaku on 2016/12/25.
*/
class GooglePlayInAppPurchaseActivity : AbsExtraFeaturePurchaseActivity(),
BillingProcessor.IBillingHandler {
private lateinit var billingProcessor: BillingProcessor
private val productId: String get() = GooglePlayExtraFeaturesService.getProductId(requestingFeature)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
billingProcessor = BillingProcessor(this, Constants.GOOGLE_PLAY_LICENCING_PUBKEY, this)
if (!isFinishing && !BillingProcessor.isIabServiceAvailable(this)) {
handleError(BILLING_RESPONSE_RESULT_USER_CANCELED)
}
}
override fun onDestroy() {
billingProcessor.release()
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (!billingProcessor.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data)
}
}
// MARK: Payment methods
override fun onBillingError(code: Int, error: Throwable?) {
handleError(code)
}
override fun onBillingInitialized() {
// See https://github.com/anjlab/android-inapp-billing-v3/issues/156
if (intent.action == ACTION_RESTORE_PURCHASE) {
getProductDetailsAndFinish()
} else {
billingProcessor.purchase(this, productId)
}
}
override fun onProductPurchased(productId: String?, details: TransactionDetails?) {
getProductDetailsAndFinish()
}
override fun onPurchaseHistoryRestored() {
getProductDetailsAndFinish()
}
private fun handleError(billingResponse: Int) {
when (billingResponse) {
BILLING_ERROR_OTHER_ERROR, BILLING_ERROR_INVALID_DEVELOPER_PAYLOAD -> {
getProductDetailsAndFinish()
}
else -> {
finishWithError(getResultCode(billingResponse))
}
}
}
private fun handlePurchased(sku: SkuDetails, transaction: TransactionDetails) {
val result = PurchaseResult()
result.feature = requestingFeature
result.price = sku.priceValue
result.currency = sku.currency
finishWithResult(result)
}
private fun getProductDetailsAndFinish() {
executeAfterFragmentResumed {
val weakThis = WeakReference(it as GooglePlayInAppPurchaseActivity)
ProgressDialogFragment.show(it.supportFragmentManager, TAG_PURCHASE_PROCESS)
task {
val activity = weakThis.get() ?: throw PurchaseException(BILLING_RESPONSE_RESULT_USER_CANCELED)
val productId = activity.productId
val bp = activity.billingProcessor
bp.loadOwnedPurchasesFromGoogle()
val skuDetails = bp.getPurchaseListingDetails(productId)
?: throw PurchaseException(BILLING_RESPONSE_RESULT_ERROR)
val transactionDetails = bp.getPurchaseTransactionDetails(productId)
?: throw PurchaseException(BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED)
return@task Pair(skuDetails, transactionDetails)
}.successUi { result ->
weakThis.get()?.handlePurchased(result.first, result.second)
}.failUi { error ->
if (error is PurchaseException) {
weakThis.get()?.handleError(error.code)
} else {
weakThis.get()?.handleError(BILLING_RESPONSE_RESULT_ERROR)
}
}.alwaysUi {
weakThis.get()?.executeAfterFragmentResumed { fragment ->
val fm = fragment.supportFragmentManager
val df = fm?.findFragmentByTag(TAG_PURCHASE_PROCESS) as? DialogFragment
df?.dismiss()
}
}
}
}
private fun getResultCode(billingResponse: Int): Int {
val resultCode = when (billingResponse) {
BILLING_RESPONSE_RESULT_OK -> Activity.RESULT_OK
BILLING_RESPONSE_RESULT_USER_CANCELED -> Activity.RESULT_CANCELED
BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE -> RESULT_SERVICE_UNAVAILABLE
BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED -> RESULT_NOT_PURCHASED
BILLING_RESPONSE_RESULT_ERROR -> RESULT_INTERNAL_ERROR
else -> billingResponse
}
return resultCode
}
class PurchaseException(val code: Int) : Exception()
companion object {
private const val TAG_PURCHASE_PROCESS = "get_purchase_process"
}
}

View File

@ -1,39 +0,0 @@
package org.mariotaku.twidere.activity.sync
import android.os.Bundle
import com.dropbox.core.android.Auth
import org.mariotaku.kpreferences.set
import org.mariotaku.twidere.Constants.DROPBOX_APP_KEY
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo
/**
* Created by mariotaku on 2016/12/7.
*/
class DropboxAuthStarterActivity : BaseActivity() {
private var shouldGetAuthResult: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Auth.startOAuth2Authentication(this, DROPBOX_APP_KEY)
}
override fun onResume() {
super.onResume()
if (shouldGetAuthResult) {
val oauthToken = Auth.getOAuth2Token()
if (oauthToken != null) {
preferences[dataSyncProviderInfoKey] = DropboxSyncProviderInfo(oauthToken)
}
finish()
shouldGetAuthResult = false
}
}
override fun onPause() {
super.onPause()
shouldGetAuthResult = true
}
}

View File

@ -1,105 +0,0 @@
package org.mariotaku.twidere.activity.sync
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.google.android.gms.auth.api.Auth
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.common.api.Scope
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.drive.DriveScopes
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.kpreferences.set
import org.mariotaku.twidere.activity.BaseActivity
import org.mariotaku.twidere.constant.dataSyncProviderInfoKey
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
class GoogleDriveAuthActivity : BaseActivity(), GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private lateinit var googleApiClient: GoogleApiClient
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestScopes(Scope(DriveScopes.DRIVE_APPDATA))
.requestServerAuthCode(GoogleDriveSyncProviderInfo.WEB_CLIENT_ID, true)
.build()
googleApiClient = GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
googleApiClient.connect();
}
override fun onDestroy() {
googleApiClient.disconnect()
super.onDestroy()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_RESOLVE_ERROR -> {
if (!googleApiClient.isConnected && !googleApiClient.isConnecting) {
googleApiClient.connect()
}
}
REQUEST_GOOGLE_SIGN_IN -> {
if (resultCode == Activity.RESULT_OK) {
val result = Auth.GoogleSignInApi.getSignInResultFromIntent(data)
val authCode = result.signInAccount?.serverAuthCode ?: return
val httpTransport = NetHttpTransport()
val jsonFactory = JacksonFactory.getDefaultInstance()
val tokenRequest = GoogleAuthorizationCodeTokenRequest(httpTransport, jsonFactory,
"https://www.googleapis.com/oauth2/v4/token", GoogleDriveSyncProviderInfo.WEB_CLIENT_ID,
GoogleDriveSyncProviderInfo.WEB_CLIENT_SECRET, authCode, "")
task {
tokenRequest.execute()
}.successUi { response ->
preferences[dataSyncProviderInfoKey] = GoogleDriveSyncProviderInfo(response.refreshToken)
setResult(Activity.RESULT_OK)
finish()
}.fail { ex ->
ex.printStackTrace()
}
}
}
}
}
override fun onConnected(connectionHint: Bundle?) {
Auth.GoogleSignInApi.signOut(googleApiClient).setResultCallback {
// Start sign in
val signInIntent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient)
startActivityForResult(signInIntent, REQUEST_GOOGLE_SIGN_IN)
}
}
override fun onConnectionSuspended(cause: Int) {
}
override fun onConnectionFailed(connectionResult: ConnectionResult) {
if (connectionResult.hasResolution()) {
connectionResult.startResolutionForResult(this, REQUEST_RESOLVE_ERROR)
} else {
}
}
companion object {
private const val REQUEST_RESOLVE_ERROR: Int = 101
private const val REQUEST_GOOGLE_SIGN_IN: Int = 102
}
}

View File

@ -1,119 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.fragment
import android.graphics.Rect
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.R
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_LATITUDE
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_LONGITUDE
import org.mariotaku.twidere.fragment.iface.IBaseFragment
import org.mariotaku.twidere.fragment.iface.IMapFragment
class GoogleMapFragment : SupportMapFragment(), Constants, IMapFragment, IBaseFragment<GoogleMapFragment> {
private val actionHelper = IBaseFragment.ActionHelper(this)
private var activeMap: GoogleMap? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
val args = arguments
if (args == null || !args.containsKey(EXTRA_LATITUDE) || !args.containsKey(EXTRA_LONGITUDE))
return
val lat = args.getDouble(EXTRA_LATITUDE, 0.0)
val lng = args.getDouble(EXTRA_LONGITUDE, 0.0)
getMapAsync { googleMap ->
val marker = MarkerOptions()
marker.position(LatLng(lat, lng))
googleMap.addMarker(marker)
center(false)
activeMap = googleMap
}
}
override fun onPause() {
actionHelper.dispatchOnPause()
super.onPause()
}
override fun onResume() {
super.onResume()
actionHelper.dispatchOnResumeFragments()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_google_maps_viewer, menu)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requestFitSystemWindows()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.center -> {
center()
}
}
return true
}
override fun center() {
center(true)
}
fun center(animate: Boolean) {
val googleMap = activeMap ?: return
val args = arguments ?: return
if (!args.containsKey(EXTRA_LATITUDE) || !args.containsKey(EXTRA_LONGITUDE))
return
val lat = args.getDouble(EXTRA_LATITUDE, 0.0)
val lng = args.getDouble(EXTRA_LONGITUDE, 0.0)
val c = CameraUpdateFactory.newLatLngZoom(LatLng(lat, lng), 12f)
if (animate) {
googleMap.animateCamera(c)
} else {
googleMap.moveCamera(c)
}
}
override fun executeAfterFragmentResumed(useHandler: Boolean, action: (GoogleMapFragment) -> Unit) {
actionHelper.executeAfterFragmentResumed(useHandler, action)
}
override fun fitSystemWindows(insets: Rect) {
val view = view
view?.setPadding(insets.left, insets.top, insets.right, insets.bottom)
}
}

View File

@ -1,133 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 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.fragment
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.WebView
import org.mariotaku.twidere.Constants.EXTRA_LATITUDE
import org.mariotaku.twidere.Constants.EXTRA_LONGITUDE
import org.mariotaku.twidere.R
import org.mariotaku.twidere.fragment.iface.IMapFragment
import org.mariotaku.twidere.util.webkit.DefaultWebViewClient
class WebMapFragment : BaseWebViewFragment(), IMapFragment {
private var latitude: Double = 0.toDouble()
private var longitude: Double = 0.toDouble()
override fun center() {
webView?.loadUrl("javascript:center();")
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item!!.itemId) {
R.id.center -> {
center()
}
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater!!.inflate(R.menu.menu_google_maps_viewer, menu)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setHasOptionsMenu(true)
getLocation()
setupWebView()
}
/**
* The Location Manager manages location providers. This code searches for
* the best provider of data (GPS, WiFi/cell phone tower lookup, some other
* mechanism) and finds the last known location.
*/
private fun getLocation() {
val bundle = arguments
if (bundle != null) {
latitude = bundle.getDouble(EXTRA_LATITUDE, 0.0)
longitude = bundle.getDouble(EXTRA_LONGITUDE, 0.0)
}
}
/**
* Sets up the WebView object and loads the URL of the page *
*/
private fun setupWebView() {
val webView = webView!!
webView.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
webView.setWebViewClient(MapWebViewClient(activity))
webView.loadUrl(MAPVIEW_URI)
val settings = webView.settings
settings.builtInZoomControls = false
/** Allows JavaScript calls to access application resources */
webView.addJavascriptInterface(MapJavaScriptInterface(this), "android")
}
/**
* Sets up the interface for getting access to Latitude and Longitude data
* from device
*/
internal class MapJavaScriptInterface(val fragment: WebMapFragment) {
@JavascriptInterface
fun getLatitude(): Double {
return fragment.latitude
}
@JavascriptInterface
fun getLongitude(): Double {
return fragment.longitude
}
}
internal inner class MapWebViewClient(activity: Activity) : DefaultWebViewClient(activity) {
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
val uri = Uri.parse(url)
if (uri.scheme == Uri.parse(MAPVIEW_URI).scheme) return false
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
startActivity(intent)
return true
}
}
companion object {
private val MAPVIEW_URI = "file:///android_asset/mapview.html"
}
}

View File

@ -1,31 +0,0 @@
package org.mariotaku.twidere.model.sync
import android.content.Context
import android.content.SharedPreferences
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.dropbox.DropboxSyncTaskRunner
/**
* Created by mariotaku on 2017/1/2.
*/
class DropboxSyncProviderInfo(val authToken: String) : SyncProviderInfo(DropboxSyncProviderInfo.TYPE) {
override fun writeToPreferences(editor: SharedPreferences.Editor) {
editor.putString(KEY_DROPBOX_AUTH_TOKEN, authToken)
}
override fun newSyncTaskRunner(context: Context): SyncTaskRunner {
return DropboxSyncTaskRunner(context, authToken)
}
companion object {
const val TYPE = "dropbox"
private const val KEY_DROPBOX_AUTH_TOKEN = "dropbox_auth_token"
fun newInstance(preferences: SharedPreferences): DropboxSyncProviderInfo? {
val authToken = preferences.getString(KEY_DROPBOX_AUTH_TOKEN, null) ?: return null
return DropboxSyncProviderInfo(authToken)
}
}
}

View File

@ -1,29 +0,0 @@
package org.mariotaku.twidere.model.sync
import android.content.Context
import android.content.SharedPreferences
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.google.GoogleDriveSyncTaskRunner
class GoogleDriveSyncProviderInfo(val refreshToken: String) : SyncProviderInfo(GoogleDriveSyncProviderInfo.TYPE) {
override fun writeToPreferences(editor: SharedPreferences.Editor) {
editor.putString(KEY_GOOGLE_DRIVE_REFRESH_TOKEN, refreshToken)
}
override fun newSyncTaskRunner(context: Context): SyncTaskRunner {
return GoogleDriveSyncTaskRunner(context, refreshToken)
}
companion object {
const val TYPE = "google_drive"
private const val KEY_GOOGLE_DRIVE_REFRESH_TOKEN = "google_drive_refresh_token"
const val WEB_CLIENT_ID = "223623398518-0sc2i5fsqliidcdoogn53iqltpktfnff.apps.googleusercontent.com"
const val WEB_CLIENT_SECRET = "BsZ0a06UgJf5hJOTI3fcxI2u"
fun newInstance(preferences: SharedPreferences): GoogleDriveSyncProviderInfo? {
val accessToken = preferences.getString(KEY_GOOGLE_DRIVE_REFRESH_TOKEN, null) ?: return null
return GoogleDriveSyncProviderInfo(accessToken)
}
}
}

View File

@ -1,151 +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.accounts.Account
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.app.Application
import android.os.Build
import com.crashlytics.android.Crashlytics
import com.crashlytics.android.answers.*
import io.fabric.sdk.android.Fabric
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.configure
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.TwidereConstants.ACCOUNT_TYPE
import org.mariotaku.twidere.model.analyzer.*
import java.math.BigDecimal
import java.util.*
/**
* Created by mariotaku on 15/7/8.
*/
class FabricAnalyzer : Analyzer(), Constants {
override fun log(priority: Int, tag: String, msg: String) {
Crashlytics.log(priority, tag, msg)
}
override fun logException(throwable: Throwable) {
Crashlytics.logException(throwable)
}
override fun log(event: Event) {
val answers = Answers.getInstance()
when (event) {
is SignIn -> {
answers.logLogin(configure(LoginEvent()) {
putMethod(event.accountType)
putSuccess(event.success)
if (event.errorReason != null) {
putCustomAttribute("Error reason", event.errorReason)
}
if (event.accountHost != null) {
putCustomAttribute("Account host", event.accountHost)
}
if (event.credentialsType != null) {
putCustomAttribute("Credentials type", event.credentialsType)
}
putCustomAttribute("Official key", event.officialKey.toString())
})
}
is Search -> {
answers.logSearch(configure(SearchEvent()) {
putQuery(event.query)
putAttributes(event)
})
}
is Share -> {
answers.logShare(configure(ShareEvent()) {
putContentType(event.type)
putContentId(event.id)
putAttributes(event)
})
}
is PurchaseConfirm -> {
answers.logStartCheckout(configure(StartCheckoutEvent()) {
event.forEachValues { name, value ->
putCustomAttribute(name, value)
}
})
}
is PurchaseIntroduction -> {
answers.logAddToCart(configure(AddToCartEvent()) {
event.forEachValues { name, value ->
putCustomAttribute(name, value)
}
})
}
is PurchaseFinished -> {
answers.logPurchase(configure(PurchaseEvent()) {
putItemName(event.productName)
if (!event.price.isNaN() && event.currency != null) {
putCurrency(Currency.getInstance(event.currency) ?: Currency.getInstance(Locale.getDefault()))
putItemPrice(BigDecimal(event.price))
}
event.forEachValues { name, value ->
putCustomAttribute(name, value)
}
putAttributes(event)
})
}
else -> {
answers.logCustom(configure(CustomEvent(event.name)) {
putAttributes(event)
event.forEachValues { name, value ->
putCustomAttribute(name, value)
}
})
}
}
}
override fun init(application: Application) {
Fabric.with(application, Crashlytics())
Crashlytics.setBool("debug", BuildConfig.DEBUG)
Crashlytics.setString("build.brand", Build.BRAND)
Crashlytics.setString("build.device", Build.DEVICE)
Crashlytics.setString("build.display", Build.DISPLAY)
Crashlytics.setString("build.hardware", Build.HARDWARE)
Crashlytics.setString("build.manufacturer", Build.MANUFACTURER)
Crashlytics.setString("build.model", Build.MODEL)
Crashlytics.setString("build.product", Build.PRODUCT)
val am = AccountManager.get(application)
try {
am.addOnAccountsUpdatedListenerSafe(OnAccountsUpdateListener { accounts ->
Crashlytics.setString("twidere.accounts", accounts.filter { it.type == ACCOUNT_TYPE }
.joinToString(transform = Account::name))
}, updateImmediately = true)
} catch (e: SecurityException) {
// Permission managers (like some Xposed plugins) may block Twidere from getting accounts
}
}
private fun AnswersEvent<*>.putAttributes(event: Analyzer.Event) {
if (event.accountType != null) {
putCustomAttribute("Account type", event.accountType)
}
if (event.accountHost != null) {
putCustomAttribute("Account host", event.accountHost)
}
}
}

View File

@ -1,42 +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.support.v4.app.Fragment
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import org.mariotaku.twidere.fragment.GoogleMapFragment
import org.mariotaku.twidere.fragment.WebMapFragment
/**
* Created by mariotaku on 15/4/27.
*/
class GoogleMapFragmentFactory : MapFragmentFactory() {
override fun createMapFragment(context: Context): Fragment {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
return GoogleMapFragment()
}
return WebMapFragment()
}
}

View File

@ -1,94 +0,0 @@
package org.mariotaku.twidere.util.premium
import android.content.Context
import android.content.Intent
import com.anjlab.android.iab.v3.BillingProcessor
import nl.komponents.kovenant.task
import org.mariotaku.twidere.Constants.GOOGLE_PLAY_LICENCING_PUBKEY
import org.mariotaku.twidere.activity.GooglePlayInAppPurchaseActivity
import org.mariotaku.twidere.activity.premium.AbsExtraFeaturePurchaseActivity
import org.mariotaku.twidere.view.controller.premium.GoogleFiltersImportViewController
import org.mariotaku.twidere.view.controller.premium.GoogleFiltersSubscriptionsViewController
import org.mariotaku.twidere.view.controller.premium.SyncStatusViewController
/**
* Created by mariotaku on 2016/12/25.
*/
class GooglePlayExtraFeaturesService : ExtraFeaturesService() {
private lateinit var bp: BillingProcessor
override fun getDashboardControllers() = listOf(
SyncStatusViewController::class.java,
GoogleFiltersImportViewController::class.java,
GoogleFiltersSubscriptionsViewController::class.java
)
override fun init(context: Context) {
super.init(context)
bp = BillingProcessor(context, GOOGLE_PLAY_LICENCING_PUBKEY, null)
}
override fun appStarted() {
task {
bp.loadOwnedPurchasesFromGoogle()
}
}
override fun release() {
bp.release()
}
override fun isSupported(): Boolean = BillingProcessor.isIabServiceAvailable(context)
override fun isEnabled(feature: String): Boolean {
if (bp.hasValidTransaction(PRODUCT_ID_EXTRA_FEATURES_PACK)) return true
val productId = getProductId(feature)
return bp.hasValidTransaction(productId)
}
override fun destroyPurchase(): Boolean {
bp.consumePurchase(PRODUCT_ID_EXTRA_FEATURES_PACK)
bp.consumePurchase(PRODUCT_ID_DATA_SYNC)
bp.consumePurchase(PRODUCT_ID_FILTERS_IMPORT)
bp.consumePurchase(PRODUCT_ID_FILTERS_SUBSCRIPTION)
bp.consumePurchase(PRODUCT_ID_SCHEDULE_STATUS)
return true
}
override fun createPurchaseIntent(context: Context, feature: String): Intent? {
return AbsExtraFeaturePurchaseActivity.purchaseIntent(context,
GooglePlayInAppPurchaseActivity::class.java, feature)
}
override fun createRestorePurchaseIntent(context: Context, feature: String): Intent? {
return AbsExtraFeaturePurchaseActivity.restorePurchaseIntent(context,
GooglePlayInAppPurchaseActivity::class.java, feature)
}
private fun BillingProcessor.hasValidTransaction(productId: String): Boolean {
val details = getPurchaseTransactionDetails(productId) ?: return false
return isValidTransactionDetails(details)
}
companion object {
private const val PRODUCT_ID_EXTRA_FEATURES_PACK = "twidere.extra.features"
private const val PRODUCT_ID_DATA_SYNC = "twidere.extra.feature.data_sync"
private const val PRODUCT_ID_FILTERS_IMPORT = "twidere.extra.feature.filter_import"
private const val PRODUCT_ID_FILTERS_SUBSCRIPTION = "twidere.extra.feature.filter_subscription"
private const val PRODUCT_ID_SCHEDULE_STATUS = "twidere.extra.feature.schedule_status"
@JvmStatic
fun getProductId(feature: String): String {
return when (feature) {
FEATURE_FEATURES_PACK -> PRODUCT_ID_EXTRA_FEATURES_PACK
FEATURE_SYNC_DATA -> PRODUCT_ID_DATA_SYNC
FEATURE_FILTERS_IMPORT -> PRODUCT_ID_FILTERS_IMPORT
FEATURE_FILTERS_SUBSCRIPTION -> PRODUCT_ID_FILTERS_SUBSCRIPTION
FEATURE_SCHEDULE_STATUS -> PRODUCT_ID_SCHEDULE_STATUS
else -> throw UnsupportedOperationException(feature)
}
}
}
}

View File

@ -1,38 +0,0 @@
package org.mariotaku.twidere.util.sync
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import org.mariotaku.twidere.R
import org.mariotaku.twidere.activity.sync.DropboxAuthStarterActivity
import org.mariotaku.twidere.activity.sync.GoogleDriveAuthActivity
import org.mariotaku.twidere.model.sync.DropboxSyncProviderInfo
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
import org.mariotaku.twidere.model.sync.SyncProviderEntry
import org.mariotaku.twidere.model.sync.SyncProviderInfo
import java.util.*
/**
* Created by mariotaku on 2017/1/2.
*/
class NonFreeSyncProviderInfoFactory : SyncProviderInfoFactory() {
override fun getInfoForType(type: String, preferences: SharedPreferences): SyncProviderInfo? {
return when (type) {
DropboxSyncProviderInfo.TYPE -> DropboxSyncProviderInfo.newInstance(preferences)
GoogleDriveSyncProviderInfo.TYPE -> GoogleDriveSyncProviderInfo.newInstance(preferences)
else -> null
}
}
override fun getSupportedProviders(context: Context): List<SyncProviderEntry> {
val list = ArrayList<SyncProviderEntry>()
list.add(SyncProviderEntry(DropboxSyncProviderInfo.TYPE,
context.getString(R.string.sync_provider_name_dropbox),
Intent(context, DropboxAuthStarterActivity::class.java)))
list.add(SyncProviderEntry(GoogleDriveSyncProviderInfo.TYPE,
context.getString(R.string.sync_provider_name_google_drive),
Intent(context, GoogleDriveAuthActivity::class.java)))
return list
}
}

View File

@ -1,94 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox
import android.content.Context
import com.dropbox.core.DbxException
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.DeleteArg
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.ListFolderErrorException
import com.dropbox.core.v2.files.ListFolderResult
import org.mariotaku.twidere.extension.model.filename
import org.mariotaku.twidere.extension.model.readMimeMessageFrom
import org.mariotaku.twidere.extension.model.writeMimeMessageTo
import org.mariotaku.twidere.model.Draft
import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import java.io.IOException
import java.util.*
internal class DropboxDraftsSyncAction(
context: Context,
val client: DbxClientV2
) : FileBasedDraftsSyncAction<FileMetadata>(context) {
@Throws(IOException::class)
override fun Draft.saveToRemote(): FileMetadata {
try {
client.newUploader("/Drafts/$filename", this.timestamp).use {
this.writeMimeMessageTo(context, it.outputStream)
return it.finish()
}
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun Draft.loadFromRemote(info: FileMetadata): Boolean {
try {
client.files().download(info.pathLower).use {
val parsed = this.readMimeMessageFrom(context, it.inputStream)
if (parsed) {
this.timestamp = info.draftTimestamp
this.unique_id = info.draftFileName.substringBeforeLast(".eml")
}
return parsed
}
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDrafts(list: List<FileMetadata>): Boolean {
try {
return client.files().deleteBatch(list.map { DeleteArg(it.pathLower) }) != null
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
override fun removeDraft(info: FileMetadata): Boolean {
try {
return client.files().delete(info.pathLower) != null
} catch (e: DbxException) {
throw IOException(e)
}
}
override val FileMetadata.draftTimestamp: Long get() = this.clientModified.time
override val FileMetadata.draftFileName: String get() = this.name
@Throws(IOException::class)
override fun listRemoteDrafts(): List<FileMetadata> {
val result = ArrayList<FileMetadata>()
try {
var listResult: ListFolderResult = client.files().listFolder("/Drafts/")
while (true) {
// Do something with files
listResult.entries.mapNotNullTo(result) { it as? FileMetadata }
if (!listResult.hasMore) break
listResult = client.files().listFolderContinue(listResult.cursor)
}
} catch (e: DbxException) {
if (e is ListFolderErrorException) {
if (e.errorValue?.pathValue?.isNotFound ?: false) {
return emptyList()
}
}
throw IOException(e)
}
return result
}
}

View File

@ -1,53 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox
import android.content.Context
import com.dropbox.core.DbxDownloader
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.UploadUploader
import org.mariotaku.twidere.extension.model.initFields
import org.mariotaku.twidere.extension.model.parse
import org.mariotaku.twidere.extension.model.serialize
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.util.sync.FileBasedFiltersDataSyncAction
internal class DropboxFiltersDataSyncAction(
context: Context,
val client: DbxClientV2
) : FileBasedFiltersDataSyncAction<DbxDownloader<FileMetadata>, DropboxUploadSession<FiltersData>>(context) {
override fun DbxDownloader<FileMetadata>.getRemoteLastModified(): Long {
return result.clientModified.time
}
private val filePath = "/Common/filters.xml"
override fun newLoadFromRemoteSession(): DbxDownloader<FileMetadata> {
return client.newDownloader(filePath)
}
override fun DbxDownloader<FileMetadata>.loadFromRemote(): FiltersData {
val data = FiltersData()
data.parse(inputStream.newPullParser(charset = Charsets.UTF_8))
data.initFields()
return data
}
override fun DropboxUploadSession<FiltersData>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
override fun DropboxUploadSession<FiltersData>.saveToRemote(data: FiltersData): Boolean {
return this.uploadData(data)
}
override fun newSaveToRemoteSession(): DropboxUploadSession<FiltersData> {
return object : DropboxUploadSession<FiltersData>(filePath, client) {
override fun performUpload(uploader: UploadUploader, data: FiltersData) {
data.serialize(uploader.outputStream.newSerializer(charset = Charsets.UTF_8, indent = true))
}
}
}
}

View File

@ -1,52 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox
import android.content.Context
import android.content.SharedPreferences
import com.dropbox.core.DbxDownloader
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.UploadUploader
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.util.sync.FileBasedPreferencesValuesSyncAction
import java.util.*
internal class DropboxPreferencesValuesSyncAction(
context: Context,
val client: DbxClientV2,
preferences: SharedPreferences,
processor: Processor,
val filePath: String
) : FileBasedPreferencesValuesSyncAction<DbxDownloader<FileMetadata>,
DropboxUploadSession<Map<String, String>>>(context, preferences, processor) {
override fun DbxDownloader<FileMetadata>.getRemoteLastModified(): Long {
return result.clientModified.time
}
override fun DbxDownloader<FileMetadata>.loadFromRemote(): MutableMap<String, String> {
val data = HashMap<String, String>()
data.parse(inputStream.newPullParser())
return data
}
override fun newLoadFromRemoteSession(): DbxDownloader<FileMetadata> {
return client.newDownloader(filePath)
}
override fun newSaveToRemoteSession(): DropboxUploadSession<Map<String, String>> {
return object : DropboxUploadSession<Map<String, String>>(filePath, client) {
override fun performUpload(uploader: UploadUploader, data: Map<String, String>) {
data.serialize(uploader.outputStream.newSerializer(charset = Charsets.UTF_8,
indent = true))
}
}
}
override fun DropboxUploadSession<Map<String, String>>.saveToRemote(data: MutableMap<String, String>): Boolean {
return this.uploadData(data)
}
override fun DropboxUploadSession<Map<String, String>>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
}

View File

@ -1,40 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox
import com.dropbox.core.DbxDownloader
import com.dropbox.core.DbxException
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.DownloadErrorException
import com.dropbox.core.v2.files.FileMetadata
import com.dropbox.core.v2.files.UploadUploader
import com.dropbox.core.v2.files.WriteMode
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
/**
* Created by mariotaku on 2017/1/10.
*/
@Throws(IOException::class)
internal fun DbxClientV2.newUploader(path: String, clientModified: Long): UploadUploader {
try {
return files().uploadBuilder(path).withMode(WriteMode.OVERWRITE).withMute(true)
.withClientModified(Date(clientModified)).start()
} catch (e: DbxException) {
throw IOException(e)
}
}
@Throws(IOException::class)
internal fun DbxClientV2.newDownloader(path: String): DbxDownloader<FileMetadata> {
try {
return files().downloadBuilder(path).start()
} catch (e: DownloadErrorException) {
if (e.errorValue?.pathValue?.isNotFound ?: false) {
throw FileNotFoundException(path)
}
throw IOException(e)
} catch (e: DbxException) {
throw IOException(e)
}
}

View File

@ -1,47 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox;
import android.content.Context
import com.dropbox.core.DbxRequestConfig
import com.dropbox.core.v2.DbxClientV2
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.sync.ISyncAction
import org.mariotaku.twidere.util.sync.SyncTaskRunner
import org.mariotaku.twidere.util.sync.UserColorsSyncProcessor
import org.mariotaku.twidere.util.sync.UserNicknamesSyncProcessor
/**
* Created by mariotaku on 2017/1/6.
*/
class DropboxSyncTaskRunner(context: Context, val authToken: String) : SyncTaskRunner(context) {
override fun onRunningTask(action: String, callback: (Boolean) -> Unit): Boolean {
val requestConfig = DbxRequestConfig.newBuilder("twidere-android/${BuildConfig.VERSION_NAME}")
.build()
val client = DbxClientV2(requestConfig, authToken)
val syncAction: ISyncAction = when (action) {
TaskServiceRunner.ACTION_SYNC_DRAFTS -> DropboxDraftsSyncAction(context, client)
TaskServiceRunner.ACTION_SYNC_FILTERS -> DropboxFiltersDataSyncAction(context, client)
TaskServiceRunner.ACTION_SYNC_USER_COLORS -> DropboxPreferencesValuesSyncAction(context,
client, userColorNameManager.colorPreferences, UserColorsSyncProcessor,
"/Common/user_colors.xml")
TaskServiceRunner.ACTION_SYNC_USER_NICKNAMES -> DropboxPreferencesValuesSyncAction(context,
client, userColorNameManager.nicknamePreferences, UserNicknamesSyncProcessor,
"/Common/user_nicknames.xml")
else -> null
} ?: return false
task {
syncAction.execute()
}.successUi {
callback(true)
}.failUi {
callback(false)
}
return true
}
}

View File

@ -1,28 +0,0 @@
package org.mariotaku.twidere.util.sync.dropbox
import com.dropbox.core.v2.DbxClientV2
import com.dropbox.core.v2.files.UploadUploader
import java.io.Closeable
import java.io.IOException
abstract internal class DropboxUploadSession<in Data>(val fileName: String, val client: DbxClientV2) : Closeable {
private var uploader: UploadUploader? = null
var localModifiedTime: Long = 0
override fun close() {
uploader?.close()
}
@Throws(IOException::class)
abstract fun performUpload(uploader: UploadUploader, data: Data)
fun uploadData(data: Data): Boolean {
uploader = client.newUploader(fileName, localModifiedTime).apply {
performUpload(this, data)
this.finish()
}
return true
}
}

View File

@ -1,15 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import java.io.Closeable
/**
* Created by mariotaku on 1/21/17.
*/
internal class CloseableAny<T>(val obj: T) : Closeable {
override fun close() {
if (obj is Closeable) {
obj.close()
}
}
}

View File

@ -1,5 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import java.util.*
internal data class DriveFileInfo(val fileId: String, val name: String, val modifiedDate: Date)

View File

@ -1,112 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import com.google.api.client.util.DateTime
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.model.filename
import org.mariotaku.twidere.extension.model.readMimeMessageFrom
import org.mariotaku.twidere.extension.model.writeMimeMessageTo
import org.mariotaku.twidere.model.Draft
import org.mariotaku.twidere.util.sync.FileBasedDraftsSyncAction
import org.mariotaku.twidere.util.tempFileInputStream
import java.io.IOException
import java.util.*
internal class GoogleDriveDraftsSyncAction(
context: Context,
val drive: Drive
) : FileBasedDraftsSyncAction<DriveFileInfo>(context) {
val draftsFolderName = "Drafts"
val draftMimeType = "message/rfc822"
private lateinit var folderId: String
private val files = drive.files()
@Throws(IOException::class)
override fun Draft.saveToRemote(): DriveFileInfo {
tempFileInputStream(context) { os ->
this.writeMimeMessageTo(context, os)
}.use {
val driveId = this.remote_extras
val fileConfig: (File) -> Unit = {
it.modifiedTime = DateTime(timestamp)
}
val file = if (driveId != null) {
drive.files().performUpdate(driveId, filename, draftMimeType, stream = it, fileConfig = fileConfig)
} else {
drive.updateOrCreate(name = filename, mimeType = draftMimeType, parent = folderId,
spaces = appDataFolderSpace, stream = it, fileConfig = fileConfig)
}
return DriveFileInfo(file.id, file.name, Date(file.modifiedTime.value))
}
}
@Throws(IOException::class)
override fun Draft.loadFromRemote(info: DriveFileInfo): Boolean {
val get = files.get(info.fileId)
get.executeMediaAsInputStream().use {
val parsed = this.readMimeMessageFrom(context, it)
if (parsed) {
this.timestamp = info.draftTimestamp
this.unique_id = info.draftFileName.substringBeforeLast(".eml")
this.remote_extras = info.fileId
}
return parsed
}
}
@Throws(IOException::class)
override fun removeDrafts(list: List<DriveFileInfo>): Boolean {
val batch = drive.batch()
val callback = SimpleJsonBatchCallback<Void>()
list.forEach { info ->
files.delete(info.fileId).queue(batch, callback)
}
batch.execute()
return true
}
@Throws(IOException::class)
override fun removeDraft(info: DriveFileInfo): Boolean {
files.delete(info.fileId).execute()
return true
}
override val DriveFileInfo.draftTimestamp: Long get() = this.modifiedDate.time
override val DriveFileInfo.draftFileName: String get() = this.name
override val DriveFileInfo.draftRemoteExtras: String? get() = this.fileId
@Throws(IOException::class)
override fun listRemoteDrafts(): List<DriveFileInfo> {
val result = ArrayList<DriveFileInfo>()
var nextPageToken: String? = null
do {
val listResult = files.basicList(appDataFolderSpace).apply {
this.q = "'$folderId' in parents and mimeType = '$draftMimeType' and trashed = false"
if (nextPageToken != null) {
this.pageToken = nextPageToken
}
}.execute()
listResult.files.filter { file ->
file.mimeType == draftMimeType
}.mapTo(result) { file ->
DriveFileInfo(file.id, file.name, Date(file.modifiedTime.value))
}
nextPageToken = listResult.nextPageToken
} while (nextPageToken != null)
return result
}
override fun setup(): Boolean {
folderId = drive.getFileOrCreate(name = draftsFolderName, mimeType = folderMimeType,
parent = appDataFolderName, spaces = appDataFolderSpace,
conflictResolver = ::resolveFoldersConflict).id
return true
}
}

View File

@ -1,74 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.model.initFields
import org.mariotaku.twidere.extension.model.parse
import org.mariotaku.twidere.extension.model.serialize
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.model.FiltersData
import org.mariotaku.twidere.util.sync.FileBasedFiltersDataSyncAction
import org.mariotaku.twidere.util.tempFileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
internal class GoogleDriveFiltersDataSyncAction(
context: Context,
val drive: Drive
) : FileBasedFiltersDataSyncAction<CloseableAny<File>, GoogleDriveUploadSession<FiltersData>>(context) {
private val fileName = "filters.xml"
private lateinit var commonFolderId: String
private val files = drive.files()
override fun newLoadFromRemoteSession(): CloseableAny<File> {
val file = drive.getFileOrNull(name = fileName, mimeType = xmlMimeType,
parent = commonFolderId, spaces = appDataFolderSpace,
conflictResolver = ::resolveFilesConflict) ?: run {
throw FileNotFoundException()
}
return CloseableAny(file)
}
override fun CloseableAny<File>.getRemoteLastModified(): Long {
return obj.modifiedTime?.value ?: throw IOException("Modified time should not be null")
}
override fun CloseableAny<File>.loadFromRemote(): FiltersData {
val data = FiltersData()
data.parse(files.get(obj.id).executeMediaAsInputStream().newPullParser(charset = Charsets.UTF_8))
data.initFields()
return data
}
override fun GoogleDriveUploadSession<FiltersData>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
override fun GoogleDriveUploadSession<FiltersData>.saveToRemote(data: FiltersData): Boolean {
return this.uploadData(data)
}
override fun newSaveToRemoteSession(): GoogleDriveUploadSession<FiltersData> {
return object : GoogleDriveUploadSession<FiltersData>(fileName, commonFolderId, xmlMimeType, drive) {
override fun FiltersData.toInputStream(): InputStream {
return tempFileInputStream(context) {
this.serialize(it.newSerializer(charset = Charsets.UTF_8, indent = true))
}
}
}
}
override fun setup(): Boolean {
commonFolderId = drive.getFileOrCreate(name = commonFolderName, mimeType = folderMimeType,
parent = appDataFolderName, spaces = appDataFolderSpace,
conflictResolver = ::resolveFoldersConflict).id
return true
}
}

View File

@ -1,73 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import android.content.SharedPreferences
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import org.mariotaku.twidere.extension.newPullParser
import org.mariotaku.twidere.extension.newSerializer
import org.mariotaku.twidere.util.sync.FileBasedPreferencesValuesSyncAction
import org.mariotaku.twidere.util.tempFileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.util.*
internal class GoogleDrivePreferencesValuesSyncAction(
context: Context,
val drive: Drive,
preferences: SharedPreferences,
processor: Processor,
val fileName: String
) : FileBasedPreferencesValuesSyncAction<CloseableAny<File>,
GoogleDriveUploadSession<Map<String, String>>>(context, preferences, processor) {
private lateinit var commonFolderId: String
private val files = drive.files()
override fun newLoadFromRemoteSession(): CloseableAny<File> {
val file = drive.getFileOrNull(name = fileName, mimeType = xmlMimeType,
parent = commonFolderId, spaces = appDataFolderSpace,
conflictResolver = ::resolveFilesConflict) ?: run {
throw FileNotFoundException()
}
return CloseableAny(file)
}
override fun CloseableAny<File>.getRemoteLastModified(): Long {
return obj.modifiedTime?.value ?: throw IOException("Modified time should not be null")
}
override fun CloseableAny<File>.loadFromRemote(): MutableMap<String, String> {
val data = HashMap<String, String>()
data.parse(files.get(obj.id).executeMediaAsInputStream().newPullParser())
return data
}
override fun newSaveToRemoteSession(): GoogleDriveUploadSession<Map<String, String>> {
return object : GoogleDriveUploadSession<Map<String, String>>(fileName, commonFolderId, xmlMimeType, drive) {
override fun Map<String, String>.toInputStream(): InputStream {
return tempFileInputStream(context) {
this.serialize(it.newSerializer(charset = Charsets.UTF_8, indent = true))
}
}
}
}
override fun GoogleDriveUploadSession<Map<String, String>>.saveToRemote(data: MutableMap<String, String>): Boolean {
return this.uploadData(data)
}
override fun GoogleDriveUploadSession<Map<String, String>>.setRemoteLastModified(lastModified: Long) {
this.localModifiedTime = lastModified
}
override fun setup(): Boolean {
commonFolderId = drive.getFileOrCreate(name = commonFolderName, mimeType = folderMimeType,
parent = appDataFolderName, spaces = appDataFolderSpace,
conflictResolver = ::resolveFoldersConflict).id
return true
}
}

View File

@ -1,260 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import com.google.api.client.googleapis.json.GoogleJsonError
import com.google.api.client.googleapis.json.GoogleJsonResponseException
import com.google.api.client.http.HttpHeaders
import com.google.api.client.http.InputStreamContent
import com.google.api.services.drive.Drive
import com.google.api.services.drive.model.File
import com.google.common.collect.HashMultimap
import java.io.IOException
import java.io.InputStream
import java.util.*
/**
* Created by mariotaku on 1/21/17.
*/
internal const val folderMimeType = "application/vnd.google-apps.folder"
internal const val xmlMimeType = "application/xml"
internal const val requiredRequestFields = "id, name, parents, mimeType, modifiedTime"
internal const val requiredFilesRequestFields = "files($requiredRequestFields)"
internal const val commonFolderName = "Common"
internal const val appDataFolderName = "appDataFolder"
internal const val rootFolderName = "root"
internal const val appDataFolderSpace = appDataFolderName
internal fun Drive.getFileOrNull(
name: String,
mimeType: String?,
parent: String? = rootFolderName,
spaces: String? = null,
trashed: Boolean = false,
conflictResolver: ((Drive, List<File>, String?) -> File)? = null
): File? {
val result = findFilesOrNull(name, mimeType, parent, spaces, trashed) ?: return null
if (result.size > 1 && conflictResolver != null) {
return conflictResolver(this, result, spaces)
}
return result.firstOrNull()
}
internal fun Drive.findFilesOrNull(
name: String,
mimeType: String?,
parent: String? = rootFolderName,
spaces: String? = null,
trashed: Boolean = false
): List<File>? {
val find = files().basicList(spaces)
var query = "name = '$name'"
if (parent != null) {
query += " and '$parent' in parents"
}
if (mimeType != null) {
query += " and mimeType = '$mimeType'"
}
query += " and trashed = $trashed"
find.q = query
try {
val files = find.execute().files
if (files.isEmpty()) return null
return files
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 404) {
return null
} else {
throw e
}
}
}
internal fun Drive.getFileOrCreate(
name: String,
mimeType: String,
parent: String = rootFolderName,
spaces: String? = null,
trashed: Boolean = false,
conflictResolver: ((Drive, List<File>, String?) -> File)? = null
): File {
val result = findFilesOrCreate(name, mimeType, parent, spaces, trashed)
if (result.size > 1 && conflictResolver != null) {
return conflictResolver(this, result, spaces)
}
return result.first()
}
internal fun Drive.findFilesOrCreate(
name: String,
mimeType: String,
parent: String = rootFolderName,
spaces: String? = null,
trashed: Boolean = false
): List<File> {
return findFilesOrNull(name, mimeType, parent, spaces, trashed) ?: run {
val file = File()
file.name = name
file.mimeType = mimeType
file.parents = listOf(parent)
val create = files().create(file)
return@run listOf(create.execute())
}
}
internal fun Drive.updateOrCreate(
name: String,
mimeType: String,
parent: String = rootFolderName,
spaces: String? = null,
trashed: Boolean = false,
stream: InputStream,
fileConfig: ((file: File) -> Unit)? = null
): File {
val files = files()
return run {
val find = files.basicList(spaces)
find.q = "name = '$name' and '$parent' in parents and mimeType = '$mimeType' and trashed = $trashed"
val fileId = try {
find.execute().files.firstOrNull()?.id ?: return@run null
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 404) {
return@run null
} else {
throw e
}
}
return@run files.performUpdate(fileId, name, mimeType, stream, fileConfig)
} ?: run {
val file = File()
file.name = name
file.mimeType = mimeType
file.parents = listOf(parent)
fileConfig?.invoke(file)
val create = files.create(file, InputStreamContent(mimeType, stream))
create.fields = requiredRequestFields
return@run create.execute()
}
}
internal fun Drive.Files.performUpdate(
fileId: String,
name: String,
mimeType: String,
stream: InputStream,
fileConfig: ((file: File) -> Unit)? = null
): File {
val file = File()
file.name = name
file.mimeType = mimeType
fileConfig?.invoke(file)
val update = update(fileId, file, InputStreamContent(mimeType, stream))
update.fields = requiredRequestFields
return update.execute()
}
internal fun resolveFilesConflict(client: Drive, list: List<File>, spaces: String?): File {
// Pick newest file
val newest = list.maxBy { it.modifiedTime.value }!!
// Delete all others
val batch = client.batch()
val callback = SimpleJsonBatchCallback<Void>()
val files = client.files()
list.filterNot { it == newest }.forEach { files.delete(it.id).queue(batch, callback) }
batch.execute()
return newest
}
internal fun resolveFoldersConflict(client: Drive, list: List<File>, spaces: String?): File {
val files = client.files()
// Pick newest folder
val newest = list.maxBy { it.modifiedTime.value }!!
// Build a map with all conflicting folders
val query = list.joinToString(" or ") { "'${it.id}' in parents" }
val filesList = ArrayList<File>()
val conflictFilesMap = HashMultimap.create<String, File>()
var nextPageToken: String? = null
do {
val result = files.basicList(spaces).apply {
this.q = query
if (nextPageToken != null) {
this.pageToken = nextPageToken
}
}.execute()
result.files.forEach { file ->
file.parents.forEach { parentId ->
if (parentId == newest.id) {
filesList.add(file)
} else {
conflictFilesMap.put(parentId, file)
}
}
}
nextPageToken = result.nextPageToken
} while (nextPageToken != null)
// Files in this list will be moved to newest folder
val insertList = ArrayList<File>()
// Files in this list will be removed
val removeList = ArrayList<File>()
for ((k, l) in conflictFilesMap.asMap()) {
for (v in l) {
val find = filesList.find { it.name == v.name }
if (find == null) {
insertList.add(v)
} else if (find.modifiedTime.value > v.modifiedTime.value) {
// Our file is newer, remove `v`
removeList.add(v)
} else {
// `v` is newer, update ours
insertList.add(v)
removeList.add(find)
}
}
}
list.filterNotTo(removeList) { it == newest }
if (insertList.isNotEmpty()) {
val callback = object : SimpleJsonBatchCallback<File>() {
override fun onFailure(error: GoogleJsonError, headers: HttpHeaders) {
throw IOException(error.message)
}
}
client.batch().apply {
insertList.forEach { file ->
files.update(file.id, File()).apply {
this.addParents = newest.id
this.removeParents = file.parents?.joinToString(",")
}.queue(this, callback)
}
}.execute()
}
if (removeList.isNotEmpty()) {
val callback = SimpleJsonBatchCallback<Void>()
client.batch().apply {
removeList.forEach { file ->
files.delete(file.id).queue(this, callback)
}
}.execute()
}
return newest
}
internal fun Drive.Files.basicList(spaces: String? = null): Drive.Files.List {
return list().apply {
this.fields = requiredFilesRequestFields
if (spaces != null) {
this.spaces = spaces
}
}
}

View File

@ -1,57 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import android.content.Context
import android.util.Log
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.drive.Drive
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.model.sync.GoogleDriveSyncProviderInfo
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.TaskServiceRunner
import org.mariotaku.twidere.util.sync.*
import java.io.IOException
/**
* Created by mariotaku on 2017/1/6.
*/
class GoogleDriveSyncTaskRunner(context: Context, val refreshToken: String) : SyncTaskRunner(context) {
override fun onRunningTask(action: String, callback: (Boolean) -> Unit): Boolean {
val httpTransport = NetHttpTransport.Builder().build()
val jsonFactory = JacksonFactory.getDefaultInstance()
val credential = GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(GoogleDriveSyncProviderInfo.WEB_CLIENT_ID, GoogleDriveSyncProviderInfo.WEB_CLIENT_SECRET)
.build()
credential.refreshToken = refreshToken
val drive = Drive.Builder(httpTransport, JacksonFactory.getDefaultInstance(), credential).build()
val syncAction: ISyncAction = when (action) {
TaskServiceRunner.ACTION_SYNC_DRAFTS -> GoogleDriveDraftsSyncAction(context, drive)
TaskServiceRunner.ACTION_SYNC_FILTERS -> GoogleDriveFiltersDataSyncAction(context, drive)
TaskServiceRunner.ACTION_SYNC_USER_COLORS -> GoogleDrivePreferencesValuesSyncAction(context,
drive, userColorNameManager.colorPreferences, UserColorsSyncProcessor,
"user_colors.xml")
TaskServiceRunner.ACTION_SYNC_USER_NICKNAMES -> GoogleDrivePreferencesValuesSyncAction(context,
drive, userColorNameManager.nicknamePreferences, UserNicknamesSyncProcessor,
"user_nicknames.xml")
else -> null
} ?: return false
task {
syncAction.execute()
}.successUi {
callback(true)
}.failUi {
DebugLog.w(LOGTAG_SYNC, "Sync $action failed", it)
callback(false)
}
return true
}
}

View File

@ -1,37 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import com.dropbox.core.v2.files.UploadUploader
import com.google.api.client.util.DateTime
import com.google.api.services.drive.Drive
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
abstract internal class GoogleDriveUploadSession<in Data>(
val name: String,
val parentId: String,
val mimeType: String,
val drive: Drive
) : Closeable {
private var uploader: UploadUploader? = null
var localModifiedTime: Long = 0
override fun close() {
uploader?.close()
}
@Throws(IOException::class)
abstract fun Data.toInputStream(): InputStream
fun uploadData(data: Data): Boolean {
data.toInputStream().use {
drive.updateOrCreate(name = name, mimeType = mimeType, parent = parentId,
spaces = appDataFolderSpace, stream = it, fileConfig = {
it.modifiedTime = DateTime(localModifiedTime)
})
}
return true
}
}

View File

@ -1,23 +0,0 @@
package org.mariotaku.twidere.util.sync.google
import com.google.api.client.googleapis.batch.json.JsonBatchCallback
import com.google.api.client.googleapis.json.GoogleJsonError
import com.google.api.client.googleapis.json.GoogleJsonResponseException
import com.google.api.client.http.HttpHeaders
import com.google.api.client.http.HttpResponseException
import java.io.IOException
/**
* Created by mariotaku on 1/22/17.
*/
internal open class SimpleJsonBatchCallback<T> : JsonBatchCallback<T>() {
@Throws(IOException::class)
override fun onFailure(error: GoogleJsonError, headers: HttpHeaders) {
}
@Throws(IOException::class)
override fun onSuccess(result: T, headers: HttpHeaders) {
}
}

View File

@ -1,53 +0,0 @@
package org.mariotaku.twidere.view.controller.premium
import android.view.View
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.REQUEST_PURCHASE_EXTRA_FEATURES
import org.mariotaku.twidere.activity.PremiumDashboardActivity
import org.mariotaku.twidere.fragment.ExtraFeaturesIntroductionDialogFragment
/**
* Created by mariotaku on 2017/2/4.
*/
abstract class AbsGoogleInAppItemViewController : PremiumDashboardActivity.ExtraFeatureViewController() {
abstract val title: String
abstract val summary: String
abstract val feature: String
abstract val availableLabel: String
override fun onCreate() {
super.onCreate()
titleView.text = title
messageView.text = summary
button1.setText(R.string.action_purchase)
button2.text = availableLabel
button1.setOnClickListener {
ExtraFeaturesIntroductionDialogFragment.show(activity.supportFragmentManager,
feature = this.feature, requestCode = REQUEST_PURCHASE_EXTRA_FEATURES)
}
button2.setOnClickListener {
onAvailableButtonClick()
}
updateEnabledState()
}
override fun onResume() {
super.onResume()
updateEnabledState()
}
abstract fun onAvailableButtonClick()
private fun updateEnabledState() {
if (extraFeaturesService.isEnabled(feature)) {
button1.visibility = View.GONE
button2.visibility = View.VISIBLE
} else {
button1.visibility = View.VISIBLE
button2.visibility = View.GONE
}
}
}

View File

@ -1,26 +0,0 @@
package org.mariotaku.twidere.view.controller.premium
import android.widget.Toast
import org.mariotaku.twidere.R
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
/**
* Created by mariotaku on 2017/2/4.
*/
class GoogleFiltersImportViewController : AbsGoogleInAppItemViewController() {
override val feature: String
get() = ExtraFeaturesService.FEATURE_FILTERS_IMPORT
override val summary: String
get() = context.getString(R.string.extra_feature_description_filters_import)
override val title: String
get() = context.getString(R.string.extra_feature_title_filters_import)
override val availableLabel: String
get() = context.getString(R.string.action_import)
override fun onAvailableButtonClick() {
IntentUtils.openFilters(context, "users")
Toast.makeText(context, R.string.message_toast_filters_import_hint, Toast.LENGTH_SHORT).show()
}
}

View File

@ -1,24 +0,0 @@
package org.mariotaku.twidere.view.controller.premium
import org.mariotaku.twidere.R
import org.mariotaku.twidere.util.IntentUtils
import org.mariotaku.twidere.util.premium.ExtraFeaturesService
/**
* Created by mariotaku on 2017/2/4.
*/
class GoogleFiltersSubscriptionsViewController : AbsGoogleInAppItemViewController() {
override val feature: String
get() = ExtraFeaturesService.FEATURE_FILTERS_SUBSCRIPTION
override val summary: String
get() = context.getString(R.string.extra_feature_description_filters_subscription)
override val title: String
get() = context.getString(R.string.extra_feature_title_filters_subscription)
override val availableLabel: String
get() = context.getString(R.string.action_filter_subscriptions_card_manage)
override fun onAvailableButtonClick() {
IntentUtils.openFilters(context, "settings")
}
}

View File

@ -1,24 +0,0 @@
<?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/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:id="@+id/map_fragment"
android:layout_height="match_parent" />

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Twidere - Twitter client for Android
~
~ Copyright (C) 2012-2014 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">
<item
android:id="@id/center"
android:icon="@drawable/ic_action_my_location"
app:showAsAction="always"
android:title="@string/action_center"/>
</menu>

View File

@ -1 +0,0 @@
org.mariotaku.twidere.util.FabricAnalyzer

View File

@ -1 +0,0 @@
org.mariotaku.twidere.util.GoogleMapFragmentFactory

View File

@ -1 +0,0 @@
org.mariotaku.twidere.util.premium.GooglePlayExtraFeaturesService

View File

@ -1,2 +0,0 @@
org.mariotaku.twidere.util.sync.NonFreeSyncProviderInfoFactory
org.mariotaku.twidere.util.sync.OpenSourceSyncProviderInfoFactory