diff --git a/build.sbt b/build.sbt index fa0ee427..b0ea50f3 100644 --- a/build.sbt +++ b/build.sbt @@ -36,12 +36,12 @@ libraryDependencies ++= Seq( "com.android.support" % "design" % "24.2.1", "com.android.support" % "gridlayout-v7" % "24.2.1", "com.android.support" % "preference-v14" % "24.2.1", + "com.evernote" % "android-job" % "1.1.0", "com.github.clans" % "fab" % "1.6.4", "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", "com.github.kevinsawicki" % "http-request" % "6.0", "com.google.android.gms" % "play-services-ads" % "9.6.1", "com.google.android.gms" % "play-services-analytics" % "9.6.1", - "com.google.firebase" % "firebase-storage" % "9.6.1", "com.j256.ormlite" % "ormlite-android" % "5.0", "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2", "dnsjava" % "dnsjava" % "2.1.7", diff --git a/src/main/assets/acl/bypass_chn.acl b/src/main/assets/acl/bypass-china.acl similarity index 100% rename from src/main/assets/acl/bypass_chn.acl rename to src/main/assets/acl/bypass-china.acl diff --git a/src/main/assets/acl/bypass_lan_chn.acl b/src/main/assets/acl/bypass-lan-china.acl similarity index 100% rename from src/main/assets/acl/bypass_lan_chn.acl rename to src/main/assets/acl/bypass-lan-china.acl diff --git a/src/main/assets/acl/bypass_lan.acl b/src/main/assets/acl/bypass-lan.acl similarity index 100% rename from src/main/assets/acl/bypass_lan.acl rename to src/main/assets/acl/bypass-lan.acl diff --git a/src/main/assets/acl/chinalist.acl b/src/main/assets/acl/china-list.acl similarity index 100% rename from src/main/assets/acl/chinalist.acl rename to src/main/assets/acl/china-list.acl diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index b2b358a3..1bc5a11e 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -39,7 +39,7 @@ package com.github.shadowsocks -import java.io.{File, IOException} +import java.io.IOException import java.util.{Timer, TimerTask} import android.app.Service @@ -54,8 +54,6 @@ import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ -import com.google.firebase.FirebaseApp -import com.google.firebase.storage.FirebaseStorage trait BaseService extends Service { @@ -278,7 +276,6 @@ trait BaseService extends Service { override def onCreate() { super.onCreate() - FirebaseApp.initializeApp(this) app.refreshContainerHolder app.updateAssets() } @@ -315,33 +312,4 @@ trait BaseService extends Service { case ex: Exception => "exclude = " + default + ";" } } - - def fetchAcl(route: String) { - val localAcl = route match { - case Route.BYPASS_LAN => "bypass_lan.acl" - case Route.BYPASS_CHN => "bypass_chn.acl" - case Route.BYPASS_LAN_CHN => "bypass_lan_chn.acl" - case Route.GFWLIST => "gfwlist.acl" - case Route.CHINALIST => "chinalist.acl" - } - val cloudAclFile = new File(getApplicationInfo.dataDir + "/" + localAcl) - if (!cloudAclFile.exists) cloudAclFile.createNewFile - val tempFile = File.createTempFile(localAcl, "acl") - val storage = FirebaseStorage.getInstance - val storageRef = storage.getReferenceFromUrl("gs://admob-app-id-3330146721.appspot.com/acl/") - val aclRef = storageRef.child(localAcl) - - val task = aclRef.getFile(tempFile) - task.addOnSuccessListener(_ => { - if (!tempFile.renameTo(cloudAclFile)) { - Log.d("shadowsocks", "Failed to rename acl file") - if (!tempFile.delete()) tempFile.deleteOnExit() - } - return - }) - task.addOnFailureListener(_ => { - Log.e("shadowsocks", "Unable to download " + localAcl) - if (!tempFile.delete()) tempFile.deleteOnExit() - }) - } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 4e3924e2..2abbc546 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -39,7 +39,7 @@ package com.github.shadowsocks -import java.io.{FileOutputStream, IOException, InputStream, OutputStream} +import java.io.{FileOutputStream, IOException} import java.util import java.util.Locale import java.util.concurrent.TimeUnit @@ -51,9 +51,11 @@ import android.os.{Build, LocaleList} import android.preference.PreferenceManager import android.support.v7.app.AppCompatDelegate import android.util.Log +import com.evernote.android.job.JobManager import com.github.shadowsocks.database.{DBHelper, ProfileManager} +import com.github.shadowsocks.job.DonaldTrump import com.github.shadowsocks.utils.CloseUtils._ -import com.github.shadowsocks.utils.{Executable, Key, TcpFastOpen, Utils} +import com.github.shadowsocks.utils._ import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser} import com.google.android.gms.common.api.ResultCallback import com.google.android.gms.tagmanager.{ContainerHolder, TagManager} @@ -181,6 +183,7 @@ class ShadowsocksApplication extends Application { } } pending.setResultCallback(callback, 2, TimeUnit.SECONDS) + JobManager.create(this).addJobCreator(DonaldTrump) TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) } @@ -190,14 +193,6 @@ class ShadowsocksApplication extends Application { if (holder != null) holder.refresh() } - private def copyStream(in: InputStream, out: OutputStream) { - val buffer = new Array[Byte](1024) - while (true) { - val count = in.read(buffer) - if (count < 0) return else out.write(buffer, 0, count) - } - } - private def copyAssets(path: String) { val assetManager = getAssets var files: Array[String] = null @@ -209,7 +204,7 @@ class ShadowsocksApplication extends Application { if (files != null) for (file <- files) autoClose(assetManager.open(if (path.nonEmpty) path + '/' + file else file))(in => autoClose(new FileOutputStream(getApplicationInfo.dataDir + '/' + file))(out => - copyStream(in, out))) + IOUtils.copy(in, out))) } def copyAssets() { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 8d0ea117..c0e85091 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -48,6 +48,7 @@ import android.os._ import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.job.AclSyncJob import com.github.shadowsocks.utils._ import eu.chainfire.libsuperuser.Shell @@ -96,14 +97,7 @@ class ShadowsocksNatService extends BaseService { if (profile.route != Route.ALL) { cmd += "--acl" - - profile.route match { - case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl") - case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl") - case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl") - case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl") - case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl") - } + cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl" } if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) @@ -338,8 +332,7 @@ class ShadowsocksNatService extends BaseService { handleConnection() - // lazily get ACL from cloud - fetchAcl(profile.route) + AclSyncJob.schedule(profile.route) changeState(State.CONNECTED) notification = new ShadowsocksNotification(this, profile.name, true) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 3c8c3c80..9629611d 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -50,6 +50,7 @@ import android.os._ import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.job.AclSyncJob import com.github.shadowsocks.utils._ import scala.collection.mutable.ArrayBuffer @@ -163,8 +164,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { handleConnection() changeState(State.CONNECTED) - // lazily get ACL from cloud - fetchAcl(profile.route) + AclSyncJob.schedule(profile.route) notification = new ShadowsocksNotification(this, profile.name) } @@ -261,14 +261,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (profile.route != Route.ALL) { cmd += "--acl" - - profile.route match { - case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl") - case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl") - case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl") - case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl") - case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl") - } + cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl" } if (TcpFastOpen.sendEnabled) cmd += "--fast-open" diff --git a/src/main/scala/com/github/shadowsocks/job/AclSyncJob.scala b/src/main/scala/com/github/shadowsocks/job/AclSyncJob.scala new file mode 100644 index 00000000..f0141ad7 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/job/AclSyncJob.scala @@ -0,0 +1,45 @@ +package com.github.shadowsocks.job + +import java.io.IOException +import java.net.URL + +import com.evernote.android.job.Job.{Params, Result} +import com.evernote.android.job.{Job, JobRequest} +import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.utils.CloseUtils._ +import com.github.shadowsocks.utils.IOUtils + +/** + * @author Mygod + */ +object AclSyncJob { + final val TAG = "AclSyncJob" + + def schedule(route: String) = new JobRequest.Builder(AclSyncJob.TAG + ':' + route) + .setExecutionWindow(1, 10000) + .setRequirementsEnforced(true) + .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) + .setRequiresCharging(true) + .setUpdateCurrent(true) + .build().schedule() +} + +class AclSyncJob(route: String) extends Job { + override def onRunJob(params: Params): Result = { + val filename = route + ".acl" + try { + //noinspection JavaAccessorMethodCalledAsEmptyParen + IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename, autoClose( + new URL("https://raw.githubusercontent.com/shadowsocks/shadowsocks-android/master/src/main/assets/acl/" + + filename).openConnection().getInputStream())(IOUtils.readString)) + Result.SUCCESS + } catch { + case e: IOException => + e.printStackTrace() + Result.RESCHEDULE + case e: Exception => // unknown failures, probably shouldn't retry + e.printStackTrace() + Result.FAILURE + } + } +} diff --git a/src/main/scala/com/github/shadowsocks/job/DonaldTrump.scala b/src/main/scala/com/github/shadowsocks/job/DonaldTrump.scala new file mode 100644 index 00000000..45872fe3 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/job/DonaldTrump.scala @@ -0,0 +1,24 @@ +package com.github.shadowsocks.job + +import android.util.Log +import com.evernote.android.job.JobCreator + +/** + * “I create jobs all day long. + * - Donald Trump, 2015 + * + * Source: http://www.cnn.com/2015/09/24/politics/donald-trump-marco-rubio-foreign-policy/ + * + * @author !Mygod + */ +object DonaldTrump extends JobCreator { + def create(tag: String) = { + val parts = tag.split(":") + parts(0) match { + case AclSyncJob.TAG => new AclSyncJob(parts(1)) + case _ => + Log.w("BillClinton", "Unknown job tag: " + tag) + null + } + } +} diff --git a/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala b/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala new file mode 100644 index 00000000..a5d5d144 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala @@ -0,0 +1,31 @@ +package com.github.shadowsocks.utils + +import com.github.shadowsocks.utils.CloseUtils._ +import java.io.{FileWriter, InputStream, OutputStream} + +/** + * @author Mygod + */ +object IOUtils { + private final val BUFFER_SIZE = 32 * 1024 + + def copy(in: InputStream, out: OutputStream) { + val buffer = new Array[Byte](BUFFER_SIZE) + while (true) { + val count = in.read(buffer) + if (count >= 0) out.write(buffer, 0, count) else return + } + } + + def readString(in: InputStream): String = { + val builder = new StringBuilder() + val buffer = new Array[Byte](BUFFER_SIZE) + while (true) { + val count = in.read(buffer) + if (count >= 0) builder.append(new String(buffer, 0, count)) else return builder.toString() + } + null + } + + def writeString(file: String, content: String) = autoClose(new FileWriter(file))(writer => writer.write(content)) +}