Commit 8e17e4fb authored by Max Lv's avatar Max Lv

Clean up and bump version

parent 36576a61
......@@ -56,8 +56,8 @@ fun Project.setupCore() {
setupCommon()
android.apply {
defaultConfig {
versionCode = 5010450
versionName = "5.1.4-nightly"
versionCode = 5010550
versionName = "5.1.5-nightly"
}
compileOptions.isCoreLibraryDesugaringEnabled = true
lintOptions {
......
......@@ -73,7 +73,7 @@ dependencies {
api(project(":plugin"))
api("androidx.appcompat:appcompat:1.2.0")
api("androidx.core:core-ktx:1.4.0-alpha01")
api("androidx.core:core-ktx:1.5.0-alpha05")
api("androidx.fragment:fragment-ktx:1.3.0-beta01")
api("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
......@@ -84,9 +84,8 @@ dependencies {
api("androidx.work:work-gcm:$workVersion")
api("com.google.android.gms:play-services-oss-licenses:17.0.0")
api("com.google.code.gson:gson:2.8.6")
api("com.google.firebase:firebase-analytics-ktx:17.5.0")
api("com.google.firebase:firebase-config-ktx:19.2.0")
api("com.google.firebase:firebase-crashlytics:17.2.2")
api("com.google.firebase:firebase-analytics-ktx:18.0.0")
api("com.google.firebase:firebase-crashlytics:17.3.0")
api("com.jakewharton.timber:timber:4.7.1")
api("dnsjava:dnsjava:3.3.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
......
......@@ -32,7 +32,6 @@ import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.signaturesCompat
import com.github.shadowsocks.utils.useCancellable
import com.google.firebase.remoteconfig.ktx.get
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
......@@ -52,37 +51,6 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
private var scheduleConfigUpdate = false
suspend fun init(service: BaseService.Interface) {
if (profile.isSponsored) {
scheduleConfigUpdate = true
val mdg = MessageDigest.getInstance("SHA-1")
mdg.update(Core.packageInfo.signaturesCompat.first().toByteArray())
val (config, success) = RemoteConfig.fetch()
scheduleConfigUpdate = !success
val conn = withContext(Dispatchers.IO) {
// Network.openConnection might use networking, see https://issuetracker.google.com/issues/135242093
service.openConnection(URL(config["proxy_url"].asString())) as HttpURLConnection
}
conn.requestMethod = "POST"
conn.doOutput = true
val proxies = conn.useCancellable {
try {
outputStream.bufferedWriter().use {
it.write("sig=" + Base64.encodeToString(mdg.digest(), Base64.DEFAULT))
}
inputStream.bufferedReader().readText()
} catch (e: IOException) {
throw BaseService.ExpectedExceptionWrapper(e)
}
}.split('|').toMutableList()
proxies.shuffle()
val proxy = proxies.first().split(':')
profile.host = proxy[0].trim()
profile.remotePort = proxy[1].trim().toInt()
profile.password = proxy[2].trim()
profile.method = proxy[3].trim()
}
// it's hard to resolve DNS on a specific interface so we'll do it here
if (profile.host.parseNumericAddress() == null) {
profile.host = try {
......@@ -139,7 +107,6 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
fun scheduleUpdate() {
if (route !in arrayOf(Acl.ALL, Acl.CUSTOM_RULES)) AclSyncer.schedule(route)
if (scheduleConfigUpdate) RemoteConfig.fetchAsync()
}
fun shutdown(scope: CoroutineScope) {
......
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2018 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 com.github.shadowsocks.bg
import com.github.shadowsocks.core.R
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.analytics.ktx.logEvent
import com.google.firebase.ktx.Firebase
import com.google.firebase.remoteconfig.ktx.remoteConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.tasks.await
import timber.log.Timber
object RemoteConfig {
private val config = GlobalScope.async(Dispatchers.Main.immediate) {
Firebase.remoteConfig.apply { setDefaultsAsync(R.xml.default_configs).await() }
}
fun fetchAsync() = GlobalScope.async(Dispatchers.Main.immediate) { fetch() }
suspend fun fetch() = config.await().run {
try {
fetch().await()
this to true
} catch (e: Exception) {
Timber.d(e)
Firebase.analytics.logEvent("femote_config_failure") { param("message", e.toString()) }
this to false
}
}
}
......@@ -101,7 +101,7 @@ data class Profile(
companion object {
private const val serialVersionUID = 1L
private const val sponsored = "198.199.101.152"
private const val sponsored = "example.shadowsocks.org"
private val pattern =
"""(?i)ss://[-a-zA-Z0-9+&@#/%?=.~*'()|!:,;_\[\]]*[-a-zA-Z0-9+&@#/%=.~*'()|\[\]]""".toRegex()
private val userInfoPattern = "^(.+?):(.*)$".toRegex()
......
......@@ -17,10 +17,9 @@ dependencies {
implementation("androidx.browser:browser:1.2.0")
implementation("androidx.camera:camera-camera2:$cameraxVersion")
implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
implementation("androidx.camera:camera-view:1.0.0-alpha17")
implementation("androidx.constraintlayout:constraintlayout:2.0.1")
implementation("androidx.camera:camera-view:1.0.0-alpha19")
implementation("androidx.constraintlayout:constraintlayout:2.0.4")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
implementation("com.google.firebase:firebase-ads:19.4.0")
implementation("com.google.mlkit:barcode-scanning:16.0.3")
implementation("com.google.zxing:core:3.4.1")
implementation("com.takisoft.preferencex:preferencex-simplemenu:1.1.0")
......
/*******************************************************************************
* *
* Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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 com.github.shadowsocks
import android.content.Context
import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.RequestConfiguration
internal object AdsManager {
init {
MobileAds.setRequestConfiguration(RequestConfiguration.Builder().apply {
setTestDeviceIds(listOf(
"B08FC1764A7B250E91EA9D0D5EBEB208", "7509D18EB8AF82F915874FEF53877A64",
"F58907F28184A828DD0DB6F8E38189C6", "FE983F496D7C5C1878AA163D9420CA97"))
}.build())
}
fun load(context: Context?, setup: AdLoader.Builder.() -> Unit) =
AdLoader.Builder(context, "ca-app-pub-3283768469187309/8632513739").apply(setup).build()
.loadAd(AdRequest.Builder().build())
}
......@@ -56,10 +56,6 @@ import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener
import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.gms.ads.VideoOptions
import com.google.android.gms.ads.formats.NativeAdOptions
import com.google.android.gms.ads.formats.UnifiedNativeAd
import com.google.android.gms.ads.formats.UnifiedNativeAdView
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
......@@ -89,32 +85,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private fun isProfileEditable(id: Long) =
(activity as MainActivity).state == BaseService.State.Stopped || id !in Core.activeProfileIds
private var nativeAd: UnifiedNativeAd? = null
private var nativeAdView: UnifiedNativeAdView? = null
private var adHost: ProfileViewHolder? = null
private fun tryBindAd() = lifecycleScope.launchWhenStarted {
val fp = layoutManager.findFirstVisibleItemPosition()
if (fp < 0) return@launchWhenStarted
for (i in object : Iterator<Int> {
var first = fp
var last = layoutManager.findLastCompletelyVisibleItemPosition()
var flipper = false
override fun hasNext() = first <= last
override fun next(): Int {
flipper = !flipper
return if (flipper) first++ else last--
}
}.asSequence().toList().reversed()) {
val viewHolder = profilesList.findViewHolderForAdapterPosition(i) as? ProfileViewHolder
if (viewHolder?.item?.isSponsored == true) {
viewHolder.populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!)
// might be in the middle of a layout after scrolling, need to wait
withContext(Dispatchers.Main) { profilesAdapter.notifyItemChanged(i) }
break
}
}
}
@SuppressLint("ValidFragment")
class QRCodeDialog() : DialogFragment() {
constructor(url: String) : this() {
......@@ -157,7 +127,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private val traffic = itemView.findViewById<TextView>(R.id.traffic)
private val edit = itemView.findViewById<View>(R.id.edit)
private val subscription = itemView.findViewById<View>(R.id.subscription)
private val adContainer = itemView.findViewById<LinearLayout>(R.id.ad_container)
init {
edit.setOnClickListener {
......@@ -181,92 +150,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
TooltipCompat.setTooltipText(share, share.contentDescription)
}
fun populateUnifiedNativeAdView(nativeAd: UnifiedNativeAd, adView: UnifiedNativeAdView) {
// Set other ad assets.
adView.headlineView = adView.findViewById(R.id.ad_headline)
adView.bodyView = adView.findViewById(R.id.ad_body)
adView.callToActionView = adView.findViewById(R.id.ad_call_to_action)
adView.iconView = adView.findViewById(R.id.ad_app_icon)
adView.starRatingView = adView.findViewById(R.id.ad_stars)
adView.advertiserView = adView.findViewById(R.id.ad_advertiser)
// The headline and media content are guaranteed to be in every UnifiedNativeAd.
(adView.headlineView as TextView).text = nativeAd.headline
// These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to
// check before trying to display them.
if (nativeAd.body == null) {
adView.bodyView.visibility = View.INVISIBLE
} else {
adView.bodyView.visibility = View.VISIBLE
(adView.bodyView as TextView).text = nativeAd.body
}
if (nativeAd.callToAction == null) {
adView.callToActionView.visibility = View.INVISIBLE
} else {
adView.callToActionView.visibility = View.VISIBLE
(adView.callToActionView as Button).text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
adView.iconView.visibility = View.GONE
} else {
(adView.iconView as ImageView).setImageDrawable(
nativeAd.icon.drawable)
adView.iconView.visibility = View.VISIBLE
}
if (nativeAd.starRating == null) {
adView.starRatingView.visibility = View.INVISIBLE
} else {
(adView.starRatingView as RatingBar).rating = nativeAd.starRating!!.toFloat()
adView.starRatingView.visibility = View.VISIBLE
}
if (nativeAd.advertiser == null) {
adView.advertiserView.visibility = View.INVISIBLE
} else {
(adView.advertiserView as TextView).text = nativeAd.advertiser
adView.advertiserView.visibility = View.VISIBLE
}
// This method tells the Google Mobile Ads SDK that you have finished populating your
// native ad view with this native ad.
adView.setNativeAd(nativeAd)
adContainer.addView(adView)
adHost = this
}
fun attach() {
if (adHost != null || !item.isSponsored) return
if (nativeAdView == null) {
nativeAdView = layoutInflater.inflate(R.layout.ad_unified, adContainer, false) as UnifiedNativeAdView
AdsManager.load(context) {
forUnifiedNativeAd { unifiedNativeAd ->
// You must call destroy on old ads when you are done with them,
// otherwise you will have a memory leak.
nativeAd?.destroy()
nativeAd = unifiedNativeAd
tryBindAd()
}
withNativeAdOptions(NativeAdOptions.Builder().apply {
setVideoOptions(VideoOptions.Builder().apply {
setStartMuted(true)
}.build())
}.build())
}
} else if (nativeAd != null) populateUnifiedNativeAdView(nativeAd!!, nativeAdView!!)
}
fun detach() {
if (adHost == this) {
adHost = null
adContainer.removeAllViews()
tryBindAd()
}
}
fun bind(item: Profile) {
this.item = item
val editable = isProfileEditable(item.id)
......@@ -341,8 +224,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
setHasStableIds(true) // see: http://stackoverflow.com/a/32488059/2245107
}
override fun onViewAttachedToWindow(holder: ProfileViewHolder) = holder.attach()
override fun onViewDetachedFromWindow(holder: ProfileViewHolder) = holder.detach()
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = holder.bind(profiles[position])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.layout_profile, parent, false))
......@@ -594,7 +475,6 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onDestroyView() {
undoManager.flush()
nativeAd?.destroy()
super.onDestroyView()
}
......
<com.google.android.gms.ads.formats.UnifiedNativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minHeight="40dp"
android:orientation="vertical">
<TextView style="@style/Theme.Shadowsocks.AdAttribution"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:paddingBottom="5dp"
android:paddingEnd="5dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:maxLines="1"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="bottom"
android:textColor="?android:attr/textColorPrimary"
android:textSize="12sp"
android:textStyle="bold"/>
<RatingBar
android:id="@+id/ad_stars"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.5" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:textColor="?android:attr/textColorSecondary"
android:textSize="12sp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal" >
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.formats.UnifiedNativeAdView>
......@@ -103,10 +103,4 @@
tools:text="@string/traffic"/>
</RelativeLayout>
<LinearLayout
android:id="@+id/ad_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:orientation="vertical" />
</LinearLayout>
rootProject.extra.apply {
set("androidPlugin", "com.android.tools.build:gradle:4.1.0-rc03")
set("androidPlugin", "com.android.tools.build:gradle:4.1.1")
set("kotlinVersion", "1.4.10")
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment