update
This commit is contained in:
parent
df24548018
commit
40fb451fa4
@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "relax.offline.mp3.music"
|
applicationId = "relax.offline.mp3.music"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 3
|
||||||
versionName = "1.0.1"
|
versionName = "1.0.3"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@ -30,13 +30,13 @@ android {
|
|||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// debug {
|
debug {
|
||||||
// isMinifyEnabled = true
|
isMinifyEnabled = true
|
||||||
// proguardFiles(
|
proguardFiles(
|
||||||
// getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
// "proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
@ -60,27 +60,21 @@ dependencies {
|
|||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.media3:media3-session:1.3.1")
|
implementation("androidx.media3:media3-session:1.3.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
|
||||||
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
|
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
|
||||||
implementation("androidx.room:room-ktx:2.6.1")
|
implementation("androidx.room:room-ktx:2.6.1")
|
||||||
implementation("androidx.room:room-runtime:2.6.1")
|
implementation("androidx.room:room-runtime:2.6.1")
|
||||||
//noinspection KaptUsageInsteadOfKsp
|
//noinspection KaptUsageInsteadOfKsp
|
||||||
kapt("androidx.room:room-compiler:2.6.1")
|
kapt("androidx.room:room-compiler:2.6.1")
|
||||||
implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
|
|
||||||
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
|
|
||||||
implementation("com.github.lihangleo2:ShadowLayout:3.4.0")
|
|
||||||
|
|
||||||
implementation("androidx.media3:media3-exoplayer:1.3.1")
|
implementation("androidx.media3:media3-exoplayer:1.3.1")
|
||||||
implementation("androidx.media3:media3-exoplayer-dash:1.3.1")
|
implementation("androidx.media3:media3-exoplayer-dash:1.3.1")
|
||||||
implementation("androidx.media3:media3-ui:1.3.1")
|
implementation("androidx.media3:media3-ui:1.3.1")
|
||||||
implementation("androidx.media3:media3-common:1.3.1")
|
implementation("androidx.media3:media3-common:1.3.1")
|
||||||
// implementation("com.android.tools.compose:compose-preview-renderer:0.0.1-alpha01")
|
|
||||||
// implementation("org.chromium.net:cronet-api:119.6045.31")
|
|
||||||
implementation("androidx.media3:media3-datasource-cronet:1.3.1")
|
implementation("androidx.media3:media3-datasource-cronet:1.3.1")
|
||||||
|
|
||||||
implementation("io.coil-kt:coil-compose:2.6.0")
|
implementation("io.coil-kt:coil-compose:2.6.0")
|
||||||
@ -92,7 +86,15 @@ dependencies {
|
|||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.8")
|
||||||
implementation("org.brotli:dec:0.1.2")
|
implementation("org.brotli:dec:0.1.2")
|
||||||
|
|
||||||
|
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||||
|
implementation("com.geyifeng.immersionbar:immersionbar:3.2.2")
|
||||||
|
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
|
||||||
|
implementation("com.github.lihangleo2:ShadowLayout:3.4.0")
|
||||||
implementation("com.google.android.flexbox:flexbox:3.0.0")
|
implementation("com.google.android.flexbox:flexbox:3.0.0")
|
||||||
implementation("io.github.scwang90:refresh-layout-kernel:2.1.0")
|
implementation("io.github.scwang90:refresh-layout-kernel:2.1.0")
|
||||||
implementation("io.github.scwang90:refresh-footer-ball:2.1.0")
|
implementation("io.github.scwang90:refresh-footer-ball:2.1.0")
|
||||||
|
implementation ("com.google.code.gson:gson:2.10.1")
|
||||||
|
|
||||||
|
//google
|
||||||
|
// implementation("com.google.android.gms:play-services-ads-identifier:18.0.1")
|
||||||
}
|
}
|
||||||
26
app/proguard-rules.pro
vendored
26
app/proguard-rules.pro
vendored
@ -20,4 +20,28 @@
|
|||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
|
-dontwarn java.lang.management.ManagementFactory
|
||||||
|
-dontwarn java.lang.management.RuntimeMXBean
|
||||||
|
-dontwarn org.slf4j.impl.StaticMDCBinder
|
||||||
|
-dontwarn org.slf4j.impl.StaticMarkerBinder
|
||||||
|
# Gson 的混淆规则
|
||||||
|
# 保持 Gson 所使用的类及其字段名称
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
-keep class com.google.gson.stream.** { *; }
|
||||||
|
|
||||||
|
# 保持使用 @Expose 注解的字段
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
|
||||||
|
# 保持被序列化/反序列化的类(确保您的模型类不会被混淆)
|
||||||
|
-keep class your.package.name.** { *; }
|
||||||
|
|
||||||
|
# 保持使用 @SerializedName 注解的字段
|
||||||
|
-keep class ** {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保持 Gson TypeAdapter 类
|
||||||
|
-keep class * extends com.google.gson.TypeAdapter {
|
||||||
|
*;
|
||||||
|
}
|
||||||
|
|||||||
@ -7,13 +7,16 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
android:maxSdkVersion="32" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="relax.offline.music.App"
|
android:name="relax.offline.music.App"
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import relax.offline.music.database.AppFavoriteDBManager
|
import relax.offline.music.database.AppFavoriteDBManager
|
||||||
|
import relax.offline.music.http.CommonIpInfoUtil
|
||||||
|
import relax.offline.music.http.UploadEventName
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
@ -119,6 +121,8 @@ class App : Application() {
|
|||||||
initImportAudio()
|
initImportAudio()
|
||||||
CacheManager.initializeCaches(this)
|
CacheManager.initializeCaches(this)
|
||||||
DownloadUtil.getDownloadManager(this)
|
DownloadUtil.getDownloadManager(this)
|
||||||
|
CommonIpInfoUtil.shared.initIPInfo()
|
||||||
|
UploadEventName.shared.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -6,14 +6,14 @@ import android.os.CountDownTimer
|
|||||||
import com.gyf.immersionbar.ktx.immersionBar
|
import com.gyf.immersionbar.ktx.immersionBar
|
||||||
import relax.offline.music.databinding.ActivityLaunchBinding
|
import relax.offline.music.databinding.ActivityLaunchBinding
|
||||||
|
|
||||||
class LaunchActivity : BaseActivity() {
|
class LaunchActivity : MoBaseActivity() {
|
||||||
private lateinit var binding: ActivityLaunchBinding
|
private lateinit var binding: ActivityLaunchBinding
|
||||||
private val totalTime = 3000L // 5秒
|
private val totalTime = 3000L // 5秒
|
||||||
private val interval = 50L // 更新间隔,毫秒
|
private val interval = 50L // 更新间隔,毫秒
|
||||||
private val steps = totalTime / interval
|
private val steps = totalTime / interval
|
||||||
private val progressPerStep = 100f / steps.toFloat()
|
private val progressPerStep = 100f / steps.toFloat()
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
override suspend fun main() {
|
||||||
binding = ActivityLaunchBinding.inflate(layoutInflater)
|
binding = ActivityLaunchBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
initTimer()
|
initTimer()
|
||||||
@ -41,7 +41,11 @@ class LaunchActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun toMainActivity() {
|
private fun toMainActivity() {
|
||||||
startActivity(Intent(this, PrimaryActivity::class.java))
|
if (!withPermission()) {
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
} else {
|
||||||
|
startActivity(Intent(this, PrimaryActivity::class.java))
|
||||||
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package relax.offline.music.activity
|
package relax.offline.music.activity
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
@ -19,34 +20,29 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaMetadata
|
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.offline.Download
|
|
||||||
import androidx.media3.exoplayer.offline.DownloadManager
|
|
||||||
import relax.offline.music.R
|
|
||||||
import relax.offline.music.media.MediaControllerManager
|
|
||||||
import relax.offline.music.sp.AppStore
|
|
||||||
import relax.offline.music.util.LogTag
|
|
||||||
import relax.offline.music.view.MusicPlayerView
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import relax.offline.music.App
|
import relax.offline.music.App
|
||||||
|
import relax.offline.music.R
|
||||||
import relax.offline.music.bean.FavoriteBean
|
import relax.offline.music.bean.FavoriteBean
|
||||||
import relax.offline.music.bean.OfflineBean
|
import relax.offline.music.bean.OfflineBean
|
||||||
import relax.offline.music.innertube.Innertube
|
import relax.offline.music.innertube.Innertube
|
||||||
import relax.offline.music.util.FileSizeConverter
|
import relax.offline.music.media.MediaControllerManager
|
||||||
|
import relax.offline.music.sp.AppStore
|
||||||
|
import relax.offline.music.util.LogTag
|
||||||
|
import relax.offline.music.http.getCountryCode
|
||||||
|
import relax.offline.music.view.MusicPlayerView
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(),
|
abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(),
|
||||||
LifecycleOwner {
|
LifecycleOwner {
|
||||||
private var playerListener: Player.Listener? = null
|
private var playerListener: Player.Listener? = null
|
||||||
private var downloadManagerListener: DownloadManager.Listener? = null
|
|
||||||
|
|
||||||
enum class Event {
|
enum class Event {
|
||||||
ActivityStart,
|
ActivityStart,
|
||||||
@ -240,7 +236,7 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
|||||||
isOffline = true
|
isOffline = true
|
||||||
)
|
)
|
||||||
LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}")
|
LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}")
|
||||||
relax.offline.music.App.appOfflineDBManager.insertOfflineBean(bean)
|
App.appOfflineDBManager.insertOfflineBean(bean)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun insertFavoriteData(mediaItem: MediaItem) {
|
suspend fun insertFavoriteData(mediaItem: MediaItem) {
|
||||||
@ -254,4 +250,46 @@ abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope
|
|||||||
LogTag.LogD(Innertube.TAG, "insertFavoriteBean bean->${bean}")
|
LogTag.LogD(Innertube.TAG, "insertFavoriteBean bean->${bean}")
|
||||||
App.appFavoriteDBManager.insertFavoriteBean(bean)
|
App.appFavoriteDBManager.insertFavoriteBean(bean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withPermission(): Boolean {
|
||||||
|
// 不允许的国家代码
|
||||||
|
val restrictedCountries = setOf("CN", "HK", "TW", "JP", "KR", "GB", "CH", "BE", "MO", "SG")
|
||||||
|
// 检查是否包含当前的国家代码
|
||||||
|
if (appStore.ipCountryCode in restrictedCountries) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 如果不在受限国家代码中,则继续其他检查
|
||||||
|
return withIso()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withIso(): Boolean {
|
||||||
|
//460 || 461 China (People's Republic of)
|
||||||
|
//454 "Hong Kong, China"
|
||||||
|
//466 "Taiwan, China"
|
||||||
|
//440 || 441 Japan
|
||||||
|
//450 Korea (Republic of)
|
||||||
|
//234 || 235 United Kingdom of Great Britain and Northern Ireland
|
||||||
|
//228 Switzerland (Confederation of)
|
||||||
|
//206 Belgium
|
||||||
|
//455 "Macao, China"
|
||||||
|
//525 Singapore (Republic of)
|
||||||
|
val restrictedCountryCodes =
|
||||||
|
setOf(
|
||||||
|
"460",
|
||||||
|
"461",
|
||||||
|
"454",
|
||||||
|
"466",
|
||||||
|
"440",
|
||||||
|
"441",
|
||||||
|
"450",
|
||||||
|
"234",
|
||||||
|
"235",
|
||||||
|
"228",
|
||||||
|
"206",
|
||||||
|
"455",
|
||||||
|
"525"
|
||||||
|
)
|
||||||
|
val currentCountryCode = getCountryCode(this)
|
||||||
|
return currentCountryCode !in restrictedCountryCodes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
|||||||
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
binding.nameTv.text = intent.getStringExtra(PLAY_DETAILS_NAME)
|
||||||
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
binding.descTv.text = intent.getStringExtra(PLAY_DETAILS_DESC)
|
||||||
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
|
val favoriteBeans = App.appFavoriteDBManager.getAllFavoriteBeans()
|
||||||
val allFilteredBeans = favoriteBeans.filter { it.isFavorite}//过滤只有为true的值
|
val allFilteredBeans = favoriteBeans.filter { it.isFavorite }//过滤只有为true的值
|
||||||
//找到当前点击进来的歌曲media
|
//找到当前点击进来的歌曲media
|
||||||
val findCurrentMedia = allFilteredBeans.find { it.videoId == videoId }?.asMediaItem
|
val findCurrentMedia = allFilteredBeans.find { it.videoId == videoId }?.asMediaItem
|
||||||
if (findCurrentMedia != null) {
|
if (findCurrentMedia != null) {
|
||||||
@ -252,10 +252,6 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
|||||||
if (currentScreenDownloads != null) {
|
if (currentScreenDownloads != null) {
|
||||||
LogD(TAG, "updateDownloadUI id->${currentScreenDownloads.request.id}")
|
LogD(TAG, "updateDownloadUI id->${currentScreenDownloads.request.id}")
|
||||||
updateDownloadUI(currentScreenDownloads)
|
updateDownloadUI(currentScreenDownloads)
|
||||||
} else {
|
|
||||||
if (id != null) {
|
|
||||||
updateDownloadUi(id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -696,6 +692,12 @@ class MoPlayDetailsActivity : MoBaseActivity(), Player.Listener {
|
|||||||
updateDownloadUi(mediaItem.mediaId)
|
updateDownloadUi(mediaItem.mediaId)
|
||||||
requests.trySend(Request.UpdateFavorite(mediaItem.mediaId))//更新喜欢状态
|
requests.trySend(Request.UpdateFavorite(mediaItem.mediaId))//更新喜欢状态
|
||||||
|
|
||||||
|
val currentDownload = DownloadUtil.getCurrentIdDownload(mediaItem.mediaId)
|
||||||
|
if (currentDownload != null) {
|
||||||
|
updateDownloadUI(currentDownload)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(mediaItem.mediaMetadata.artworkUri)
|
.load(mediaItem.mediaMetadata.artworkUri)
|
||||||
|
|||||||
@ -117,16 +117,20 @@ class MoSearchMoreActivity : MoBaseActivity() {
|
|||||||
Innertube.moSearchPage(SearchBody(query = query, params = params))?.onSuccess { result ->
|
Innertube.moSearchPage(SearchBody(query = query, params = params))?.onSuccess { result ->
|
||||||
if (result.isNotEmpty()) {
|
if (result.isNotEmpty()) {
|
||||||
showDataUi()
|
showDataUi()
|
||||||
val title = result[0].title
|
var myResult = result[0]
|
||||||
|
if(myResult.searchResultList.isEmpty()){
|
||||||
|
myResult = result[1]
|
||||||
|
}
|
||||||
|
val title = myResult.title
|
||||||
if (title.isNullOrEmpty()) {
|
if (title.isNullOrEmpty()) {
|
||||||
binding.title.visibility = View.GONE
|
binding.title.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
binding.title.visibility = View.VISIBLE
|
binding.title.visibility = View.VISIBLE
|
||||||
binding.title.text = title
|
binding.title.text = title
|
||||||
}
|
}
|
||||||
currentContinuation = result[0].continuation
|
currentContinuation = myResult.continuation
|
||||||
list.clear()
|
list.clear()
|
||||||
list.addAll(result[0].searchResultList)
|
list.addAll(myResult.searchResultList)
|
||||||
} else {
|
} else {
|
||||||
showNoContentUi()
|
showNoContentUi()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.media.MediaPlayer
|
import android.media.MediaPlayer
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -55,15 +56,7 @@ class ImportFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
binding.addBtn.setOnClickListener {
|
binding.addBtn.setOnClickListener {
|
||||||
binding.addBtn.visibility = View.GONE
|
binding.addBtn.visibility = View.GONE
|
||||||
if (ContextCompat.checkSelfPermission(
|
checkAndRequestPermissions()
|
||||||
requireActivity(),
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
|
||||||
) != PackageManager.PERMISSION_GRANTED
|
|
||||||
) {
|
|
||||||
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
|
||||||
} else {
|
|
||||||
openAudioPicker()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
importAdapterList.clear()
|
importAdapterList.clear()
|
||||||
importAdapterList.addAll(relax.offline.music.App.importList)
|
importAdapterList.addAll(relax.offline.music.App.importList)
|
||||||
@ -210,10 +203,9 @@ class ImportFragment : Fragment() {
|
|||||||
binding.noContentLayout.visibility = View.GONE
|
binding.noContentLayout.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
binding.noContentLayout.visibility = View.VISIBLE
|
binding.noContentLayout.visibility = View.VISIBLE
|
||||||
|
showNoDataDialog()
|
||||||
}
|
}
|
||||||
binding.loadingLayout.visibility = View.GONE
|
binding.loadingLayout.visibility = View.GONE
|
||||||
|
|
||||||
binding.addBtn.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,4 +214,74 @@ class ImportFragment : Fragment() {
|
|||||||
return musicFiles
|
return musicFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showNoDataDialog() {
|
||||||
|
AlertDialog.Builder(requireActivity())
|
||||||
|
.setTitle(getString(R.string.prompt))
|
||||||
|
.setMessage(getString(R.string.no_data_prompt_dialog_content))
|
||||||
|
.setPositiveButton(getString(R.string.ok)) { dialog, _ ->
|
||||||
|
binding.addBtn.visibility = View.VISIBLE
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
.setCancelable(false)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun checkAndRequestPermissions() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
val permissionsToRequest = mutableListOf<String>()
|
||||||
|
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
requireActivity(),
|
||||||
|
Manifest.permission.READ_MEDIA_IMAGES
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
permissionsToRequest.add(Manifest.permission.READ_MEDIA_IMAGES)
|
||||||
|
}
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
requireActivity(),
|
||||||
|
Manifest.permission.READ_MEDIA_VIDEO
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
permissionsToRequest.add(Manifest.permission.READ_MEDIA_VIDEO)
|
||||||
|
}
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
requireActivity(),
|
||||||
|
Manifest.permission.READ_MEDIA_AUDIO
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
permissionsToRequest.add(Manifest.permission.READ_MEDIA_AUDIO)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionsToRequest.isNotEmpty()) {
|
||||||
|
requestMultiplePermissionsLauncher.launch(permissionsToRequest.toTypedArray())
|
||||||
|
} else {
|
||||||
|
openAudioPicker()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 请求旧版本的权限
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
requireActivity(),
|
||||||
|
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
} else {
|
||||||
|
openAudioPicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val requestMultiplePermissionsLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
) { permissions ->
|
||||||
|
permissions.forEach { (permission, isGranted) ->
|
||||||
|
if (isGranted) {
|
||||||
|
openAudioPicker()
|
||||||
|
} else {
|
||||||
|
showExplanationDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
186
app/src/main/java/relax/offline/music/http/AppUtil.kt
Normal file
186
app/src/main/java/relax/offline/music/http/AppUtil.kt
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.media.MediaDrm
|
||||||
|
import android.os.Build
|
||||||
|
import android.telephony.TelephonyManager
|
||||||
|
import android.text.TextUtils
|
||||||
|
import relax.offline.music.App
|
||||||
|
import relax.offline.music.sp.AppStore
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
val packageName: String = App.app.packageName
|
||||||
|
|
||||||
|
fun getCountryCode(context: Context): String? {
|
||||||
|
var countryCode = ""
|
||||||
|
try {
|
||||||
|
val telephonyManager =
|
||||||
|
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||||
|
countryCode = if (telephonyManager.phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
|
||||||
|
val c = Class.forName("android.os.SystemProperties")
|
||||||
|
val get = c.getMethod("get", String::class.java)
|
||||||
|
|
||||||
|
// Gives MCC + MNC
|
||||||
|
val homeOperator = get.invoke(c, "ro.cdma.home.operator.numeric") as String
|
||||||
|
homeOperator.substring(0, 3) // the last three digits is MNC
|
||||||
|
} else {
|
||||||
|
val config = context.resources.configuration
|
||||||
|
config.mcc.toString()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return countryCode
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSimCountryIso(context: Context): String? {
|
||||||
|
val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
|
||||||
|
return telephonyManager.simCountryIso
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取国家代码
|
||||||
|
fun getCountry(context: Context): String? {
|
||||||
|
return getSimCountryIso(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun getAppVersionName(context: Context): String? {
|
||||||
|
val packageManager = context.packageManager
|
||||||
|
var packInfo: PackageInfo? = null
|
||||||
|
try {
|
||||||
|
packInfo = packageManager.getPackageInfo(context.packageName, 0)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return packInfo!!.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppVersionCode(context: Context): Long {
|
||||||
|
return try {
|
||||||
|
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.packageManager.getPackageInfo(context.packageName, PackageManager.PackageInfoFlags.of(0))
|
||||||
|
} else {
|
||||||
|
context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
packageInfo.longVersionCode
|
||||||
|
} else {
|
||||||
|
packageInfo.versionCode.toLong()
|
||||||
|
}
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun userID(context: Context): String {
|
||||||
|
var id = ""
|
||||||
|
if (TextUtils.isEmpty(AppStore(context).userID)) {
|
||||||
|
val newID = UUID.randomUUID().toString()
|
||||||
|
AppStore(context).userID = newID
|
||||||
|
} else {
|
||||||
|
id = AppStore(context).userID
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUUId(ctx: Context): String {
|
||||||
|
val cache = AppStore(ctx).appUUID
|
||||||
|
return if (TextUtils.isEmpty(cache)) {
|
||||||
|
val u = generateImmutableUUID(ctx)
|
||||||
|
AppStore(ctx).appUUID = u
|
||||||
|
u
|
||||||
|
} else {
|
||||||
|
cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateImmutableUUID(context: Context): String {
|
||||||
|
/*The Android ID
|
||||||
|
* 通常被认为不可信,因为它有时为null。开发文档中说明了:这个ID会改变如果进行了出厂设置。
|
||||||
|
* 并且,如果某个Andorid手机被Root过的话,这个ID也可以被任意改变。
|
||||||
|
* */
|
||||||
|
val m_szAndroidID = android.provider.Settings.Secure.getString(
|
||||||
|
context.contentResolver,
|
||||||
|
android.provider.Settings.Secure.ANDROID_ID
|
||||||
|
)
|
||||||
|
/*
|
||||||
|
*Android系统2.3版本以上可以获取硬件Serial Number
|
||||||
|
* 优点:非手机设备也可以通过该接口获取ID。
|
||||||
|
* */
|
||||||
|
var serial: String? = null
|
||||||
|
serial = try {
|
||||||
|
//API>=9 使用serial号
|
||||||
|
android.os.Build::class.java.getField("SERIAL").get(null).toString()
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
"serial" // serial需要一个初始化,随便一个初始化
|
||||||
|
}
|
||||||
|
|
||||||
|
//drm id
|
||||||
|
var drmId: String = getDrmDeviceId()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 有一些特殊的情况,一些如平板电脑的设置没有通话功能,或者你不愿加入READ_PHONE_STATE许可。
|
||||||
|
* 而你仍然想获得唯一序列号之类的东西。这时你可以通过取出ROM版本、制造商、CPU型号、以及其他硬件信息来实现这一点。
|
||||||
|
* 这样计算出来的ID不是唯一的(因为如果两个手机应用了同样的硬件以及Rom 镜像)。
|
||||||
|
* 但应当明白的是,出现类似情况的可能性基本可以忽略。要实现这一点,你可以使用Build类:
|
||||||
|
* */
|
||||||
|
val m_szDevIDShort = "666" +
|
||||||
|
Build.HOST.length % 10 +
|
||||||
|
Build.ID.length % 10 +
|
||||||
|
Build.MANUFACTURER.length % 10 +
|
||||||
|
Build.MODEL.length % 10 +
|
||||||
|
Build.PRODUCT.length % 10 +
|
||||||
|
Build.TAGS.length % 10 +
|
||||||
|
Build.TYPE.length % 10 +
|
||||||
|
Build.BOARD.length % 10 +
|
||||||
|
Build.BRAND.length % 10 +
|
||||||
|
Build.CPU_ABI.length % 10 +
|
||||||
|
Build.DEVICE.length % 10 +
|
||||||
|
Build.DISPLAY.length % 10 +
|
||||||
|
Build.USER.length % 10 //13 digits
|
||||||
|
|
||||||
|
val m_szLongID = serial + m_szDevIDShort + m_szAndroidID + drmId
|
||||||
|
//md5加密生成唯一uuid
|
||||||
|
var m: MessageDigest? = null
|
||||||
|
try {
|
||||||
|
m = MessageDigest.getInstance("MD5")
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
m!!.update(m_szLongID.toByteArray(), 0, m_szLongID.length)
|
||||||
|
// get md5 bytes
|
||||||
|
val p_md5Data = m!!.digest()
|
||||||
|
// create a hex string
|
||||||
|
var m_szUniqueID = StringBuilder()
|
||||||
|
for (aP_md5Data in p_md5Data) {
|
||||||
|
val b = 0xFF and aP_md5Data.toInt()
|
||||||
|
// if it is a single digit, make sure it have 0 in front (proper padding)
|
||||||
|
if (b <= 0xF)
|
||||||
|
m_szUniqueID.append("0")
|
||||||
|
// add number to string
|
||||||
|
m_szUniqueID.append(Integer.toHexString(b))
|
||||||
|
} // hex string to uppercase
|
||||||
|
m_szUniqueID = StringBuilder(m_szUniqueID.toString().toLowerCase())
|
||||||
|
return m_szUniqueID.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDrmDeviceId(): String {
|
||||||
|
var id = ""
|
||||||
|
val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
|
||||||
|
val COMMON_PSSH_UUID = UUID(0x1077EFECC0B24D02L, -0x531cc3e1ad1d04b5L)
|
||||||
|
val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL)
|
||||||
|
val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL)
|
||||||
|
try {
|
||||||
|
val bytesId = MediaDrm(WIDEVINE_UUID)
|
||||||
|
.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
|
||||||
|
id = android.util.Base64.encodeToString(bytesId, android.util.Base64.NO_WRAP)
|
||||||
|
} catch (ignore: Exception) {
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
103
app/src/main/java/relax/offline/music/http/BaseApiUtil.kt
Normal file
103
app/src/main/java/relax/offline/music/http/BaseApiUtil.kt
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import relax.offline.music.App
|
||||||
|
import relax.offline.music.util.AesEncryptUtil
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
|
class BaseApiUtil {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val shared: BaseApiUtil by lazy { BaseApiUtil() }
|
||||||
|
const val initialAddress = "/"
|
||||||
|
const val key = "7387m0ax5x0n5tca"
|
||||||
|
const val iv = "D4Q1ymaIHSYifWS9"
|
||||||
|
|
||||||
|
const val getIPInfoUrl = "/app/common/getIPInfo"
|
||||||
|
const val getAppDataURL = "/statistic/appdatacollection/saveAppData"
|
||||||
|
|
||||||
|
const val BaseUrl = "https://api.tikustok.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PostContentMessage.toJson(): String {
|
||||||
|
val gson = Gson()
|
||||||
|
return gson.toJson(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PostContentSecurityBody.toJson(): String {
|
||||||
|
val gson = Gson()
|
||||||
|
return gson.toJson(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initPostBody(
|
||||||
|
method: String,
|
||||||
|
url: String,
|
||||||
|
query: HashMap<String, String>,
|
||||||
|
json: JSONObject? = null,
|
||||||
|
currentBaseUrl: String? = null
|
||||||
|
): String {
|
||||||
|
val randomStr = UUID.randomUUID().toString().replace("-", "")
|
||||||
|
|
||||||
|
val headers: HashMap<String, String> = HashMap()
|
||||||
|
headers["request_userid"] = userID(App.app)
|
||||||
|
headers["request_pkgname"] = packageName
|
||||||
|
headers["base_url"] = currentBaseUrl + ""
|
||||||
|
headers["request_deviceid"] = getUUId(App.app)
|
||||||
|
headers["request_id"] = UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
val requestId = UUID.randomUUID().toString()
|
||||||
|
val timestamp = "TS" + System.currentTimeMillis()
|
||||||
|
|
||||||
|
val trueBody = PostContentMessage(
|
||||||
|
requestId,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
query,
|
||||||
|
headers,
|
||||||
|
json.toString(),
|
||||||
|
timestamp,
|
||||||
|
randomStr
|
||||||
|
)
|
||||||
|
val jsonString = trueBody.toJson()
|
||||||
|
val encryptJsonString = AesEncryptUtil.encrypt(jsonString, key, iv)
|
||||||
|
val postContentSecurityBody = PostContentSecurityBody(method, encryptJsonString)
|
||||||
|
return postContentSecurityBody.toJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun postJson(context: Context, eventName: String?): JSONObject {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
try {
|
||||||
|
jsonObject.put("eventName", eventName)
|
||||||
|
jsonObject.put("timestamp", System.currentTimeMillis())
|
||||||
|
jsonObject.put("uuid", getUUId(context))
|
||||||
|
jsonObject.put("app_version_name", getAppVersionName(context))
|
||||||
|
jsonObject.put("app_version_code", getAppVersionCode(context))
|
||||||
|
jsonObject.put("channel", "google")
|
||||||
|
jsonObject.put("country", getCountry(context))
|
||||||
|
jsonObject.put("device", "android")
|
||||||
|
jsonObject.put("language", Locale.getDefault().language)
|
||||||
|
jsonObject.put("pkgName", context.packageName)
|
||||||
|
jsonObject.put("userId", userID(context))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
//需要添加gaid的json组装
|
||||||
|
fun postJson(context: Context, eventName: String?, gaId: String?): JSONObject {
|
||||||
|
val jsonObject = postJson(context, eventName)
|
||||||
|
try {
|
||||||
|
jsonObject.put("adId", gaId)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.json.JSONObject
|
||||||
|
import relax.offline.music.App
|
||||||
|
import relax.offline.music.sp.AppStore
|
||||||
|
import relax.offline.music.util.AesEncryptUtil
|
||||||
|
import relax.offline.music.util.LogTag
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class CommonIpInfoUtil {
|
||||||
|
|
||||||
|
private val TAG = LogTag.VO_API_LOG
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val shared: CommonIpInfoUtil by lazy { CommonIpInfoUtil() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initIPInfo() {
|
||||||
|
try {
|
||||||
|
MyHttpUtil.mInstance.getIPInfo(
|
||||||
|
object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
try {
|
||||||
|
LogTag.LogD(TAG, "getIPInfo response->${response}")
|
||||||
|
if (response.code == 200) {
|
||||||
|
val responseData = response.body?.string()
|
||||||
|
if (responseData != null) {
|
||||||
|
val jsonObject = JSONObject(responseData)
|
||||||
|
val status = jsonObject.optString("status")
|
||||||
|
if (status == "Success") {
|
||||||
|
val data = jsonObject.optString("data")
|
||||||
|
val decryptData = AesEncryptUtil.desEncrypt(data)
|
||||||
|
val jsonObjectDecryptData = JSONObject(decryptData)
|
||||||
|
LogTag.LogD(TAG, "getIPInfo data->${jsonObjectDecryptData}")
|
||||||
|
val isoCode = jsonObjectDecryptData.optString("isoCode")
|
||||||
|
AppStore(App.app).ipCountryCode = isoCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/src/main/java/relax/offline/music/http/MyCallback.kt
Normal file
10
app/src/main/java/relax/offline/music/http/MyCallback.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
interface MyCallback {
|
||||||
|
fun onFailure(message: String?)
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun onSuccess(data: String)
|
||||||
|
}
|
||||||
90
app/src/main/java/relax/offline/music/http/MyHttpUtil.kt
Normal file
90
app/src/main/java/relax/offline/music/http/MyHttpUtil.kt
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class MyHttpUtil {
|
||||||
|
|
||||||
|
fun isNetworkAvailable(context: Context): Boolean {
|
||||||
|
val connectivityManager =
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
val activeNetworkInfo = connectivityManager.activeNetworkInfo
|
||||||
|
return activeNetworkInfo != null && activeNetworkInfo.isConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mOkHttpClient: OkHttpClient? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val mInstance: MyHttpUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||||
|
MyHttpUtil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val CONNECT_TIMEOUT: Long = 60 //超时时间,秒
|
||||||
|
|
||||||
|
private val READ_TIMEOUT: Long = 60 //读取时间,秒
|
||||||
|
|
||||||
|
private val WRITE_TIMEOUT: Long = 60 //写入时间,秒
|
||||||
|
|
||||||
|
init {
|
||||||
|
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
|
||||||
|
mOkHttpClient = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun postSaveAppData(json: JSONObject, callback: Callback) {
|
||||||
|
val query: HashMap<String, String> = HashMap()
|
||||||
|
val body = BaseApiUtil.shared.initPostBody(
|
||||||
|
"POST",
|
||||||
|
BaseApiUtil.getAppDataURL,
|
||||||
|
query,
|
||||||
|
json
|
||||||
|
)
|
||||||
|
val path = BaseApiUtil.BaseUrl + BaseApiUtil.initialAddress
|
||||||
|
post(path, body, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIPInfo(callback: Callback) {
|
||||||
|
val query: HashMap<String, String> = HashMap()
|
||||||
|
val body = BaseApiUtil.shared.initPostBody(
|
||||||
|
"GET",
|
||||||
|
BaseApiUtil.getIPInfoUrl,
|
||||||
|
query
|
||||||
|
)
|
||||||
|
val path = BaseApiUtil.BaseUrl+ BaseApiUtil.initialAddress
|
||||||
|
post(path, body, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun post(path: String, postBody: String, callback: Callback) {
|
||||||
|
val requestBody = postBody.toRequestBody(mediaType)
|
||||||
|
val request: Request = Request.Builder()
|
||||||
|
.url(path)
|
||||||
|
.post(requestBody)
|
||||||
|
.build()
|
||||||
|
doAsync(request, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步请求
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun doAsync(request: Request, callback: Callback) {
|
||||||
|
//创建请求会话
|
||||||
|
val call = mOkHttpClient!!.newCall(request)
|
||||||
|
//同步执行会话请求
|
||||||
|
call.enqueue(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class PostContentMessage(
|
||||||
|
var requestId: String,//uuid
|
||||||
|
var method: String,// 请求原接口的http方法 GET,POST
|
||||||
|
var url: String, // 原接口url
|
||||||
|
var query: HashMap<String, String>,//原接口传的url参数
|
||||||
|
var headers: HashMap<String, String>,// 原接口传的head
|
||||||
|
var body: String,// 原接口post数据的json
|
||||||
|
var timestamp: String,// 时间戳
|
||||||
|
var randomStr: String//随机字符串和url上的一致
|
||||||
|
) : Serializable
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
data class PostContentSecurityBody(
|
||||||
|
var method: String,
|
||||||
|
var body: String,
|
||||||
|
): Serializable
|
||||||
123
app/src/main/java/relax/offline/music/http/UploadEventName.kt
Normal file
123
app/src/main/java/relax/offline/music/http/UploadEventName.kt
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package relax.offline.music.http
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.json.JSONObject
|
||||||
|
import relax.offline.music.sp.AppStore
|
||||||
|
import relax.offline.music.util.LogTag
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class UploadEventName {
|
||||||
|
private val TAG = LogTag.VO_API_LOG
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val shared: UploadEventName by lazy { UploadEventName() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(context: Context) {
|
||||||
|
if (MyHttpUtil.mInstance.isNetworkAvailable(context)) {
|
||||||
|
initFirstOpen(context)
|
||||||
|
initStartProgram(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initFirstOpen(context: Context) {
|
||||||
|
if (!AppStore(context).firstOpenIsSucceed) {
|
||||||
|
try {
|
||||||
|
MyHttpUtil.mInstance.postSaveAppData(
|
||||||
|
BaseApiUtil.shared.postJson(context, "first_open"),
|
||||||
|
object : okhttp3.Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
try {
|
||||||
|
LogTag.LogD(TAG, "first_open response->${response}")
|
||||||
|
if (response.code == 200) {
|
||||||
|
val responseData = response.body?.string()
|
||||||
|
if (responseData != null) {
|
||||||
|
val jsonObject = JSONObject(responseData)
|
||||||
|
val status = jsonObject.getString("status")
|
||||||
|
LogTag.LogD(TAG, "first_open jsonObject->${jsonObject}")
|
||||||
|
LogTag.LogD(TAG, "first_open status->${status}")
|
||||||
|
if (status.equals("Success")) {
|
||||||
|
AppStore(context).firstOpenIsSucceed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogTag.LogD(TAG, e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initStartProgram(context: Context) {
|
||||||
|
try {
|
||||||
|
MyHttpUtil.mInstance.postSaveAppData(
|
||||||
|
BaseApiUtil.shared.postJson(context, "app_open"),
|
||||||
|
object : okhttp3.Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
try {
|
||||||
|
LogTag.LogD(TAG, "start_program response->${response}")
|
||||||
|
if (response.code == 200) {
|
||||||
|
val responseData = response.body?.string()
|
||||||
|
if (responseData != null) {
|
||||||
|
val jsonObject = JSONObject(responseData)
|
||||||
|
val status = jsonObject.getString("status")
|
||||||
|
LogTag.LogD(TAG, "start_program jsonObject->${jsonObject}")
|
||||||
|
LogTag.LogD(TAG, "start_program status->${status}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogTag.LogE(TAG, e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initAppEventData(json: JSONObject) {
|
||||||
|
try {
|
||||||
|
MyHttpUtil.mInstance.postSaveAppData(
|
||||||
|
json,
|
||||||
|
object : okhttp3.Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
LogTag.LogD(TAG, "AppEventData onFailure->${e}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
try {
|
||||||
|
LogTag.LogD(TAG, "AppEventData response->${response}")
|
||||||
|
if (response.code == 200) {
|
||||||
|
val responseData = response.body?.string()
|
||||||
|
if (responseData != null) {
|
||||||
|
val jsonObject = JSONObject(responseData)
|
||||||
|
val status = jsonObject.getString("status")
|
||||||
|
LogTag.LogD(TAG, "AppEventData jsonObject->${jsonObject}")
|
||||||
|
LogTag.LogD(TAG, "AppEventData status->${status}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LogTag.LogE(TAG, e.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -98,7 +98,7 @@ class PlaybackService : MediaSessionService(), Player.Listener {
|
|||||||
// setSmallIcon(R.mipmap.app_logo_img)
|
// setSmallIcon(R.mipmap.app_logo_img)
|
||||||
// }
|
// }
|
||||||
setMediaNotificationProvider(DefaultMediaNotificationProvider(this).apply {
|
setMediaNotificationProvider(DefaultMediaNotificationProvider(this).apply {
|
||||||
setSmallIcon(R.mipmap.app_logo)
|
setSmallIcon(R.mipmap.app_logo_no_bg)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,10 +27,33 @@ class AppStore(context: Context) {
|
|||||||
defaultValue = PlayMode.LIST_LOOP.value
|
defaultValue = PlayMode.LIST_LOOP.value
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var userID: String by store.string(
|
||||||
|
key = APP_USERID,
|
||||||
|
defaultValue = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
var appUUID : String by store.string(
|
||||||
|
key = APP_UUID,
|
||||||
|
defaultValue = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
var ipCountryCode: String by store.string(
|
||||||
|
key = IP_COUNTRY_CODE,
|
||||||
|
defaultValue = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
var firstOpenIsSucceed : Boolean by store.boolean(
|
||||||
|
key = FIRST_OPEN_IS_SUCCEED,
|
||||||
|
defaultValue = false
|
||||||
|
)
|
||||||
companion object {
|
companion object {
|
||||||
private const val FILE_NAME = "music_oo_app"
|
private const val FILE_NAME = "music_oo_app"
|
||||||
const val SEARCH_HISTORY = "search_history"
|
const val SEARCH_HISTORY = "search_history"
|
||||||
const val MY_VISITOR_DATA = "my_visitor_data"
|
const val MY_VISITOR_DATA = "my_visitor_data"
|
||||||
const val PLAY_MUSIC_MODE = "play_music_mode"
|
const val PLAY_MUSIC_MODE = "play_music_mode"
|
||||||
|
const val APP_USERID = "app_userid"
|
||||||
|
const val APP_UUID = "app_uuid"
|
||||||
|
const val IP_COUNTRY_CODE = "ip_country_code"
|
||||||
|
const val FIRST_OPEN_IS_SUCCEED = "first_open_is_succeed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package relax.offline.music.util;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import relax.offline.music.http.BaseApiUtil;
|
||||||
|
|
||||||
|
public class AesEncryptUtil {
|
||||||
|
|
||||||
|
private static final String AES_MODE = "AES/CBC/NOPadding";
|
||||||
|
|
||||||
|
public static String encrypt(String data, String key, String iv) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(AES_MODE);
|
||||||
|
int blockSize = cipher.getBlockSize();
|
||||||
|
byte[] dataBytes = data.getBytes();
|
||||||
|
int plaintextLength = dataBytes.length;
|
||||||
|
if (plaintextLength % blockSize != 0) {
|
||||||
|
plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
|
||||||
|
}
|
||||||
|
byte[] plaintext = new byte[plaintextLength];
|
||||||
|
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
|
||||||
|
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
||||||
|
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
|
||||||
|
byte[] encrypted = cipher.doFinal(plaintext);
|
||||||
|
return Base64.encodeBytes(encrypted);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String desEncrypt(String data) {
|
||||||
|
return desEncrypt(data, BaseApiUtil.key, BaseApiUtil.iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解密方法
|
||||||
|
*
|
||||||
|
* @param data 要解密的数据
|
||||||
|
* @param key 解密key
|
||||||
|
* @param iv 解密iv
|
||||||
|
* @return 解密的结果
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static String desEncrypt(String data, String key, String iv) {
|
||||||
|
try {
|
||||||
|
byte[] encrypted1 = Base64.decode(data);
|
||||||
|
Cipher cipher = Cipher.getInstance(AES_MODE);
|
||||||
|
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
|
||||||
|
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
|
||||||
|
byte[] original = cipher.doFinal(encrypted1);
|
||||||
|
String originalString = new String(original);
|
||||||
|
return originalString.trim();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1426
app/src/main/java/relax/offline/music/util/Base64.java
Normal file
1426
app/src/main/java/relax/offline/music/util/Base64.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -171,7 +171,7 @@ object DownloadUtil {
|
|||||||
return isExist
|
return isExist
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentIdDownloadState(id: String): Int {
|
fun getCurrentIdDownload(id: String): Download? {
|
||||||
if (downloadManager != null) {
|
if (downloadManager != null) {
|
||||||
val downloadIndex = downloadManager!!.downloadIndex
|
val downloadIndex = downloadManager!!.downloadIndex
|
||||||
downloadIndex.getDownloads()
|
downloadIndex.getDownloads()
|
||||||
@ -179,12 +179,12 @@ object DownloadUtil {
|
|||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val download = cursor.download
|
val download = cursor.download
|
||||||
if (download.request.id == id) {
|
if (download.request.id == id) {
|
||||||
return download.state
|
return download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadCount(): Int {
|
fun getDownloadCount(): Int {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package relax.offline.music.util
|
package relax.offline.music.util
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import relax.offline.music.BuildConfig
|
||||||
|
|
||||||
object LogTag {
|
object LogTag {
|
||||||
const val VO_ACT_LOG = "vo-act—log"
|
const val VO_ACT_LOG = "vo-act—log"
|
||||||
@ -11,14 +12,14 @@ object LogTag {
|
|||||||
const val VO_TEST_ONLY = "vo-only—log"
|
const val VO_TEST_ONLY = "vo-only—log"
|
||||||
|
|
||||||
fun LogD(tag: String, message: String) {
|
fun LogD(tag: String, message: String) {
|
||||||
Log.d(tag, message)
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d(tag, message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LogE(tag: String, message: String) {
|
fun LogE(tag: String, message: String) {
|
||||||
Log.e(tag, message)
|
if (BuildConfig.DEBUG) {
|
||||||
}
|
Log.e(tag, message)
|
||||||
|
}
|
||||||
fun LogI(tag: String, message: String) {
|
|
||||||
Log.i(tag, message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,6 +57,16 @@ class SearchResultOptimalView(context: Context, data: Innertube.SearchDataPage)
|
|||||||
)
|
)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"MUSIC_PAGE_TYPE_PLAYLIST" -> {
|
||||||
|
val intent = Intent(context, MoListDetailsActivity::class.java)
|
||||||
|
intent.putExtra(
|
||||||
|
MoListDetailsActivity.PLAY_LIST_PAGE_BROWSE_ID,
|
||||||
|
optimalBean.browseId
|
||||||
|
)
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val intent = Intent(context, MoSingerDetailsActivity::class.java)
|
val intent = Intent(context, MoSingerDetailsActivity::class.java)
|
||||||
intent.putExtra(
|
intent.putExtra(
|
||||||
|
|||||||
@ -78,6 +78,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
android:visibility="visible">
|
android:visibility="visible">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|||||||
@ -190,6 +190,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="18dp"
|
android:layout_marginStart="18dp"
|
||||||
android:layout_marginEnd="18dp"
|
android:layout_marginEnd="18dp"
|
||||||
|
android:visibility="gone"
|
||||||
android:fontFamily="@font/medium_font"
|
android:fontFamily="@font/medium_font"
|
||||||
android:text="@string/new_playlist"
|
android:text="@string/new_playlist"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
|
|||||||
BIN
app/src/main/res/mipmap-xxhdpi/app_logo_no_bg.jpg
Normal file
BIN
app/src/main/res/mipmap-xxhdpi/app_logo_no_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@ -36,4 +36,6 @@
|
|||||||
<string name="new_playlist">New playlist</string>
|
<string name="new_playlist">New playlist</string>
|
||||||
<string name="liked_songs_no_data_prompt">You haven\'t liked any songs yet.</string>
|
<string name="liked_songs_no_data_prompt">You haven\'t liked any songs yet.</string>
|
||||||
<string name="offline_songs_no_data_prompt">You haven\'t saved any songs for offline listening yet.</string>
|
<string name="offline_songs_no_data_prompt">You haven\'t saved any songs for offline listening yet.</string>
|
||||||
|
<string name="no_data_prompt_dialog_content">It looks like there\'s no data. Please add some and try again.</string>
|
||||||
|
<string name="prompt">Prompt</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Reference in New Issue
Block a user