diff --git a/.gitignore b/.gitignore index 4d651efb..7c439c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +.last_build_id # IntelliJ related *.iml @@ -39,3 +40,6 @@ lib/generated_plugin_registrant.dart # Firebase android/app/google-services.json ios/Runner/GoogleService-Info.plist + +# release build +*.symbols \ No newline at end of file diff --git a/README.md b/README.md index 215a518d..ecaeb720 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ ## 貢獻者 - [morris13579](https://github.com/morris13579) - [yaoandy107](https://github.com/yaoandy107) +- [Xanonymous](https://github.com/Xanonymous-GitHub) - [DevilTea](https://github.com/DevilTea) ## 授權 diff --git a/android/.gitignore b/android/.gitignore index bc2100d8..eee13a6c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,3 +5,4 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +*.jks \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index ef30434e..a37171d0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply plugin: 'com.google.gms.google-services' -apply plugin: 'io.fabric' +apply plugin: 'com.google.firebase.crashlytics' def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') @@ -35,7 +35,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 29 + compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -50,11 +50,12 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "club.ntut.npc.tat" - minSdkVersion 16 - targetSdkVersion 29 + minSdkVersion 23 + targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true } signingConfigs { @@ -71,10 +72,11 @@ android { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.release - shrinkResources true // To enable code shrinking with ProGuard - minifyEnabled true // 移除未使用的code, 簡化apk - proguardFiles getDefaultProguardFile('proguard-android.txt'), - 'proguard-rules.pro' + shrinkResources false // To enable code shrinking with ProGuard + minifyEnabled false // remove unused code + proguardFiles getDefaultProguardFile( + 'proguard-android.txt' + ), 'proguard-rules.pro' } } } @@ -86,11 +88,13 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' - - implementation 'com.google.firebase:firebase-analytics:17.2.3' - implementation 'com.llew:reflect:1.0.1' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation 'com.android.support:multidex:1.0.3' implementation 'com.google.code.gson:gson:2.8.6' - implementation "androidx.media:media:1.1.0" + implementation "androidx.media:media:1.2.1" + implementation 'com.google.firebase:firebase-analytics:18.0.1' + implementation 'com.google.firebase:firebase-crashlytics:17.3.0' + implementation 'com.google.firebase:firebase-messaging:21.0.1' + implementation "androidx.multidex:multidex:2.0.1" } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index ad4751d8..ba9bb15d 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -5,7 +5,6 @@ -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } --keep class tv.danmaku.ijk.media.player.** {*;} -dontwarn io.flutter.embedding.** @@ -34,4 +33,6 @@ } ## flutter_local_notification plugin rules --keep class com.dexterous.** { *; } \ No newline at end of file +-keep class com.dexterous.** { *; } + +-keep class com.google.firebase.provider.FirebaseInitProvider \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 26bc657a..34173ef4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -13,11 +13,14 @@ + + android:usesCleartextTraffic="true" + android:label="TAT" + tools:targetApi="m"> + + + + @@ -38,16 +45,18 @@ android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/splash" /> + + + --> + @@ -64,7 +73,7 @@ - + + + + diff --git a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterLogger.java b/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterLogger.java deleted file mode 100644 index e285b72d..00000000 --- a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterLogger.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.flutter.embedding.engine.hotfix; - -import android.util.Log; - -public class FlutterLogger { - - public static boolean logEnabled = true; - - public static String logTag = FlutterLogger.class.getSimpleName(); - - - public static void setLogEnabled(boolean enabled) { - logEnabled = enabled; - } - - public static void i(String msg) { - i(logTag, msg); - } - - public static void i(String tag, String msg) { - if (logEnabled) { - Log.i(tag, msg); - } - } - - public static void w(String msg) { - w(logTag, msg); - } - - public static void w(String tag, String msg) { - if (logEnabled) { - Log.w(tag, msg); - } - } - - public static void e(String msg) { - e(logTag, msg); - } - - public static void e(String tag, String msg) { - if (logEnabled) { - Log.e(tag, msg); - } - } -} diff --git a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterManager.java b/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterManager.java deleted file mode 100644 index c36b81c5..00000000 --- a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterManager.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.flutter.embedding.engine.hotfix; - -import android.content.Context; -import android.os.Looper; - -import java.io.File; - -import io.flutter.embedding.engine.loader.FlutterLoader; -import io.flutter.embedding.engine.loader.FlutterLoaderV012000; -import io.flutter.view.FlutterMain; - -public class FlutterManager { - - private static final String TAG = "FlutterManager"; - - public static void startInitialization(Context context) { - startInitialization(context, null); - } - - public static void startInitialization(Context context, File aotFile) { - startInitialization(context, aotFile, FlutterVersion.VERSION_012000); - } - - public static void startInitialization(Context context, File aotFile, FlutterVersion version) { - startInitialization(context, aotFile, version, new FlutterMain.Settings()); - } - - public static void startInitialization(Context context, File aotFile, FlutterVersion version, FlutterMain.Settings settings) { - ensureInitializeOnMainThread(); - FlutterCallback flutterCallback = generateFlutterCallback(version); - if (null != flutterCallback) { - flutterCallback.startInitialization(context, aotFile, getFlutterLoaderSettings(settings)); - } else { - FlutterLogger.w(TAG, "Flutter Version not supported: " + version); - FlutterMain.startInitialization(context); - } - } - - private static void ensureInitializeOnMainThread() { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException("startInitialization must be called on the main thread"); - } - } - - private static FlutterLoader.Settings getFlutterLoaderSettings(FlutterMain.Settings settings) { - FlutterLoader.Settings setting = new FlutterLoader.Settings(); - if (null != settings) { - setting.setLogTag(settings.getLogTag()); - } - return setting; - } - - private static FlutterCallback generateFlutterCallback(FlutterVersion version) { - if (FlutterVersion.VERSION_012000 == version) { - return FlutterLoaderV012000.getInstance(); - } - return null; - } - - public interface FlutterCallback { - void startInitialization(Context context, File aotFile, FlutterLoader.Settings settings); - } -} diff --git a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterVersion.java b/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterVersion.java deleted file mode 100644 index 0fbceb8f..00000000 --- a/android/app/src/main/java/io/flutter/embedding/engine/hotfix/FlutterVersion.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.flutter.embedding.engine.hotfix; - -public enum FlutterVersion { - /** - * Flutter Version: 1.14.0 - */ - VERSION_012000 -} diff --git a/android/app/src/main/java/io/flutter/embedding/engine/loader/FlutterLoaderV012000.java b/android/app/src/main/java/io/flutter/embedding/engine/loader/FlutterLoaderV012000.java deleted file mode 100644 index c3b18ba7..00000000 --- a/android/app/src/main/java/io/flutter/embedding/engine/loader/FlutterLoaderV012000.java +++ /dev/null @@ -1,488 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.embedding.engine.loader; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.util.Log; -import android.view.WindowManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.llew.reflect.FieldUtils; - -import io.flutter.BuildConfig; -import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.hotfix.FlutterLogger; -import io.flutter.embedding.engine.hotfix.FlutterManager; -import io.flutter.util.PathUtils; -import io.flutter.view.VsyncWaiter; - -import java.io.File; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -/** - * Finds Flutter resources in an application APK and also loads Flutter's native library. - */ -public class FlutterLoaderV012000 extends FlutterLoader implements FlutterManager.FlutterCallback { - private static final String TAG = "FlutterLoader"; - - // Must match values in flutter::switches - private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name"; - private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path"; - private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data"; - private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data"; - private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir"; - - // XML Attribute keys supported in AndroidManifest.xml - private static final String PUBLIC_AOT_SHARED_LIBRARY_NAME = - FlutterLoader.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME; - private static final String PUBLIC_VM_SNAPSHOT_DATA_KEY = - FlutterLoader.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY; - private static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY = - FlutterLoader.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY; - private static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY = - FlutterLoader.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY; - - // Resource names used for components of the precompiled snapshot. - private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so"; - private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data"; - private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data"; - private static final String DEFAULT_LIBRARY = "libflutter.so"; - private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin"; - private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets"; - - // Mutable because default values can be overridden via config properties - private String aotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME; - private String vmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA; - private String isolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA; - private String flutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR; - - private static FlutterLoaderV012000 instance; - - /** - * Returns a singleton {@code FlutterLoader} instance. - * - *

The returned instance loads Flutter native libraries in the standard way. A singleton object - * is used instead of static methods to facilitate testing without actually running native library - * linking. - */ - @NonNull - public static FlutterLoaderV012000 getInstance() { - if (instance == null) { - instance = new FlutterLoaderV012000(); - } - return instance; - } - - private boolean initialized = false; - @Nullable - private Settings settings; - private long initStartTimestampMillis; - - private static class InitResult { - final String appStoragePath; - final String engineCachesPath; - final String dataDirPath; - - private InitResult(String appStoragePath, String engineCachesPath, String dataDirPath) { - this.appStoragePath = appStoragePath; - this.engineCachesPath = engineCachesPath; - this.dataDirPath = dataDirPath; - } - } - - @Nullable - Future initResultFuture; - - /** - * Starts initialization of the native system. - * - * @param applicationContext The Android application context. - */ - public void startInitialization(@NonNull Context applicationContext) { - startInitialization(applicationContext, new Settings()); - } - - /** - * Starts initialization of the native system. - * - *

This loads the Flutter engine's native library to enable subsequent JNI calls. This also - * starts locating and unpacking Dart resources packaged in the app's APK. - * - *

Calling this method multiple times has no effect. - * - * @param applicationContext The Android application context. - * @param settings Configuration settings. - */ - public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) { - // Do not run startInitialization more than once. - if (this.settings != null) { - return; - } - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException("startInitialization must be called on the main thread"); - } - - // Ensure that the context is actually the application context. - final Context appContext = applicationContext.getApplicationContext(); - - this.settings = settings; - - initStartTimestampMillis = SystemClock.uptimeMillis(); - initConfig(appContext); - VsyncWaiter.getInstance((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)) - .init(); - - // Use a background thread for initialization tasks that require disk access. - Callable initTask = - new Callable() { - @Override - public InitResult call() { - ResourceExtractor resourceExtractor = initResources(appContext); - - System.loadLibrary("flutter"); - - // Prefetch the default font manager as soon as possible on a background thread. - // It helps to reduce time cost of engine setup that blocks the platform thread. - Executors.newSingleThreadExecutor() - .execute( - new Runnable() { - @Override - public void run() { - FlutterJNI.nativePrefetchDefaultFontManager(); - } - }); - - if (resourceExtractor != null) { - resourceExtractor.waitForCompletion(); - } - - return new InitResult( - PathUtils.getFilesDir(appContext), - PathUtils.getCacheDirectory(appContext), - PathUtils.getDataDirectory(appContext)); - } - }; - initResultFuture = Executors.newSingleThreadExecutor().submit(initTask); - } - - // *************************************************** hot fix code start ***************************************************// - private static final String FIELD_NAME = "instance"; - - private File aotSharedLibraryFile; - - @Override - public void startInitialization(Context context, File aotFile, FlutterLoader.Settings settings) { - aotSharedLibraryFile = aotFile; - hookFlutterLoaderIfNecessary(); - FlutterLoader.getInstance().startInitialization(context, settings); - } - - private void hookFlutterLoaderIfNecessary() { - try { - if (!flutterLoaderHookedSuccess()) { - FlutterLogger.i(TAG, "FlutterLoader hook start."); - FlutterLoaderV012000 instance = FlutterLoaderV012000.getInstance(); - FieldUtils.writeStaticField(FlutterLoader.class, FIELD_NAME, instance); - FlutterLogger.i(TAG, "FlutterLoader hook finish."); - - if (flutterLoaderHookedSuccess()) { - FlutterLogger.i(TAG, "FlutterLoader hook success."); - } else { - FlutterLogger.i(TAG, "FlutterLoader hook failure."); - } - } else { - FlutterLogger.i(TAG, "FlutterLoader already hooked."); - } - } catch (Throwable error) { - FlutterLogger.w(TAG, "FlutterLoader hook " + (flutterLoaderHookedSuccess() ? "success" : "failure") + " and error occured: " + error); - } - } - - private boolean flutterLoaderHookedSuccess() { - return FlutterLoader.getInstance() instanceof FlutterLoaderV012000; - } - - /** - * Blocks until initialization of the native system has completed. - * - *

Calling this method multiple times has no effect. - * - * @param applicationContext The Android application context. - * @param args Flags sent to the Flutter runtime. - */ - public void ensureInitializationComplete( - @NonNull Context applicationContext, @Nullable String[] args) { - if (initialized) { - return; - } - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException( - "ensureInitializationComplete must be called on the main thread"); - } - if (settings == null) { - throw new IllegalStateException( - "ensureInitializationComplete must be called after startInitialization"); - } - try { - InitResult result = initResultFuture.get(); - - List shellArgs = new ArrayList<>(); - shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat"); - - ApplicationInfo applicationInfo = getApplicationInfo(applicationContext); - shellArgs.add( - "--icu-native-lib-path=" - + applicationInfo.nativeLibraryDir - + File.separator - + DEFAULT_LIBRARY); - if (args != null) { - Collections.addAll(shellArgs, args); - } - - String kernelPath = null; - if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { - String snapshotAssetPath = result.dataDirPath + File.separator + flutterAssetsDir; - kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB; - shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath); - shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData); - shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData); - } else { - // replace libapp.so fie here if aotSharedLibraryFile is valid - if (null != aotSharedLibraryFile - && aotSharedLibraryFile.exists() - && aotSharedLibraryFile.isFile() - && aotSharedLibraryFile.canRead() - && aotSharedLibraryFile.length() > 0) { - shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryFile.getName()); - - // Most devices can load the AOT shared library based on the library name - // with no directory path. Provide a fully qualified path to the library - // as a workaround for devices where that fails. - shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryFile.getAbsolutePath()); - - FlutterLogger.i(TAG, "initialize with fixed file: " + aotSharedLibraryFile.getAbsolutePath()); - } else { - // aotSharedLibraryFile is not valid, and use origin file here - shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName); - - // Most devices can load the AOT shared library based on the library name - // with no directory path. Provide a fully qualified path to the library - // as a workaround for devices where that fails. - shellArgs.add( - "--" - + AOT_SHARED_LIBRARY_NAME - + "=" - + applicationInfo.nativeLibraryDir - + File.separator - + aotSharedLibraryName); - FlutterLogger.i(TAG, "initialize with origin file: " + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName); - } - - } - - shellArgs.add("--cache-dir-path=" + result.engineCachesPath); - if (settings.getLogTag() != null) { - shellArgs.add("--log-tag=" + settings.getLogTag()); - } - - long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; - - // TODO(cyanlaz): Remove this when dynamic thread merging is done. - // https://github.com/flutter/flutter/issues/59930 - Bundle bundle = applicationInfo.metaData; - if (bundle != null) { - boolean use_embedded_view = bundle.getBoolean("io.flutter.embedded_views_preview"); - if (use_embedded_view) { - shellArgs.add("--use-embedded-view"); - } - } - - FlutterJNI.nativeInit( - applicationContext, - shellArgs.toArray(new String[0]), - kernelPath, - result.appStoragePath, - result.engineCachesPath, - initTimeMillis); - - initialized = true; - } catch (Exception e) { - Log.e(TAG, "Flutter initialization failed.", e); - throw new RuntimeException(e); - } - } - - /** - * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background - * thread, then invoking {@code callback} on the {@code callbackHandler}. - */ - public void ensureInitializationCompleteAsync( - @NonNull Context applicationContext, - @Nullable String[] args, - @NonNull Handler callbackHandler, - @NonNull Runnable callback) { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException( - "ensureInitializationComplete must be called on the main thread"); - } - if (settings == null) { - throw new IllegalStateException( - "ensureInitializationComplete must be called after startInitialization"); - } - if (initialized) { - callbackHandler.post(callback); - return; - } - Executors.newSingleThreadExecutor() - .execute( - new Runnable() { - @Override - public void run() { - InitResult result; - try { - result = initResultFuture.get(); - } catch (Exception e) { - Log.e(TAG, "Flutter initialization failed.", e); - throw new RuntimeException(e); - } - new Handler(Looper.getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - ensureInitializationComplete( - applicationContext.getApplicationContext(), args); - callbackHandler.post(callback); - } - }); - } - }); - } - - @NonNull - private ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) { - try { - return applicationContext - .getPackageManager() - .getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - } - - /** - * Initialize our Flutter config values by obtaining them from the manifest XML file, falling back - * to default values. - */ - private void initConfig(@NonNull Context applicationContext) { - Bundle metadata = getApplicationInfo(applicationContext).metaData; - - // There isn't a `` tag as a direct child of `` in - // `AndroidManifest.xml`. - if (metadata == null) { - return; - } - - aotSharedLibraryName = - metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME); - flutterAssetsDir = - metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR); - - vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA); - isolateSnapshotData = - metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA); - } - - /** - * Extract assets out of the APK that need to be cached as uncompressed files on disk. - */ - private ResourceExtractor initResources(@NonNull Context applicationContext) { - ResourceExtractor resourceExtractor = null; - if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) { - final String dataDirPath = PathUtils.getDataDirectory(applicationContext); - final String packageName = applicationContext.getPackageName(); - final PackageManager packageManager = applicationContext.getPackageManager(); - final AssetManager assetManager = applicationContext.getResources().getAssets(); - resourceExtractor = - new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager); - - // In debug/JIT mode these assets will be written to disk and then - // mapped into memory so they can be provided to the Dart VM. - resourceExtractor - .addResource(fullAssetPathFrom(vmSnapshotData)) - .addResource(fullAssetPathFrom(isolateSnapshotData)) - .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB)); - - resourceExtractor.start(); - } - return resourceExtractor; - } - - @NonNull - public String findAppBundlePath() { - return flutterAssetsDir; - } - - /** - * Returns the file name for the given asset. The returned file name can be used to access the - * asset in the APK through the {@link android.content.res.AssetManager} API. - * - * @param asset the name of the asset. The name can be hierarchical - * @return the filename to be used with {@link android.content.res.AssetManager} - */ - @NonNull - public String getLookupKeyForAsset(@NonNull String asset) { - return fullAssetPathFrom(asset); - } - - /** - * Returns the file name for the given asset which originates from the specified packageName. The - * returned file name can be used to access the asset in the APK through the {@link - * android.content.res.AssetManager} API. - * - * @param asset the name of the asset. The name can be hierarchical - * @param packageName the name of the package from which the asset originates - * @return the file name to be used with {@link android.content.res.AssetManager} - */ - @NonNull - public String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) { - return getLookupKeyForAsset("packages" + File.separator + packageName + File.separator + asset); - } - - @NonNull - private String fullAssetPathFrom(@NonNull String filePath) { - return flutterAssetsDir + File.separator + filePath; - } - - public static class Settings { - private String logTag; - - @Nullable - public String getLogTag() { - return logTag; - } - - /** - * Set the tag associated with Flutter app log messages. - * - * @param tag Log tag. - */ - public void setLogTag(String tag) { - logTag = tag; - } - } -} diff --git a/android/app/src/main/java/widget/CourseWidgetProvider.java b/android/app/src/main/java/widget/CourseWidgetProvider.java index 6c6226c0..cb710374 100644 --- a/android/app/src/main/java/widget/CourseWidgetProvider.java +++ b/android/app/src/main/java/widget/CourseWidgetProvider.java @@ -14,7 +14,7 @@ import java.util.Arrays; -import club.ntut.npc.tat.BootLoaderActivity; +import club.ntut.npc.tat.MainActivity; import club.ntut.npc.tat.R; import io.flutter.Log; @@ -50,7 +50,8 @@ public void onReceive(Context context, Intent intent) { if (ACTION_ONCLICK.equals(intent.getAction())) { //Toast.makeText(context, "開啟app", Toast.LENGTH_LONG).show(); - Intent actIntent = new Intent(context, BootLoaderActivity.class); + //Intent actIntent = new Intent(context, BootLoaderActivity.class); + Intent actIntent = new Intent(context, MainActivity.class); actIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME); context.startActivity(actIntent); } diff --git a/android/app/src/main/kotlin/club/ntut/npc/tat/Application.java b/android/app/src/main/kotlin/club/ntut/npc/tat/Application.java new file mode 100644 index 00000000..f140c942 --- /dev/null +++ b/android/app/src/main/kotlin/club/ntut/npc/tat/Application.java @@ -0,0 +1,20 @@ +package club.ntut.npc.tat; + +import io.flutter.app.FlutterApplication; +import io.flutter.plugin.common.PluginRegistry; +import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService; +import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin; + +public class Application extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback { + @Override + public void onCreate() { + super.onCreate(); + FlutterFirebaseMessagingBackgroundService.setPluginRegistrant(this); + } + + @Override + public void registerWith(PluginRegistry registry) { + + //FlutterFirebaseMessagingPlugin.registerWith(registry.registrarFor("io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingPlugin")); + } +} diff --git a/android/app/src/main/kotlin/club/ntut/npc/tat/BootLoaderActivity.java b/android/app/src/main/kotlin/club/ntut/npc/tat/BootLoaderActivity.java deleted file mode 100644 index 99241624..00000000 --- a/android/app/src/main/kotlin/club/ntut/npc/tat/BootLoaderActivity.java +++ /dev/null @@ -1,157 +0,0 @@ -package club.ntut.npc.tat; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import androidx.annotation.Nullable; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.nio.channels.FileChannel; - -import io.flutter.embedding.engine.hotfix.FlutterLogger; -import io.flutter.embedding.engine.hotfix.FlutterManager; -import io.flutter.embedding.engine.hotfix.FlutterVersion; - -public class BootLoaderActivity extends Activity { - //final String Tag = "BootLoaderActivity"; - - final String flutter_state_key = "flutter.flutter_state"; //會刪除由flutter app寫入如果檢查到沒寫入代表app crash,會清除補丁 - final String app_version_key = "flutter.version"; //取得之前執行APP版本 - final String app_patch_version = "flutter.patch_version"; - final String hotfixFileName = "hotfix.so"; - - SharedPreferences pref; - File dir; - - public String getVersionName(Context context) { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packageInfo; - String versionName = ""; - try { - packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - versionName = packageInfo.versionName; - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - return versionName; - } - - void checkPatchDir() { - if (!dir.exists()) { - if (dir.mkdirs()) { - FlutterLogger.i("mkdirs success: " + dir.getAbsolutePath()); - } else { - FlutterLogger.i("mkdirs failure: " + dir.getAbsolutePath()); - } - } else { - FlutterLogger.i("dirs exists: " + dir.getAbsolutePath()); - } - } - - void handleAppVersionUpdate() { - String appVersionNow = getVersionName(getApplicationContext()); - String appVersionName = pref.getString(app_version_key, appVersionNow); - boolean app_version_update = !appVersionName.contains(appVersionNow); //版本號不同刪除補丁 - if (app_version_update) { //app版本更新 - File dest = new File(dir, hotfixFileName); - try { - if (dest.exists() && !dest.delete()) { //刪除舊的補釘 - FileWriter writer = new FileWriter(dest, false); - writer.write(""); - writer.flush(); - writer.close(); - } - pref.edit().remove(app_patch_version).apply(); - } catch (Exception e) { - FlutterLogger.i("delete fail"); - } - } - } - - void handleAppCrash() { - boolean launch_success = pref.contains(flutter_state_key); //如果flutter沒有正常啟動就不會寫入 - if (!launch_success) { //載入失敗刪除補丁 - File dest = new File(dir, hotfixFileName); - try { - if (dest.exists() && !dest.delete()) { //刪除舊的補釘 - FileWriter writer = new FileWriter(dest, false); - writer.write(""); - writer.flush(); - writer.close(); - } - } catch (Exception e) { - FlutterLogger.i("delete fail"); - } - } - pref.edit().remove(flutter_state_key).apply(); //每次啟動會刪除由flutter重新寫入 - } - - - void handlePatchUpdate() { - try { - File downloadDir = getApplicationContext().getExternalFilesDir(null); - File source = new File(downloadDir, hotfixFileName); - File dest = new File(dir, hotfixFileName); - if (source.exists()) { //檢查是否有要更新的補丁 - //寫入前將舊的補丁刪除 - if (dest.exists() && !dest.delete()) { //刪除舊的補釘 - FileWriter writer = new FileWriter(dest, false); - writer.write(""); - writer.flush(); - writer.close(); - } - FileChannel inputChannel = new FileInputStream(source).getChannel(); - FileChannel outputChannel = new FileOutputStream(dest).getChannel(); - outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); - inputChannel.close(); - outputChannel.close(); - if (source.delete()) { - FlutterLogger.i("delete patch"); - } - FlutterLogger.i("copy fixed file finish: " + dest.getAbsolutePath()); - } - } catch (Throwable error) { - FlutterLogger.e("copy file error: " + error); - } - } - - - void loadPatch() { - File dest = new File(dir, hotfixFileName); - if (dest.exists()) { //檢查如果補釘存在就載入 - FlutterManager.startInitialization(this, dest, FlutterVersion.VERSION_012000); - } - } - - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - pref = getSharedPreferences("FlutterSharedPreferences", MODE_PRIVATE); - dir = new File(getFilesDir(), "/flutter/hotfix"); //更新目錄 - setContentView(R.layout.bootloader_activity); - //創建補丁存放資料夾 - checkPatchDir(); - //app版本更新刪除patch - handleAppVersionUpdate(); - //處理更新補釘後app無法開啟 - handleAppCrash(); - //檢查補釘是否要更新 - handlePatchUpdate(); - //載入補釘 - loadPatch(); - //啟動flutter - Intent intent = new Intent(this, MainActivity.class); - BootLoaderActivity.this.startActivity(intent); - BootLoaderActivity.this.finish(); - } - - -} diff --git a/android/app/src/main/kotlin/club/ntut/npc/tat/MainActivity.kt b/android/app/src/main/kotlin/club/ntut/npc/tat/MainActivity.kt index 2b079625..2aa7c144 100644 --- a/android/app/src/main/kotlin/club/ntut/npc/tat/MainActivity.kt +++ b/android/app/src/main/kotlin/club/ntut/npc/tat/MainActivity.kt @@ -6,15 +6,15 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import androidx.annotation.NonNull -import com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin import io.flutter.Log import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant import kotlin.system.exitProcess -class MainActivity : FlutterActivity() { +class MainActivity : FlutterFragmentActivity() { private val channelName = "club.ntut.npc.tat.main.mothod.channel.name" private val logTag = "FlutterActivity" override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { @@ -27,7 +27,7 @@ class MainActivity : FlutterActivity() { Log.i(logTag, "update_weight") try { val intend = Intent("android.appwidget.action.APPWIDGET_UPDATE") //顯示意圖 - context.sendBroadcast(intend) + this.sendBroadcast(intend) result.success(true) } catch (e: Exception) { result.success(false) @@ -36,7 +36,7 @@ class MainActivity : FlutterActivity() { } } "restart_app" -> { - doRestart(context); + doRestart(this) } else -> { result.notImplemented() diff --git a/android/app/src/main/kotlin/club/ntut/npc/tat/MyFlutterLocalNotificationsPlugin.java b/android/app/src/main/kotlin/club/ntut/npc/tat/MyFlutterLocalNotificationsPlugin.java deleted file mode 100644 index 5626a91a..00000000 --- a/android/app/src/main/kotlin/club/ntut/npc/tat/MyFlutterLocalNotificationsPlugin.java +++ /dev/null @@ -1,980 +0,0 @@ -package club.ntut.npc.tat; - -import android.app.Activity; -import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.AssetFileDescriptor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.AudioAttributes; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Build; -import android.text.Html; -import android.text.Spanned; - -import androidx.annotation.NonNull; -import androidx.core.app.AlarmManagerCompat; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.app.Person; -import androidx.core.graphics.drawable.IconCompat; - -import com.dexterous.flutterlocalnotifications.BitmapSource; -import com.dexterous.flutterlocalnotifications.NotificationStyle; -import com.dexterous.flutterlocalnotifications.RuntimeTypeAdapterFactory; -import com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver; -import com.dexterous.flutterlocalnotifications.SoundSource; -import com.dexterous.flutterlocalnotifications.models.IconSource; -import com.dexterous.flutterlocalnotifications.models.MessageDetails; -import com.dexterous.flutterlocalnotifications.models.NotificationChannelAction; -import com.dexterous.flutterlocalnotifications.models.NotificationChannelDetails; -import com.dexterous.flutterlocalnotifications.models.NotificationDetails; -import com.dexterous.flutterlocalnotifications.models.PersonDetails; -import com.dexterous.flutterlocalnotifications.models.styles.BigPictureStyleInformation; -import com.dexterous.flutterlocalnotifications.models.styles.BigTextStyleInformation; -import com.dexterous.flutterlocalnotifications.models.styles.DefaultStyleInformation; -import com.dexterous.flutterlocalnotifications.models.styles.InboxStyleInformation; -import com.dexterous.flutterlocalnotifications.models.styles.MessagingStyleInformation; -import com.dexterous.flutterlocalnotifications.models.styles.StyleInformation; -import com.dexterous.flutterlocalnotifications.utils.BooleanUtils; -import com.dexterous.flutterlocalnotifications.utils.StringUtils; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.reflect.TypeToken; - -import java.io.FileInputStream; -import java.io.IOException; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.view.FlutterMain; - -/** - * FlutterLocalNotificationsPlugin - */ -public class MyFlutterLocalNotificationsPlugin implements MethodCallHandler, PluginRegistry.NewIntentListener, FlutterPlugin, ActivityAware { - private static final String SHARED_PREFERENCES_KEY = "notification_plugin_cache"; - private static final String DRAWABLE = "drawable"; - private static final String DEFAULT_ICON = "defaultIcon"; - private static final String SELECT_NOTIFICATION = "SELECT_NOTIFICATION"; - private static final String SCHEDULED_NOTIFICATIONS = "scheduled_notifications"; - private static final String INITIALIZE_METHOD = "initialize"; - private static final String CREATE_NOTIFICATION_CHANNEL_METHOD = "createNotificationChannel"; - private static final String DELETE_NOTIFICATION_CHANNEL_METHOD = "deleteNotificationChannel"; - private static final String PENDING_NOTIFICATION_REQUESTS_METHOD = "pendingNotificationRequests"; - private static final String SHOW_METHOD = "show"; - private static final String CANCEL_METHOD = "cancel"; - private static final String CANCEL_ALL_METHOD = "cancelAll"; - private static final String SCHEDULE_METHOD = "schedule"; - private static final String PERIODICALLY_SHOW_METHOD = "periodicallyShow"; - private static final String SHOW_DAILY_AT_TIME_METHOD = "showDailyAtTime"; - private static final String SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD = "showWeeklyAtDayAndTime"; - private static final String GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD = "getNotificationAppLaunchDetails"; - private static final String METHOD_CHANNEL = "dexterous.com/flutter/local_notifications"; - private static final String PAYLOAD = "payload"; - private static final String INVALID_ICON_ERROR_CODE = "INVALID_ICON"; - private static final String INVALID_LARGE_ICON_ERROR_CODE = "INVALID_LARGE_ICON"; - private static final String INVALID_BIG_PICTURE_ERROR_CODE = "INVALID_BIG_PICTURE"; - private static final String INVALID_SOUND_ERROR_CODE = "INVALID_SOUND"; - private static final String INVALID_LED_DETAILS_ERROR_CODE = "INVALID_LED_DETAILS"; - private static final String INVALID_LED_DETAILS_ERROR_MESSAGE = "Must specify both ledOnMs and ledOffMs to configure the blink cycle on older versions of Android before Oreo"; - private static final String NOTIFICATION_LAUNCHED_APP = "notificationLaunchedApp"; - private static final String INVALID_DRAWABLE_RESOURCE_ERROR_MESSAGE = "The resource %s could not be found. Please make sure it has been added as a drawable resource to your Android head project."; - private static final String INVALID_RAW_RESOURCE_ERROR_MESSAGE = "The resource %s could not be found. Please make sure it has been added as a raw resource to your Android head project."; - static String NOTIFICATION_ID = "notification_id"; - static String NOTIFICATION = "notification"; - static String NOTIFICATION_DETAILS = "notificationDetails"; - static String REPEAT = "repeat"; - static Gson gson; - private MethodChannel channel; - private Context applicationContext; - private Activity mainActivity; - private boolean initialized; - - public static void registerWith(Registrar registrar) { - MyFlutterLocalNotificationsPlugin plugin = new MyFlutterLocalNotificationsPlugin(); - plugin.setActivity(registrar.activity()); - registrar.addNewIntentListener(plugin); - plugin.onAttachedToEngine(registrar.context(), registrar.messenger()); - } - - static void rescheduleNotifications(Context context) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - if (scheduledNotification.repeatInterval == null) { - scheduleNotification(context, scheduledNotification, false); - } else { - repeatNotification(context, scheduledNotification, false); - } - } - } - - private static Notification createNotification(Context context, NotificationDetails notificationDetails) { - setupNotificationChannel(context, NotificationChannelDetails.fromNotificationDetails(notificationDetails)); - Intent intent = new Intent(context, getMainActivityClass(context)); - intent.setAction(SELECT_NOTIFICATION); - intent.putExtra(PAYLOAD, notificationDetails.payload); - PendingIntent pendingIntent = PendingIntent.getActivity(context, notificationDetails.id, intent, PendingIntent.FLAG_UPDATE_CURRENT); - DefaultStyleInformation defaultStyleInformation = (DefaultStyleInformation) notificationDetails.styleInformation; - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, notificationDetails.channelId) - .setContentTitle(defaultStyleInformation.htmlFormatTitle ? fromHtml(notificationDetails.title) : notificationDetails.title) - .setContentText(defaultStyleInformation.htmlFormatBody ? fromHtml(notificationDetails.body) : notificationDetails.body) - .setTicker(notificationDetails.ticker) - .setAutoCancel(BooleanUtils.getValue(notificationDetails.autoCancel)) - .setContentIntent(pendingIntent) - .setPriority(notificationDetails.priority) - .setOngoing(BooleanUtils.getValue(notificationDetails.ongoing)) - .setOnlyAlertOnce(BooleanUtils.getValue(notificationDetails.onlyAlertOnce)); - - setSmallIcon(context, notificationDetails, builder); - if (!StringUtils.isNullOrEmpty(notificationDetails.largeIcon)) { - builder.setLargeIcon(getBitmapFromSource(context, notificationDetails.largeIcon, notificationDetails.largeIconBitmapSource)); - } - if (notificationDetails.color != null) { - builder.setColor(notificationDetails.color.intValue()); - } - - if (notificationDetails.showWhen != null) { - builder.setShowWhen(BooleanUtils.getValue(notificationDetails.showWhen)); - } - - if (notificationDetails.when != null) { - builder.setWhen(notificationDetails.when); - } - - setVisibility(notificationDetails, builder); - applyGrouping(notificationDetails, builder); - setSound(context, notificationDetails, builder); - setVibrationPattern(notificationDetails, builder); - setLights(notificationDetails, builder); - setStyle(context, notificationDetails, builder); - setProgress(notificationDetails, builder); - setCategory(notificationDetails, builder); - setTimeoutAfter(notificationDetails, builder); - Notification notification = builder.build(); - if (notificationDetails.additionalFlags != null && notificationDetails.additionalFlags.length > 0) { - for(int additionalFlag:notificationDetails.additionalFlags) { - notification.flags |= additionalFlag; - } - } - return notification; - } - - private static void setSmallIcon(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (!StringUtils.isNullOrEmpty(notificationDetails.icon)) { - builder.setSmallIcon(getDrawableResourceId(context, notificationDetails.icon)); - } else { - SharedPreferences sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); - String defaultIcon = sharedPreferences.getString(DEFAULT_ICON, null); - if (StringUtils.isNullOrEmpty(defaultIcon)) { - // for backwards compatibility: this is for handling the old way references to the icon used to be kept but should be removed in future - builder.setSmallIcon(notificationDetails.iconResourceId); - - } else { - builder.setSmallIcon(getDrawableResourceId(context, defaultIcon)); - } - } - } - - @NonNull - static Gson buildGson() { - if (gson == null) { - RuntimeTypeAdapterFactory styleInformationAdapter = - RuntimeTypeAdapterFactory - .of(StyleInformation.class) - .registerSubtype(DefaultStyleInformation.class) - .registerSubtype(BigTextStyleInformation.class) - .registerSubtype(BigPictureStyleInformation.class) - .registerSubtype(InboxStyleInformation.class) - .registerSubtype(MessagingStyleInformation.class); - GsonBuilder builder = new GsonBuilder().registerTypeAdapterFactory(styleInformationAdapter); - gson = builder.create(); - } - return gson; - } - - private static ArrayList loadScheduledNotifications(Context context) { - ArrayList scheduledNotifications = new ArrayList<>(); - SharedPreferences sharedPreferences = context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); - String json = sharedPreferences.getString(SCHEDULED_NOTIFICATIONS, null); - if (json != null) { - Gson gson = buildGson(); - Type type = new TypeToken>() { - }.getType(); - scheduledNotifications = gson.fromJson(json, type); - } - return scheduledNotifications; - } - - private static void saveScheduledNotifications(Context context, ArrayList scheduledNotifications) { - Gson gson = buildGson(); - String json = gson.toJson(scheduledNotifications); - SharedPreferences sharedPreferences = context.getSharedPreferences(SCHEDULED_NOTIFICATIONS, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(SCHEDULED_NOTIFICATIONS, json); - editor.commit(); - } - - static void removeNotificationFromCache(Integer notificationId, Context context) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - for (Iterator it = scheduledNotifications.iterator(); it.hasNext(); ) { - NotificationDetails notificationDetails = it.next(); - if (notificationDetails.id.equals(notificationId)) { - it.remove(); - break; - } - } - saveScheduledNotifications(context, scheduledNotifications); - } - - @SuppressWarnings("deprecation") - private static Spanned fromHtml(String html) { - if (html == null) { - return null; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY); - } else { - return Html.fromHtml(html); - } - } - - private static void scheduleNotification(Context context, final NotificationDetails notificationDetails, Boolean updateScheduledNotificationsCache) { - Gson gson = buildGson(); - String notificationDetailsJson = gson.toJson(notificationDetails); - Intent notificationIntent = new Intent(context, ScheduledNotificationReceiver.class); - notificationIntent.putExtra(NOTIFICATION_DETAILS, notificationDetailsJson); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationDetails.id, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - AlarmManager alarmManager = getAlarmManager(context); - if (BooleanUtils.getValue(notificationDetails.allowWhileIdle)) { - AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, notificationDetails.millisecondsSinceEpoch, pendingIntent); - } else { - AlarmManagerCompat.setExact(alarmManager, AlarmManager.RTC_WAKEUP, notificationDetails.millisecondsSinceEpoch, pendingIntent); - } - - if (updateScheduledNotificationsCache) { - saveScheduledNotification(context, notificationDetails); - } - } - - private static void repeatNotification(Context context, NotificationDetails notificationDetails, Boolean updateScheduledNotificationsCache) { - Gson gson = buildGson(); - String notificationDetailsJson = gson.toJson(notificationDetails); - Intent notificationIntent = new Intent(context, ScheduledNotificationReceiver.class); - notificationIntent.putExtra(NOTIFICATION_DETAILS, notificationDetailsJson); - notificationIntent.putExtra(REPEAT, true); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, notificationDetails.id, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - AlarmManager alarmManager = getAlarmManager(context); - long repeatInterval = 0; - switch (notificationDetails.repeatInterval) { - case EveryMinute: - repeatInterval = 60000; - break; - case Hourly: - repeatInterval = 60000 * 60; - break; - case Daily: - repeatInterval = 60000 * 60 * 24; - break; - case Weekly: - repeatInterval = 60000 * 60 * 24 * 7; - break; - default: - break; - } - - long startTimeMilliseconds = notificationDetails.calledAt; - if (notificationDetails.repeatTime != null) { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeInMillis(System.currentTimeMillis()); - calendar.set(Calendar.HOUR_OF_DAY, notificationDetails.repeatTime.hour); - calendar.set(Calendar.MINUTE, notificationDetails.repeatTime.minute); - calendar.set(Calendar.SECOND, notificationDetails.repeatTime.second); - if (notificationDetails.day != null) { - calendar.set(Calendar.DAY_OF_WEEK, notificationDetails.day); - } - - startTimeMilliseconds = calendar.getTimeInMillis(); - } - - // ensure that start time is in the future - long currentTime = System.currentTimeMillis(); - while (startTimeMilliseconds < currentTime) { - startTimeMilliseconds += repeatInterval; - } - - alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, startTimeMilliseconds, repeatInterval, pendingIntent); - - if (updateScheduledNotificationsCache) { - saveScheduledNotification(context, notificationDetails); - } - } - - private static void saveScheduledNotification(Context context, NotificationDetails notificationDetails) { - ArrayList scheduledNotifications = loadScheduledNotifications(context); - ArrayList scheduledNotificationsToSave = new ArrayList<>(); - for (NotificationDetails scheduledNotification : scheduledNotifications) { - if (scheduledNotification.id == notificationDetails.id) { - continue; - } - scheduledNotificationsToSave.add(scheduledNotification); - } - scheduledNotificationsToSave.add(notificationDetails); - saveScheduledNotifications(context, scheduledNotificationsToSave); - } - - private static int getDrawableResourceId(Context context, String name) { - return context.getResources().getIdentifier(name, DRAWABLE, context.getPackageName()); - } - - private static Bitmap getBitmapFromSource(Context context, String bitmapPath, BitmapSource bitmapSource) { - Bitmap bitmap = null; - if (bitmapSource == BitmapSource.DrawableResource) { - bitmap = BitmapFactory.decodeResource(context.getResources(), getDrawableResourceId(context, bitmapPath)); - } else if (bitmapSource == BitmapSource.FilePath) { - bitmap = BitmapFactory.decodeFile(bitmapPath); - } - - return bitmap; - } - - private static IconCompat getIconFromSource(Context context, String iconPath, IconSource iconSource) { - IconCompat icon = null; - switch (iconSource) { - case DrawableResource: - icon = IconCompat.createWithResource(context, getDrawableResourceId(context, iconPath)); - break; - case BitmapFilePath: - icon = IconCompat.createWithBitmap(BitmapFactory.decodeFile(iconPath)); - break; - case ContentUri: - icon = IconCompat.createWithContentUri(iconPath); - break; - case FlutterBitmapAsset: - try { - AssetFileDescriptor assetFileDescriptor = context.getAssets().openFd(FlutterMain.getLookupKeyForAsset(iconPath)); - FileInputStream fileInputStream = assetFileDescriptor.createInputStream(); - icon = IconCompat.createWithBitmap(BitmapFactory.decodeStream(fileInputStream)); - fileInputStream.close(); - assetFileDescriptor.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - break; - default: - break; - } - return icon; - } - - /** - * Sets the visibility property to the input Notification Builder - * - * @throws IllegalArgumentException If `notificationDetails.visibility` is not null but also - * not matches any known index. - */ - private static void setVisibility(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (notificationDetails.visibility == null) { - return; - } - - int visibility; - switch (notificationDetails.visibility) { - case 0: // Private - visibility = NotificationCompat.VISIBILITY_PRIVATE; - break; - case 1: // Public - visibility = NotificationCompat.VISIBILITY_PUBLIC; - break; - case 2: // Secret - visibility = NotificationCompat.VISIBILITY_SECRET; - break; - - default: - throw new IllegalArgumentException("Unknown index: " + notificationDetails.visibility); - } - - builder.setVisibility(visibility); - } - - private static void applyGrouping(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - boolean isGrouped = false; - if (!StringUtils.isNullOrEmpty(notificationDetails.groupKey)) { - builder.setGroup(notificationDetails.groupKey); - isGrouped = true; - } - - if (isGrouped) { - if (BooleanUtils.getValue(notificationDetails.setAsGroupSummary)) { - builder.setGroupSummary(true); - } - - builder.setGroupAlertBehavior(notificationDetails.groupAlertBehavior); - } - } - - private static void setVibrationPattern(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (BooleanUtils.getValue(notificationDetails.enableVibration)) { - if (notificationDetails.vibrationPattern != null && notificationDetails.vibrationPattern.length > 0) { - builder.setVibrate(notificationDetails.vibrationPattern); - } - } else { - builder.setVibrate(new long[]{0}); - } - } - - private static void setLights(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (BooleanUtils.getValue(notificationDetails.enableLights) && notificationDetails.ledOnMs != null && notificationDetails.ledOffMs != null) { - builder.setLights(notificationDetails.ledColor, notificationDetails.ledOnMs, notificationDetails.ledOffMs); - } - } - - private static void setSound(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (BooleanUtils.getValue(notificationDetails.playSound)) { - Uri uri = retrieveSoundResourceUri(context, notificationDetails.sound, notificationDetails.soundSource); - builder.setSound(uri); - } else { - builder.setSound(null); - } - } - - private static void setCategory(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (notificationDetails.category == null) { - return; - } - builder.setCategory(notificationDetails.category); - } - - private static void setTimeoutAfter(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (notificationDetails.timeoutAfter == null) { - return; - } - builder.setTimeoutAfter(notificationDetails.timeoutAfter); - } - - private static Class getMainActivityClass(Context context) { - String className = MainActivity.class.getName(); - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return null; - } - } - - private static void setStyle(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - switch (notificationDetails.style) { - case BigPicture: - setBigPictureStyle(context, notificationDetails, builder); - break; - case BigText: - setBigTextStyle(notificationDetails, builder); - break; - case Inbox: - setInboxStyle(notificationDetails, builder); - break; - case Messaging: - setMessagingStyle(context, notificationDetails, builder); - break; - case Media: - setMediaStyle(builder); - break; - default: - break; - } - } - - private static void setProgress(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - if (BooleanUtils.getValue(notificationDetails.showProgress)) { - builder.setProgress(notificationDetails.maxProgress, notificationDetails.progress, notificationDetails.indeterminate); - } - } - - private static void setBigPictureStyle(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - BigPictureStyleInformation bigPictureStyleInformation = (BigPictureStyleInformation) notificationDetails.styleInformation; - NotificationCompat.BigPictureStyle bigPictureStyle = new NotificationCompat.BigPictureStyle(); - if (bigPictureStyleInformation.contentTitle != null) { - CharSequence contentTitle = bigPictureStyleInformation.htmlFormatContentTitle ? fromHtml(bigPictureStyleInformation.contentTitle) : bigPictureStyleInformation.contentTitle; - bigPictureStyle.setBigContentTitle(contentTitle); - } - if (bigPictureStyleInformation.summaryText != null) { - CharSequence summaryText = bigPictureStyleInformation.htmlFormatSummaryText ? fromHtml(bigPictureStyleInformation.summaryText) : bigPictureStyleInformation.summaryText; - bigPictureStyle.setSummaryText(summaryText); - } - - if (bigPictureStyleInformation.hideExpandedLargeIcon) { - bigPictureStyle.bigLargeIcon(null); - } else { - if (bigPictureStyleInformation.largeIcon != null) { - bigPictureStyle.bigLargeIcon(getBitmapFromSource(context, bigPictureStyleInformation.largeIcon, bigPictureStyleInformation.largeIconBitmapSource)); - } - } - bigPictureStyle.bigPicture(getBitmapFromSource(context, bigPictureStyleInformation.bigPicture, bigPictureStyleInformation.bigPictureBitmapSource)); - builder.setStyle(bigPictureStyle); - } - - private static void setInboxStyle(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - InboxStyleInformation inboxStyleInformation = (InboxStyleInformation) notificationDetails.styleInformation; - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - if (inboxStyleInformation.contentTitle != null) { - CharSequence contentTitle = inboxStyleInformation.htmlFormatContentTitle ? fromHtml(inboxStyleInformation.contentTitle) : inboxStyleInformation.contentTitle; - inboxStyle.setBigContentTitle(contentTitle); - } - if (inboxStyleInformation.summaryText != null) { - CharSequence summaryText = inboxStyleInformation.htmlFormatSummaryText ? fromHtml(inboxStyleInformation.summaryText) : inboxStyleInformation.summaryText; - inboxStyle.setSummaryText(summaryText); - } - if (inboxStyleInformation.lines != null) { - for (String line : inboxStyleInformation.lines) { - inboxStyle.addLine(inboxStyleInformation.htmlFormatLines ? fromHtml(line) : line); - } - } - builder.setStyle(inboxStyle); - } - - private static void setMediaStyle(NotificationCompat.Builder builder) { - androidx.media.app.NotificationCompat.MediaStyle mediaStyle = new androidx.media.app.NotificationCompat.MediaStyle(); - builder.setStyle(mediaStyle); - } - - private static void setMessagingStyle(Context context, NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - MessagingStyleInformation messagingStyleInformation = (MessagingStyleInformation) notificationDetails.styleInformation; - Person person = buildPerson(context, messagingStyleInformation.person); - NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(person); - messagingStyle.setGroupConversation(BooleanUtils.getValue(messagingStyleInformation.groupConversation)); - if (messagingStyleInformation.conversationTitle != null) { - messagingStyle.setConversationTitle(messagingStyleInformation.conversationTitle); - } - if (messagingStyleInformation.messages != null && !messagingStyleInformation.messages.isEmpty()) { - for (MessageDetails messageDetails : messagingStyleInformation.messages) { - NotificationCompat.MessagingStyle.Message message = createMessage(context, messageDetails); - messagingStyle.addMessage(message); - } - } - builder.setStyle(messagingStyle); - } - - private static NotificationCompat.MessagingStyle.Message createMessage(Context context, MessageDetails messageDetails) { - NotificationCompat.MessagingStyle.Message message = new NotificationCompat.MessagingStyle.Message(messageDetails.text, messageDetails.timestamp, buildPerson(context, messageDetails.person)); - if (messageDetails.dataUri != null && messageDetails.dataMimeType != null) { - message.setData(messageDetails.dataMimeType, Uri.parse(messageDetails.dataUri)); - } - return message; - } - - private static Person buildPerson(Context context, PersonDetails personDetails) { - if (personDetails == null) { - return null; - } - - Person.Builder personBuilder = new Person.Builder(); - personBuilder.setBot(BooleanUtils.getValue(personDetails.bot)); - if (personDetails.icon != null && personDetails.iconBitmapSource != null) { - personBuilder.setIcon(getIconFromSource(context, personDetails.icon, personDetails.iconBitmapSource)); - } - personBuilder.setImportant(BooleanUtils.getValue(personDetails.important)); - if (personDetails.key != null) { - personBuilder.setKey(personDetails.key); - } - if (personDetails.name != null) { - personBuilder.setName(personDetails.name); - } - if (personDetails.uri != null) { - personBuilder.setUri(personDetails.uri); - } - return personBuilder.build(); - } - - private static void setBigTextStyle(NotificationDetails notificationDetails, NotificationCompat.Builder builder) { - BigTextStyleInformation bigTextStyleInformation = (BigTextStyleInformation) notificationDetails.styleInformation; - NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle(); - if (bigTextStyleInformation.bigText != null) { - CharSequence bigText = bigTextStyleInformation.htmlFormatBigText ? fromHtml(bigTextStyleInformation.bigText) : bigTextStyleInformation.bigText; - bigTextStyle.bigText(bigText); - } - if (bigTextStyleInformation.contentTitle != null) { - CharSequence contentTitle = bigTextStyleInformation.htmlFormatContentTitle ? fromHtml(bigTextStyleInformation.contentTitle) : bigTextStyleInformation.contentTitle; - bigTextStyle.setBigContentTitle(contentTitle); - } - if (bigTextStyleInformation.summaryText != null) { - CharSequence summaryText = bigTextStyleInformation.htmlFormatSummaryText ? fromHtml(bigTextStyleInformation.summaryText) : bigTextStyleInformation.summaryText; - bigTextStyle.setSummaryText(summaryText); - } - builder.setStyle(bigTextStyle); - } - - private static void setupNotificationChannel(Context context, NotificationChannelDetails notificationChannelDetails) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel notificationChannel = notificationManager.getNotificationChannel(notificationChannelDetails.id); - // only create/update the channel when needed/specified. Allow this happen to when channelAction may be null to support cases where notifications had been - // created on older versions of the plugin where channel management options weren't available back then - if ((notificationChannel == null && (notificationChannelDetails.channelAction == null || notificationChannelDetails.channelAction == NotificationChannelAction.CreateIfNotExists)) || (notificationChannel != null && notificationChannelDetails.channelAction == NotificationChannelAction.Update)) { - notificationChannel = new NotificationChannel(notificationChannelDetails.id, notificationChannelDetails.name, notificationChannelDetails.importance); - notificationChannel.setDescription(notificationChannelDetails.description); - if (notificationChannelDetails.playSound) { - AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build(); - Uri uri = retrieveSoundResourceUri(context, notificationChannelDetails.sound, notificationChannelDetails.soundSource); - notificationChannel.setSound(uri, audioAttributes); - } else { - notificationChannel.setSound(null, null); - } - notificationChannel.enableVibration(BooleanUtils.getValue(notificationChannelDetails.enableVibration)); - if (notificationChannelDetails.vibrationPattern != null && notificationChannelDetails.vibrationPattern.length > 0) { - notificationChannel.setVibrationPattern(notificationChannelDetails.vibrationPattern); - } - boolean enableLights = BooleanUtils.getValue(notificationChannelDetails.enableLights); - notificationChannel.enableLights(enableLights); - if (enableLights && notificationChannelDetails.ledColor != null) { - notificationChannel.setLightColor(notificationChannelDetails.ledColor); - } - notificationChannel.setShowBadge(BooleanUtils.getValue(notificationChannelDetails.showBadge)); - notificationManager.createNotificationChannel(notificationChannel); - } - } - } - - private static Uri retrieveSoundResourceUri(Context context, String sound, SoundSource soundSource) { - Uri uri = null; - if (StringUtils.isNullOrEmpty(sound)) { - uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - } else { - // allow null as soundSource was added later and prior to that, it was assumed to be a raw resource - if (soundSource == null || soundSource == SoundSource.RawResource) { - int soundResourceId = context.getResources().getIdentifier(sound, "raw", context.getPackageName()); - uri = Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId); - } else if (soundSource == SoundSource.Uri) { - uri = Uri.parse(sound); - } - } - return uri; - } - - private static AlarmManager getAlarmManager(Context context) { - return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - } - - private static boolean isValidDrawableResource(Context context, String name, Result result, String errorCode) { - int resourceId = context.getResources().getIdentifier(name, DRAWABLE, context.getPackageName()); - if (resourceId == 0) { - result.error(errorCode, String.format(INVALID_DRAWABLE_RESOURCE_ERROR_MESSAGE, name), null); - return false; - } - return true; - } - - static void showNotification(Context context, NotificationDetails notificationDetails) { - Notification notification = createNotification(context, notificationDetails); - NotificationManagerCompat notificationManagerCompat = getNotificationManager(context); - notificationManagerCompat.notify(notificationDetails.id, notification); - } - - private static NotificationManagerCompat getNotificationManager(Context context) { - return NotificationManagerCompat.from(context); - } - - private void setActivity(Activity flutterActivity) { - this.mainActivity = flutterActivity; - } - - private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { - this.applicationContext = context; - this.channel = new MethodChannel(binaryMessenger, METHOD_CHANNEL); - this.channel.setMethodCallHandler(this); - } - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger()); - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - } - - @Override - public void onAttachedToActivity(ActivityPluginBinding binding) { - binding.addOnNewIntentListener(this); - mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - this.mainActivity = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - binding.addOnNewIntentListener(this); - mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivity() { - this.mainActivity = null; - } - - @Override - public void onMethodCall(MethodCall call, Result result) { - switch (call.method) { - case INITIALIZE_METHOD: { - initialize(call, result); - break; - } - case GET_NOTIFICATION_APP_LAUNCH_DETAILS_METHOD: { - getNotificationAppLaunchDetails(result); - break; - } - case SHOW_METHOD: { - show(call, result); - break; - } - case SCHEDULE_METHOD: { - schedule(call, result); - break; - } - case PERIODICALLY_SHOW_METHOD: - case SHOW_DAILY_AT_TIME_METHOD: - case SHOW_WEEKLY_AT_DAY_AND_TIME_METHOD: { - repeat(call, result); - break; - } - case CANCEL_METHOD: - cancel(call, result); - break; - case CANCEL_ALL_METHOD: - cancelAllNotifications(result); - break; - case PENDING_NOTIFICATION_REQUESTS_METHOD: - pendingNotificationRequests(result); - break; - case CREATE_NOTIFICATION_CHANNEL_METHOD: - createNotificationChannel(call, result); - break; - case DELETE_NOTIFICATION_CHANNEL_METHOD: - deleteNotificationChannel(call, result); - break; - default: - result.notImplemented(); - break; - } - } - - private void pendingNotificationRequests(Result result) { - ArrayList scheduledNotifications = loadScheduledNotifications(applicationContext); - List> pendingNotifications = new ArrayList<>(); - - for (NotificationDetails scheduledNotification : scheduledNotifications) { - HashMap pendingNotification = new HashMap<>(); - pendingNotification.put("id", scheduledNotification.id); - pendingNotification.put("title", scheduledNotification.title); - pendingNotification.put("body", scheduledNotification.body); - pendingNotification.put("payload", scheduledNotification.payload); - pendingNotifications.add(pendingNotification); - } - result.success(pendingNotifications); - } - - private void cancel(MethodCall call, Result result) { - Integer id = call.arguments(); - cancelNotification(id); - result.success(null); - } - - private void repeat(MethodCall call, Result result) { - Map arguments = call.arguments(); - NotificationDetails notificationDetails = extractNotificationDetails(result, arguments); - if (notificationDetails != null) { - repeatNotification(applicationContext, notificationDetails, true); - result.success(null); - } - } - - private void schedule(MethodCall call, Result result) { - Map arguments = call.arguments(); - NotificationDetails notificationDetails = extractNotificationDetails(result, arguments); - if (notificationDetails != null) { - scheduleNotification(applicationContext, notificationDetails, true); - result.success(null); - } - } - - private void show(MethodCall call, Result result) { - Map arguments = call.arguments(); - NotificationDetails notificationDetails = extractNotificationDetails(result, arguments); - if (notificationDetails != null) { - showNotification(applicationContext, notificationDetails); - result.success(null); - } - } - - private void getNotificationAppLaunchDetails(Result result) { - Map notificationAppLaunchDetails = new HashMap<>(); - String payload = null; - Boolean notificationLaunchedApp = !initialized && mainActivity != null && SELECT_NOTIFICATION.equals(mainActivity.getIntent().getAction()); - notificationAppLaunchDetails.put(NOTIFICATION_LAUNCHED_APP, notificationLaunchedApp); - if (notificationLaunchedApp) { - payload = mainActivity.getIntent().getStringExtra(PAYLOAD); - } - notificationAppLaunchDetails.put(PAYLOAD, payload); - result.success(notificationAppLaunchDetails); - } - - private void initialize(MethodCall call, Result result) { - Map arguments = call.arguments(); - String defaultIcon = (String) arguments.get(DEFAULT_ICON); - if (!isValidDrawableResource(applicationContext, defaultIcon, result, INVALID_ICON_ERROR_CODE)) { - return; - } - SharedPreferences sharedPreferences = applicationContext.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(DEFAULT_ICON, defaultIcon); - editor.commit(); - - if (mainActivity != null) { - sendNotificationPayloadMessage(mainActivity.getIntent()); - } - initialized = true; - result.success(true); - } - - /// Extracts the details of the notifications passed from the Flutter side and also validates that some of the details (especially resources) passed are valid - private NotificationDetails extractNotificationDetails(Result result, Map arguments) { - NotificationDetails notificationDetails = NotificationDetails.from(arguments); - if (hasInvalidIcon(result, notificationDetails.icon) || - hasInvalidLargeIcon(result, notificationDetails.largeIcon, notificationDetails.largeIconBitmapSource) || - hasInvalidBigPictureResources(result, notificationDetails) || - hasInvalidRawSoundResource(result, notificationDetails) || - hasInvalidLedDetails(result, notificationDetails)) { - return null; - } - - return notificationDetails; - } - - private boolean hasInvalidLedDetails(Result result, NotificationDetails notificationDetails) { - if (notificationDetails.ledColor != null && (notificationDetails.ledOnMs == null || notificationDetails.ledOffMs == null)) { - result.error(INVALID_LED_DETAILS_ERROR_CODE, INVALID_LED_DETAILS_ERROR_MESSAGE, null); - return true; - } - return false; - } - - private boolean hasInvalidRawSoundResource(Result result, NotificationDetails notificationDetails) { - if (!StringUtils.isNullOrEmpty(notificationDetails.sound) && (notificationDetails.soundSource == null || notificationDetails.soundSource == SoundSource.RawResource)) { - int soundResourceId = applicationContext.getResources().getIdentifier(notificationDetails.sound, "raw", applicationContext.getPackageName()); - if (soundResourceId == 0) { - result.error(INVALID_SOUND_ERROR_CODE, INVALID_RAW_RESOURCE_ERROR_MESSAGE, null); - return true; - } - } - return false; - } - - private boolean hasInvalidBigPictureResources(Result result, NotificationDetails notificationDetails) { - if (notificationDetails.style == NotificationStyle.BigPicture) { - BigPictureStyleInformation bigPictureStyleInformation = (BigPictureStyleInformation) notificationDetails.styleInformation; - if (hasInvalidLargeIcon(result, bigPictureStyleInformation.largeIcon, bigPictureStyleInformation.largeIconBitmapSource)) - return true; - return bigPictureStyleInformation.bigPictureBitmapSource == BitmapSource.DrawableResource && !isValidDrawableResource(applicationContext, bigPictureStyleInformation.bigPicture, result, INVALID_BIG_PICTURE_ERROR_CODE); - } - return false; - } - - private boolean hasInvalidLargeIcon(Result result, String largeIcon, BitmapSource largeIconBitmapSource) { - return !StringUtils.isNullOrEmpty(largeIcon) && largeIconBitmapSource == BitmapSource.DrawableResource && !isValidDrawableResource(applicationContext, largeIcon, result, INVALID_LARGE_ICON_ERROR_CODE); - } - - private boolean hasInvalidIcon(Result result, String icon) { - return !StringUtils.isNullOrEmpty(icon) && !isValidDrawableResource(applicationContext, icon, result, INVALID_ICON_ERROR_CODE); - } - - private void cancelNotification(Integer id) { - Intent intent = new Intent(applicationContext, ScheduledNotificationReceiver.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast(applicationContext, id, intent, PendingIntent.FLAG_UPDATE_CURRENT); - AlarmManager alarmManager = getAlarmManager(applicationContext); - alarmManager.cancel(pendingIntent); - NotificationManagerCompat notificationManager = getNotificationManager(applicationContext); - notificationManager.cancel(id); - removeNotificationFromCache(id, applicationContext); - } - - private void cancelAllNotifications(Result result) { - NotificationManagerCompat notificationManager = getNotificationManager(applicationContext); - notificationManager.cancelAll(); - ArrayList scheduledNotifications = loadScheduledNotifications(applicationContext); - if (scheduledNotifications == null || scheduledNotifications.isEmpty()) { - result.success(null); - return; - } - - Intent intent = new Intent(applicationContext, ScheduledNotificationReceiver.class); - for (NotificationDetails scheduledNotification : - scheduledNotifications) { - PendingIntent pendingIntent = PendingIntent.getBroadcast(applicationContext, scheduledNotification.id, intent, PendingIntent.FLAG_UPDATE_CURRENT); - AlarmManager alarmManager = getAlarmManager(applicationContext); - alarmManager.cancel(pendingIntent); - } - - saveScheduledNotifications(applicationContext, new ArrayList()); - result.success(null); - } - - @Override - public boolean onNewIntent(Intent intent) { - boolean res = sendNotificationPayloadMessage(intent); - if (res && mainActivity != null) { - mainActivity.setIntent(intent); - } - return res; - } - - private Boolean sendNotificationPayloadMessage(Intent intent) { - if (SELECT_NOTIFICATION.equals(intent.getAction())) { - String payload = intent.getStringExtra(PAYLOAD); - channel.invokeMethod("selectNotification", payload); - return true; - } - return false; - } - - private void createNotificationChannel(MethodCall call, Result result) { - Map arguments = call.arguments(); - NotificationChannelDetails notificationChannelDetails = NotificationChannelDetails.from(arguments); - setupNotificationChannel(applicationContext, notificationChannelDetails); - result.success(null); - } - - private void deleteNotificationChannel(MethodCall call, Result result) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager notificationManager = (NotificationManager) applicationContext.getSystemService(Context.NOTIFICATION_SERVICE); - String channelId = call.arguments(); - notificationManager.deleteNotificationChannel(channelId); - result.success(null); - } - } -} diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 6700a389..ac17fc75 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,6 +1,8 @@ -