diff --git a/README.md b/README.md index b5b14fc6..93060b9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # AndroidEasterEggs -[![wakatime](https://wakatime.com/badge/user/5dcaf7c9-f166-4fc1-b818-5a6761bb52b6.svg)](https://wakatime.com/@5dcaf7c9-f166-4fc1-b818-5a6761bb52b6) - 整理了Android系统各正式版内置彩蛋 ![icon](./app/src/main/ic_launcher-playstore.png) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dfdb2650..a958bfb2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,8 +19,8 @@ android { applicationId = "com.dede.android_eggs" minSdk = Versions.MIN_SDK targetSdk = Versions.TARGET_SDK - versionCode = 11 - versionName = "1.5.1" + versionCode = 12 + versionName = "1.6.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations.addAll(listOf("zh", "en")) @@ -75,7 +75,6 @@ dependencies { implementation(deps.androidx.preference.ktx) implementation(deps.androidx.constraintlayout) implementation(deps.androidx.browser) - implementation(deps.google.browserhelper) implementation(deps.google.material) implementation(deps.free.reflection) debugImplementation(deps.leakcanary) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e8300540..bc6dca63 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ tools:replace="android:icon,android:label"> diff --git a/app/src/main/java/com/dede/android_eggs/ChromeTabPreference.kt b/app/src/main/java/com/dede/android_eggs/ChromeTabPreference.kt index 1ae8f1fd..469544f7 100644 --- a/app/src/main/java/com/dede/android_eggs/ChromeTabPreference.kt +++ b/app/src/main/java/com/dede/android_eggs/ChromeTabPreference.kt @@ -5,9 +5,7 @@ import android.content.Intent import android.net.Uri import android.text.TextUtils import android.util.AttributeSet -import androidx.browser.trusted.TrustedWebActivityIntentBuilder import androidx.preference.Preference -import com.google.androidbrowserhelper.trusted.TwaLauncher /** * Chrome Custom Tabs Preference @@ -18,7 +16,11 @@ import com.google.androidbrowserhelper.trusted.TwaLauncher open class ChromeTabPreference : Preference, Preference.OnPreferenceClickListener { private var uri: Uri? = null - private val useTwa: Boolean + private val useChromeTab: Boolean + + init { + ChromeTabsBrowser.warmup(context) + } constructor(context: Context) : this(context, null) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { @@ -27,7 +29,7 @@ open class ChromeTabPreference : Preference, Preference.OnPreferenceClickListene if (!TextUtils.isEmpty(uriString)) { uri = Uri.parse(uriString) } - useTwa = arrays.getBoolean(R.styleable.ChromeTabPreference_useTwa, true) + useChromeTab = arrays.getBoolean(R.styleable.ChromeTabPreference_useChromeTab, true) arrays.recycle() if (uri != null) { @@ -42,8 +44,8 @@ open class ChromeTabPreference : Preference, Preference.OnPreferenceClickListene override fun onPreferenceClick(preference: Preference?): Boolean { val uri = this.uri if (uri != null) { - if (useTwa) { - openTwaWeb(uri) + if (useChromeTab) { + openChromeTabs(uri) } else { openBrowser(uri) } @@ -58,10 +60,8 @@ open class ChromeTabPreference : Preference, Preference.OnPreferenceClickListene context.startActivity(intent) } - private fun openTwaWeb(uri: Uri) { - val twaLauncher = TwaLauncher(context) - val builder = TrustedWebActivityIntentBuilder(uri) - twaLauncher.launch(builder, null, null, null) + private fun openChromeTabs(uri: Uri) { + ChromeTabsBrowser.launchUrl(context, uri) } } \ No newline at end of file diff --git a/app/src/main/java/com/dede/android_eggs/ChromeTabsBrowser.kt b/app/src/main/java/com/dede/android_eggs/ChromeTabsBrowser.kt new file mode 100644 index 00000000..6fe359db --- /dev/null +++ b/app/src/main/java/com/dede/android_eggs/ChromeTabsBrowser.kt @@ -0,0 +1,83 @@ +package com.dede.android_eggs + +import android.content.ComponentName +import android.content.Context +import android.graphics.Color +import android.net.Uri +import androidx.appcompat.app.AppCompatDelegate +import androidx.browser.customtabs.* +import com.google.android.material.color.MaterialColors + +/** + * CustomTabs Help + * + * @author hsh + * @since 2021/11/19 2:14 下午 + */ +object ChromeTabsBrowser { + + // Package name for the Chrome channel the client wants to connect to. This depends on the channel name. + // Stable = com.android.chrome + // Beta = com.chrome.beta + // Dev = com.chrome.dev + private const val CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome" + private const val CUSTOM_SESSION_ID = 10 + + private var mayLaunchUrl: Uri? = null + private val customTabsCallback = CustomTabsCallback() + private var customTabsSession: CustomTabsSession? = null + + private val customTabsServiceConnection = object : CustomTabsServiceConnection() { + override fun onServiceDisconnected(name: ComponentName?) { + customTabsSession = null + } + + override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { + val result = client.warmup(0) + if (result) { + val session = client.newSession(customTabsCallback, CUSTOM_SESSION_ID) + if (session != null) { + customTabsSession = session + if (mayLaunchUrl != null) { + session.mayLaunchUrl(mayLaunchUrl, null, null) + } + } + } + } + } + + /** + * 预热并预加载 + */ + fun warmup(context: Context, mayLaunchUrl: Uri? = null) { + if (customTabsSession != null) return + this.mayLaunchUrl = mayLaunchUrl + val appContext = context.applicationContext + CustomTabsClient.bindCustomTabsService( + appContext, + CUSTOM_TAB_PACKAGE_NAME, + customTabsServiceConnection + ) + } + + fun launchUrl(context: Context, uri: Uri) { + val colorScheme = + if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) + CustomTabsIntent.COLOR_SCHEME_DARK else CustomTabsIntent.COLOR_SCHEME_LIGHT + + val color = MaterialColors.getColor(context, android.R.attr.colorPrimary, Color.WHITE) + val params = CustomTabColorSchemeParams.Builder() + .setToolbarColor(color) + .build() + + val builder = CustomTabsIntent.Builder() + .setColorScheme(colorScheme) + .setDefaultColorSchemeParams(params) + val session = customTabsSession + if (session != null) { + builder.setSession(session) + } + val customTabsIntent = builder.build() + customTabsIntent.launchUrl(context, uri) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/dede/android_eggs/EasterEggsActivity.kt b/app/src/main/java/com/dede/android_eggs/EasterEggsActivity.kt index 716fc0e6..61bbe88f 100644 --- a/app/src/main/java/com/dede/android_eggs/EasterEggsActivity.kt +++ b/app/src/main/java/com/dede/android_eggs/EasterEggsActivity.kt @@ -63,4 +63,9 @@ class EasterEggsActivity : AppCompatActivity(), Runnable { .start() } + override fun onDestroy() { + binding.content.removeCallbacks(this) + super.onDestroy() + } + } diff --git a/app/src/main/java/com/dede/android_eggs/EggPreference.kt b/app/src/main/java/com/dede/android_eggs/EggPreference.kt index ab1904f0..60271a61 100644 --- a/app/src/main/java/com/dede/android_eggs/EggPreference.kt +++ b/app/src/main/java/com/dede/android_eggs/EggPreference.kt @@ -90,14 +90,6 @@ class EggPreference : Preference { super.performClick() } - private class OvalOutlineProvider : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - outline.setOval(view.paddingLeft, view.paddingTop, - view.width - view.paddingRight, - view.height - view.paddingBottom) - } - } - private class CornersOutlineProvider(val radius: Float) : ViewOutlineProvider() { override fun getOutline(view: View, outline: Outline) { outline.setRoundRect(view.paddingLeft, view.paddingTop, diff --git a/app/src/main/java/com/dede/android_eggs/OvalOutlineProvider.kt b/app/src/main/java/com/dede/android_eggs/OvalOutlineProvider.kt new file mode 100644 index 00000000..d75762a3 --- /dev/null +++ b/app/src/main/java/com/dede/android_eggs/OvalOutlineProvider.kt @@ -0,0 +1,20 @@ +package com.dede.android_eggs + +import android.graphics.Outline +import android.view.View +import android.view.ViewOutlineProvider + +/** + * View 圆角 + * @author hsh + * @since 2021/10/21 5:03 下午 + */ +class OvalOutlineProvider : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setOval( + view.paddingLeft, view.paddingTop, + view.width - view.paddingRight, + view.height - view.paddingBottom + ) + } +} \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index ec342ceb..ea392930 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -3,12 +3,12 @@ \ No newline at end of file diff --git a/app/src/main/res/values/attrs_preference.xml b/app/src/main/res/values/attrs_preference.xml index 4762c525..5000db91 100644 --- a/app/src/main/res/values/attrs_preference.xml +++ b/app/src/main/res/values/attrs_preference.xml @@ -2,7 +2,7 @@ - + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f13f6d25..06487528 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -5,6 +5,4 @@ #7b1fa2 #80cbc4 #00796b - #000000 - #FFFFFF \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index eb8a41ff..9d6e441f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -11,11 +11,11 @@ @color/purple_500 @color/purple_500 - @color/white + @android:color/white @color/teal_200 @color/teal_700 - @color/black + @android:color/black \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 0d0095db..4cccb8b9 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -128,8 +128,7 @@ + app:title="@string/title_version" /> diff --git a/basic/src/main/AndroidManifest.xml b/basic/src/main/AndroidManifest.xml index 676c6ace..b793132c 100644 --- a/basic/src/main/AndroidManifest.xml +++ b/basic/src/main/AndroidManifest.xml @@ -2,17 +2,14 @@ + + + - - - - \ No newline at end of file diff --git a/basic/src/main/java/com/dede/basic/ImageExt.kt b/basic/src/main/java/com/dede/basic/ImageExt.kt new file mode 100644 index 00000000..771c121d --- /dev/null +++ b/basic/src/main/java/com/dede/basic/ImageExt.kt @@ -0,0 +1,230 @@ +@file:JvmName("ImageExt") + +package com.dede.basic + +import android.content.* +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import android.util.Log +import java.io.File +import java.io.FileNotFoundException +import java.io.InputStream +import java.io.OutputStream + + +private const val TAG = "ImageExt" + +const val MIME_PNG = "image/png" +const val MIME_JPG = "image/jpg" +private val ALBUM_DIR = Environment.DIRECTORY_PICTURES + +private class OutputFileTaker(var file: File? = null) + +/** + * 复制图片文件到相册的Pictures文件夹 + * + * @param context 上下文 + * @param fileName 文件名。 需要携带后缀 + * @param relativePath 相对于Pictures的路径 + */ +fun File.copyToAlbum(context: Context, fileName: String, relativePath: String?): Uri? { + if (!this.canRead() || !this.exists()) { + Log.w(TAG, "check: read file error: $this") + return null + } + return this.inputStream().use { + it.saveToAlbum(context, fileName, relativePath) + } +} + +/** + * 保存图片Stream到相册的Pictures文件夹 + * + * @param context 上下文 + * @param fileName 文件名。 需要携带后缀 + * @param relativePath 相对于Pictures的路径 + */ +fun InputStream.saveToAlbum(context: Context, fileName: String, relativePath: String?): Uri? { + val resolver = context.contentResolver + val outputFile = OutputFileTaker() + val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile) + if (imageUri == null) { + Log.w(TAG, "insert: error: uri == null") + return null + } + + (imageUri.outputStream(resolver) ?: return null).use { output -> + this.use { input -> + input.copyTo(output) + imageUri.finishPending(context, resolver, outputFile.file) + } + } + return imageUri +} + +/** + * 保存Bitmap到相册的Pictures文件夹 + * + * https://developer.android.google.cn/training/data-storage/shared/media + * + * @param context 上下文 + * @param fileName 文件名。 需要携带后缀 + * @param relativePath 相对于Pictures的路径 + * @param quality 质量 + */ +fun Bitmap.saveToAlbum( + context: Context, + fileName: String, + relativePath: String? = null, + quality: Int = 75 +): Uri? { + // 插入图片信息 + val resolver = context.contentResolver + val outputFile = OutputFileTaker() + val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile) + if (imageUri == null) { + Log.w(TAG, "insert: error: uri == null") + return null + } + + // 保存图片 + (imageUri.outputStream(resolver) ?: return null).use { + val format = + if (fileName.endsWith(".png")) Bitmap.CompressFormat.PNG else Bitmap.CompressFormat.JPEG + this@saveToAlbum.compress(format, quality, it) + imageUri.finishPending(context, resolver, outputFile.file) + } + return imageUri +} + +private fun Uri.outputStream(resolver: ContentResolver): OutputStream? { + return try { + resolver.openOutputStream(this) + } catch (e: FileNotFoundException) { + Log.e(TAG, "save: open stream error: $e") + null + } +} + +private fun Uri.finishPending( + context: Context, + resolver: ContentResolver, + outputFile: File? +) { + val imageValues = ContentValues() + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if (outputFile != null) { + imageValues.put(MediaStore.Images.Media.SIZE, outputFile.length()) + } + resolver.update(this, imageValues, null, null) + // 通知媒体库更新 + val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, this) + context.sendBroadcast(intent) + } else { + // Android Q添加了IS_PENDING状态,为0时其他应用才可见 + imageValues.put(MediaStore.Images.Media.IS_PENDING, 0) + resolver.update(this, imageValues, null, null) + } +} + +/** + * 插入图片到媒体库 + */ +private fun ContentResolver.insertMediaImage( + fileName: String, + relativePath: String?, + outputFileTaker: OutputFileTaker? = null +): Uri? { + // 图片信息 + val imageValues = ContentValues().apply { + val mimeType = if (fileName.endsWith(".png")) MIME_PNG else MIME_JPG + put(MediaStore.Images.Media.MIME_TYPE, mimeType) + val date = System.currentTimeMillis() / 1000 + put(MediaStore.Images.Media.DATE_ADDED, date) + put(MediaStore.Images.Media.DATE_MODIFIED, date) + } + // 保存的位置 + val collection: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val path = if (relativePath != null) "${ALBUM_DIR}/${relativePath}" else ALBUM_DIR + imageValues.apply { + put(MediaStore.Images.Media.DISPLAY_NAME, fileName) + put(MediaStore.Images.Media.RELATIVE_PATH, path) + put(MediaStore.Images.Media.IS_PENDING, 1) + } + collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + // 高版本不用查重直接插入,会自动重命名 + } else { + // 老版本 + val pictures = Environment.getExternalStoragePublicDirectory(ALBUM_DIR) + val saveDir = if (relativePath != null) File(pictures, relativePath) else pictures + + if (!saveDir.exists() && !saveDir.mkdirs()) { + Log.e(TAG, "save: error: can't create Pictures directory") + return null + } + + // 文件路径查重,重复的话在文件名后拼接数字 + var imageFile = File(saveDir, fileName) + val fileNameWithoutExtension = imageFile.nameWithoutExtension + val fileExtension = imageFile.extension + + var queryUri = this.queryMediaImage28(imageFile.absolutePath) + var suffix = 1 + while (queryUri != null) { + val newName = fileNameWithoutExtension + "(${suffix++})." + fileExtension + imageFile = File(saveDir, newName) + queryUri = this.queryMediaImage28(imageFile.absolutePath) + } + + imageValues.apply { + put(MediaStore.Images.Media.DISPLAY_NAME, imageFile.name) + // 保存路径 + val imagePath = imageFile.absolutePath + Log.v(TAG, "save file: $imagePath") + put(MediaStore.Images.Media.DATA, imagePath) + } + outputFileTaker?.file = imageFile// 回传文件路径,用于设置文件大小 + collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + // 插入图片信息 + return this.insert(collection, imageValues) +} + +/** + * Android Q以下版本,查询媒体库中当前路径是否存在 + * @return Uri 返回null时说明不存在,可以进行图片插入逻辑 + */ +private fun ContentResolver.queryMediaImage28(imagePath: String): Uri? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return null + + val imageFile = File(imagePath) + if (imageFile.canRead() && imageFile.exists()) { + Log.v(TAG, "query: path: $imagePath exists") + // 文件已存在,返回一个file://xxx的uri + return Uri.fromFile(imageFile) + } + // 保存的位置 + val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI + + // 查询是否已经存在相同图片 + val query = this.query( + collection, + arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA), + "${MediaStore.Images.Media.DATA} == ?", + arrayOf(imagePath), null + ) + query?.use { + while (it.moveToNext()) { + val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID) + val id = it.getLong(idColumn) + val existsUri = ContentUris.withAppendedId(collection, id) + Log.v(TAG, "query: path: $imagePath exists uri: $existsUri") + return existsUri + } + } + return null +} diff --git a/basic/src/main/java/com/dede/basic/ShareCatUtils.kt b/basic/src/main/java/com/dede/basic/ShareCatUtils.kt index c9b33aac..10d09f36 100644 --- a/basic/src/main/java/com/dede/basic/ShareCatUtils.kt +++ b/basic/src/main/java/com/dede/basic/ShareCatUtils.kt @@ -3,22 +3,15 @@ package com.dede.basic import android.app.Activity +import android.content.Context import android.content.Intent import android.graphics.Bitmap -import android.media.MediaScannerConnection import android.net.Uri -import android.os.Build -import android.os.Environment import android.util.Log -import androidx.core.content.FileProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.io.OutputStream /** @@ -28,53 +21,29 @@ import java.io.OutputStream */ object ShareCatUtils { - @JvmStatic - fun share(activity: Activity, bitmap: Bitmap, catName: String) { + private const val CATS_DIR = "Cats" - fun saveCat(bitmap: Bitmap, catName: String): File? { - val dir: File = File( - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), - "Cats" - ) - if (!dir.exists() && !dir.mkdirs()) { - Log.e("NekoLand", "save: error: can't create Pictures directory") - return null - } - try { - val png = File(dir, catName.replace("[/ #:]+".toRegex(), "_") + ".png") - val os: OutputStream = FileOutputStream(png) - bitmap.compress(Bitmap.CompressFormat.PNG, 0, os) - os.close() - return png - } catch (e: IOException) { - Log.e("NekoLand", "save: error: $e") - } - return null + private suspend fun saveCat(context: Context, bitmap: Bitmap, catName: String): Uri? = + withContext(Dispatchers.IO) { + bitmap.saveToAlbum(context, catName.toFileName(), CATS_DIR, 0) } + private fun String.toFileName(): String { + return this.replace("[/ #:]+".toRegex(), "_") + ".png" + } + + @JvmStatic + fun share(activity: Activity, bitmap: Bitmap, catName: String) { + GlobalScope.launch { - val png = withContext(Dispatchers.IO) { saveCat(bitmap, catName) } ?: return@launch - MediaScannerConnection.scanFile( - activity, - arrayOf(png.toString()), - arrayOf("image/png"), - null - ) - Log.v("Neko", "cat file: $png") - val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - FileProvider.getUriForFile( - activity, - activity.packageName + ".fileprovider", png - ) - } else { - Uri.fromFile(png) - } + val uri = saveCat(activity, bitmap, catName) ?: return@launch Log.v("Neko", "cat uri: $uri") + val intent = Intent(Intent.ACTION_SEND) intent.putExtra(Intent.EXTRA_STREAM, uri) intent.putExtra(Intent.EXTRA_SUBJECT, catName) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.type = "image/png" + intent.type = MIME_PNG activity.startActivity( Intent.createChooser(intent, null) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) diff --git a/basic/src/main/res/xml/filepaths.xml b/basic/src/main/res/xml/filepaths.xml deleted file mode 100644 index 7fbd3702..00000000 --- a/basic/src/main/res/xml/filepaths.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - \ No newline at end of file diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index f7abd9f3..0ebdb02b 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -1,8 +1,8 @@ object Versions { - const val COMPILE_SDK = 30 - const val BUILD_TOOLS = "30.0.3" - const val TARGET_SDK = 30 + const val COMPILE_SDK = 31 + const val BUILD_TOOLS = "31.0.0" + const val TARGET_SDK = 31 const val MIN_SDK = 21 } \ No newline at end of file diff --git a/eggs/KitKat/src/main/java/com/android_k/egg/DessertCase.java b/eggs/KitKat/src/main/java/com/android_k/egg/DessertCase.java index dbe6ce3a..249672c1 100644 --- a/eggs/KitKat/src/main/java/com/android_k/egg/DessertCase.java +++ b/eggs/KitKat/src/main/java/com/android_k/egg/DessertCase.java @@ -46,18 +46,22 @@ public void onStart() { setContentView(container); } + private Runnable action; + @Override public void onResume() { super.onResume(); - mView.postDelayed(new Runnable() { + action = new Runnable() { public void run() { mView.start(); } - }, 1000); + }; + mView.postDelayed(action, 1000); } @Override public void onPause() { + mView.removeCallbacks(action); super.onPause(); mView.stop(); } diff --git a/eggs/KitKat/src/main/java/com/android_k/egg/DessertCaseDream.java b/eggs/KitKat/src/main/java/com/android_k/egg/DessertCaseDream.java index 73c0fefd..0a7e7b8f 100644 --- a/eggs/KitKat/src/main/java/com/android_k/egg/DessertCaseDream.java +++ b/eggs/KitKat/src/main/java/com/android_k/egg/DessertCaseDream.java @@ -36,18 +36,22 @@ public void onAttachedToWindow() { setContentView(mContainer); } + private Runnable action; + @Override public void onDreamingStarted() { super.onDreamingStarted(); - mView.postDelayed(new Runnable() { + action = new Runnable() { public void run() { mView.start(); } - }, 1000); + }; + mView.postDelayed(action, 1000); } @Override public void onDreamingStopped() { + mView.removeCallbacks(action); super.onDreamingStopped(); mView.stop(); } diff --git a/eggs/Lollipop/src/main/AndroidManifest.xml b/eggs/Lollipop/src/main/AndroidManifest.xml index 691b0a60..e2fe27d4 100644 --- a/eggs/Lollipop/src/main/AndroidManifest.xml +++ b/eggs/Lollipop/src/main/AndroidManifest.xml @@ -20,7 +20,7 @@ android:label="@string/lland" android:launchMode="singleInstance" android:screenOrientation="locked" - android:theme="@android:style/Theme.Material.Light.NoActionBar" /> + android:theme="@android:style/Theme.Material.NoActionBar" /> \ No newline at end of file diff --git a/eggs/Lollipop/src/main/java/com/android_l/egg/LLand.java b/eggs/Lollipop/src/main/java/com/android_l/egg/LLand.java index 353a48b7..ab63cec9 100644 --- a/eggs/Lollipop/src/main/java/com/android_l/egg/LLand.java +++ b/eggs/Lollipop/src/main/java/com/android_l/egg/LLand.java @@ -185,6 +185,7 @@ public float getLastTimeStep() { public void setScoreField(TextView tv) { mScoreField = tv; + setScore(mScore); if (tv != null) { tv.setTranslationZ(PARAMS.HUD_Z); if (!(mAnimating && mPlaying)) { @@ -634,13 +635,15 @@ private void unpoke() { mDroid.unboost(); } + final Paint pt = new Paint(Paint.ANTI_ALIAS_FLAG); + @Override public void onDraw(Canvas c) { super.onDraw(c); if (!DEBUG_DRAW) return; - final Paint pt = new Paint(Paint.ANTI_ALIAS_FLAG); +// final Paint pt = new Paint(Paint.ANTI_ALIAS_FLAG); pt.setColor(0xFFFFFFFF); final int L = mDroid.corners.length; final int N = L / 2; diff --git a/eggs/Marshmallow/src/main/java/com/android_m/egg/MLand.java b/eggs/Marshmallow/src/main/java/com/android_m/egg/MLand.java index 6f23d398..1bfe2574 100644 --- a/eggs/Marshmallow/src/main/java/com/android_m/egg/MLand.java +++ b/eggs/Marshmallow/src/main/java/com/android_m/egg/MLand.java @@ -303,7 +303,7 @@ private static float luma(int bgcolor) { } public Player getPlayer(int i) { - return i < mPlayers.size() ? mPlayers.get(i) : null; + return i > -1 && i < mPlayers.size() ? mPlayers.get(i) : null; } private int addPlayerInternal(Player p) { diff --git a/eggs/Nougat/AndroidManifest.xml b/eggs/Nougat/AndroidManifest.xml index e7f05505..3f8cfacf 100644 --- a/eggs/Nougat/AndroidManifest.xml +++ b/eggs/Nougat/AndroidManifest.xml @@ -17,7 +17,12 @@ Copyright (C) 2016 The Android Open Source Project package="com.android_n.egg"> - + + @@ -64,6 +70,7 @@ Copyright (C) 2016 The Android Open Source Project diff --git a/eggs/Nougat/src/com/android_n/egg/neko/NekoLand.java b/eggs/Nougat/src/com/android_n/egg/neko/NekoLand.java index 4f3c868c..c360c443 100644 --- a/eggs/Nougat/src/com/android_n/egg/neko/NekoLand.java +++ b/eggs/Nougat/src/com/android_n/egg/neko/NekoLand.java @@ -246,11 +246,17 @@ public void onClick(DialogInterface dialog, int which) { public void onClick(View v) { setContextGroupVisible(holder, false); Cat cat = mCats[holder.getAdapterPosition()]; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + shareCat(cat); + return; + } if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_GRANTED || + checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { mPendingShareCat = cat; - requestPermissions( - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}, STORAGE_PERM_REQUEST); return; } diff --git a/eggs/Oreo/AndroidManifest.xml b/eggs/Oreo/AndroidManifest.xml index 89e60b83..ec51a62e 100644 --- a/eggs/Oreo/AndroidManifest.xml +++ b/eggs/Oreo/AndroidManifest.xml @@ -28,6 +28,7 @@ Copyright (C) 2016 The Android Open Source Project diff --git a/eggs/Pie/AndroidManifest.xml b/eggs/Pie/AndroidManifest.xml index e20da55a..7f7ec94f 100644 --- a/eggs/Pie/AndroidManifest.xml +++ b/eggs/Pie/AndroidManifest.xml @@ -28,6 +28,7 @@ diff --git a/eggs/Q/AndroidManifest.xml b/eggs/Q/AndroidManifest.xml index c5448949..2f6fab9f 100644 --- a/eggs/Q/AndroidManifest.xml +++ b/eggs/Q/AndroidManifest.xml @@ -14,6 +14,7 @@ diff --git a/eggs/R/AndroidManifest.xml b/eggs/R/AndroidManifest.xml index bb362b28..86654969 100644 --- a/eggs/R/AndroidManifest.xml +++ b/eggs/R/AndroidManifest.xml @@ -2,7 +2,7 @@ - + diff --git a/eggs/R/res/values/strings.xml b/eggs/R/res/values/strings.xml index 5b678a08..226521c6 100644 --- a/eggs/R/res/values/strings.xml +++ b/eggs/R/res/values/strings.xml @@ -16,5 +16,5 @@ Copyright (C) 2018 The Android Open Source Project Android R Easter Egg - Cat Controls + Cat Controls(Collection) diff --git a/eggs/R/src/com/android_r/egg/neko/Cat.java b/eggs/R/src/com/android_r/egg/neko/Cat.java index a5c99bbd..b17f4958 100644 --- a/eggs/R/src/com/android_r/egg/neko/Cat.java +++ b/eggs/R/src/com/android_r/egg/neko/Cat.java @@ -31,6 +31,7 @@ import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.os.Build; import android.os.Bundle; import androidx.annotation.RequiresApi; @@ -39,6 +40,7 @@ import java.io.ByteArrayOutputStream; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; @@ -262,9 +264,13 @@ public Notification.Builder buildNotification(Context context) { .build(); context.getSystemService(ShortcutManager.class).addDynamicShortcuts(List.of(shortcut)); + int flag = PendingIntent.FLAG_IMMUTABLE; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flag = PendingIntent.FLAG_MUTABLE; + } Notification.BubbleMetadata bubbs = new Notification.BubbleMetadata.Builder() .setIntent( - PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)) + PendingIntent.getActivity(context, 0, intent, flag)) .setIcon(notificationIcon) .setSuppressNotification(false) .setDesiredHeight(context.getResources().getDisplayMetrics().heightPixels) @@ -279,7 +285,7 @@ public Notification.Builder buildNotification(Context context) { .setCategory(Notification.CATEGORY_STATUS) .setContentText(getName()) .setContentIntent( - PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)) + PendingIntent.getActivity(context, 0, intent, flag)) .setAutoCancel(true) .setStyle(new Notification.MessagingStyle(createPerson()) .addMessage(mFirstMessage, System.currentTimeMillis(), createPerson()) diff --git a/eggs/R/src/com/android_r/egg/neko/NekoLand.java b/eggs/R/src/com/android_r/egg/neko/NekoLand.java index 54a6bf1f..7794844a 100644 --- a/eggs/R/src/com/android_r/egg/neko/NekoLand.java +++ b/eggs/R/src/com/android_r/egg/neko/NekoLand.java @@ -16,17 +16,16 @@ package com.android_r.egg.neko; -import android.Manifest; import android.app.ActionBar; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -51,7 +50,7 @@ //import com.android.internal.logging.MetricsLogger; -@RequiresApi(30) +@RequiresApi(Build.VERSION_CODES.R) public class NekoLand extends Activity implements PrefsListener { public static String CHAN_ID = "EGG"; @@ -60,12 +59,12 @@ public class NekoLand extends Activity implements PrefsListener { private static final int EXPORT_BITMAP_SIZE = 600; - private static final int STORAGE_PERM_REQUEST = 123; + //private static final int STORAGE_PERM_REQUEST = 123; private static boolean CAT_GEN = false; private PrefState mPrefs; private CatAdapter mAdapter; - private Cat mPendingShareCat; + //private Cat mPendingShareCat; @Override @@ -248,14 +247,14 @@ public void onClick(DialogInterface dialog, int which) { public void onClick(View v) { setContextGroupVisible(holder, false); Cat cat = mCats[holder.getAdapterPosition()]; - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - mPendingShareCat = cat; - requestPermissions( - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - STORAGE_PERM_REQUEST); - return; - } +// if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) +// != PackageManager.PERMISSION_GRANTED) { +// mPendingShareCat = cat; +// requestPermissions( +// new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, +// STORAGE_PERM_REQUEST); +// return; +// } shareCat(cat); } }); @@ -275,16 +274,16 @@ private void shareCat(Cat cat) { } } - @Override - public void onRequestPermissionsResult(int requestCode, - String permissions[], int[] grantResults) { - if (requestCode == STORAGE_PERM_REQUEST) { - if (mPendingShareCat != null) { - shareCat(mPendingShareCat); - mPendingShareCat = null; - } - } - } +// @Override +// public void onRequestPermissionsResult(int requestCode, +// String permissions[], int[] grantResults) { +// if (requestCode == STORAGE_PERM_REQUEST) { +// if (mPendingShareCat != null) { +// shareCat(mPendingShareCat); +// mPendingShareCat = null; +// } +// } +// } private static class CatHolder extends RecyclerView.ViewHolder { private final ImageView imageView; diff --git a/libs.versions.toml b/libs.versions.toml index cdba075d..26b02294 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin = "1.5.30" +kotlin = "1.6.0" [libraries] android-gradle = "com.android.tools.build:gradle:7.0.3" @@ -14,8 +14,7 @@ androidx-annotation = "androidx.annotation:annotation:1.2.0" androidx-core-ktx = "androidx.core:core-ktx:1.6.0" androidx-preference-ktx = "androidx.preference:preference-ktx:1.1.1" androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.0.4" -androidx-browser = "androidx.browser:browser:1.3.0" -google-browserhelper = "com.google.androidbrowserhelper:androidbrowserhelper:2.2.0" +androidx-browser = "androidx.browser:browser:1.4.0" google-material = "com.google.android.material:material:1.4.0" free-reflection = "me.weishu:free_reflection:3.0.1"