diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index 976f28700d..4062832075 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -135,7 +135,7 @@ import javax.mail.search.ReceivedDateTerm; import javax.mail.search.SearchTerm; import javax.mail.search.SentDateTerm; -import me.leolin.shortcutbadger.ShortcutBadger; +import me.leolin.shortcutbadger.ShortcutBadgerAlt; class Core { static final int DEFAULT_CHUNK_SIZE = 50; @@ -5453,7 +5453,7 @@ class Core { nm.notify(tag, NotificationHelper.NOTIFICATION_TAGGED, notification); // https://github.com/leolin310148/ShortcutBadger/wiki/Xiaomi-Device-Support if (id == 0 && badge && Helper.isXiaomi()) - ShortcutBadger.applyNotification(context, notification, current); + ShortcutBadgerAlt.applyNotification(context, notification, current); } catch (Throwable ex) { Log.w(ex); } diff --git a/app/src/main/java/eu/faircode/email/EntityLog.java b/app/src/main/java/eu/faircode/email/EntityLog.java index 304fb95dd3..1228b21198 100644 --- a/app/src/main/java/eu/faircode/email/EntityLog.java +++ b/app/src/main/java/eu/faircode/email/EntityLog.java @@ -67,7 +67,7 @@ public class EntityLog { public enum Type {General, Statistics, Scheduling, Network, Account, Protocol, Classification, Notification, Rules, Cloud, Debug} - static void log(final Context context, String data) { + public static void log(final Context context, String data) { log(context, Type.General, data); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 171f273c7d..19a7339276 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -109,7 +109,7 @@ import javax.mail.event.MessageCountEvent; import javax.mail.event.StoreEvent; import javax.mail.event.StoreListener; -import me.leolin.shortcutbadger.ShortcutBadger; +import me.leolin.shortcutbadger.ShortcutBadgerAlt; public class ServiceSynchronize extends ServiceBase implements SharedPreferences.OnSharedPreferenceChangeListener { private Network lastActive = null; @@ -938,9 +938,9 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences // Update badge try { if (count == 0 || !badge) - ShortcutBadger.removeCount(ServiceSynchronize.this); + ShortcutBadgerAlt.removeCount(ServiceSynchronize.this); else - ShortcutBadger.applyCount(ServiceSynchronize.this, count); + ShortcutBadgerAlt.applyCount(ServiceSynchronize.this, count); } catch (Throwable ex) { Log.e(ex); } diff --git a/app/src/main/java/me/leolin/shortcutbadger/ShortcutBadgerAlt.java b/app/src/main/java/me/leolin/shortcutbadger/ShortcutBadgerAlt.java new file mode 100644 index 0000000000..b8fc3d752f --- /dev/null +++ b/app/src/main/java/me/leolin/shortcutbadger/ShortcutBadgerAlt.java @@ -0,0 +1,260 @@ +package me.leolin.shortcutbadger; + +import android.app.Notification; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Build; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import me.leolin.shortcutbadger.impl.AdwHomeBadger; +import me.leolin.shortcutbadger.impl.ApexHomeBadger; +import me.leolin.shortcutbadger.impl.AsusHomeBadger; +import me.leolin.shortcutbadger.impl.DefaultBadger; +import me.leolin.shortcutbadger.impl.EverythingMeHomeBadger; +import me.leolin.shortcutbadger.impl.HuaweiHomeBadger; +import me.leolin.shortcutbadger.impl.NewHtcHomeBadger; +import me.leolin.shortcutbadger.impl.NovaHomeBadger; +import me.leolin.shortcutbadger.impl.OPPOHomeBader; +import me.leolin.shortcutbadger.impl.SamsungHomeBadger; +import me.leolin.shortcutbadger.impl.SonyHomeBadger; +import me.leolin.shortcutbadger.impl.VivoHomeBadger; +import me.leolin.shortcutbadger.impl.ZTEHomeBadger; +import me.leolin.shortcutbadger.impl.ZukHomeBadger; + + +/** + * @author Leo Lin + */ +public final class ShortcutBadgerAlt { + + private static final String LOG_TAG = "ShortcutBadger"; + private static final int SUPPORTED_CHECK_ATTEMPTS = 3; + + private static final List> BADGERS = new LinkedList>(); + + private volatile static Boolean sIsBadgeCounterSupported; + private final static Object sCounterSupportedLock = new Object(); + + static { + BADGERS.add(AdwHomeBadger.class); + BADGERS.add(ApexHomeBadger.class); + BADGERS.add(DefaultBadger.class); + BADGERS.add(NewHtcHomeBadger.class); + BADGERS.add(NovaHomeBadger.class); + BADGERS.add(SonyHomeBadger.class); + BADGERS.add(AsusHomeBadger.class); + BADGERS.add(HuaweiHomeBadger.class); + BADGERS.add(OPPOHomeBader.class); + BADGERS.add(SamsungHomeBadger.class); + BADGERS.add(ZukHomeBadger.class); + BADGERS.add(VivoHomeBadger.class); + BADGERS.add(ZTEHomeBadger.class); + BADGERS.add(EverythingMeHomeBadger.class); + } + + private static Badger sShortcutBadger; + private static ComponentName sComponentName; + + /** + * Tries to update the notification count + * + * @param context Caller context + * @param badgeCount Desired badge count + * @return true in case of success, false otherwise + */ + public static boolean applyCount(Context context, int badgeCount) { + try { + applyCountOrThrow(context, badgeCount); + eu.faircode.email.EntityLog.log(context, "Badger applied" + + " count=" + badgeCount + + " badger=" + sShortcutBadger.getClass().getSimpleName()); + return true; + } catch (ShortcutBadgeException e) { + eu.faircode.email.EntityLog.log(context, "Badger not applied" + + " count=" + badgeCount + + " badger=" + sShortcutBadger.getClass().getSimpleName() + + " reason=" + (e.getCause() == null ? e.getMessage() : e.getCause().getMessage())); + if (!(sShortcutBadger instanceof DefaultBadger)) { + Throwable ex = new Throwable("Badger=" + sShortcutBadger.getClass(), e); + eu.faircode.email.Log.e(ex); + eu.faircode.email.EntityLog.log(context, ex + "\n" + Log.getStackTraceString(ex)); + } + if (Log.isLoggable(LOG_TAG, Log.DEBUG)) { + Log.d(LOG_TAG, "Unable to execute badge", e); + } + return false; + } + } + + /** + * Tries to update the notification count, throw a {@link ShortcutBadgeException} if it fails + * + * @param context Caller context + * @param badgeCount Desired badge count + */ + public static void applyCountOrThrow(Context context, int badgeCount) throws ShortcutBadgeException { + if (sShortcutBadger == null) { + boolean launcherReady = initBadger(context); + + if (!launcherReady) + throw new ShortcutBadgeException("No default launcher available"); + } + + try { + sShortcutBadger.executeBadge(context, sComponentName, badgeCount); + } catch (Exception e) { + throw new ShortcutBadgeException("Unable to execute badge", e); + } + } + + /** + * Tries to remove the notification count + * + * @param context Caller context + * @return true in case of success, false otherwise + */ + public static boolean removeCount(Context context) { + return applyCount(context, 0); + } + + /** + * Tries to remove the notification count, throw a {@link ShortcutBadgeException} if it fails + * + * @param context Caller context + */ + public static void removeCountOrThrow(Context context) throws ShortcutBadgeException { + applyCountOrThrow(context, 0); + } + + /** + * Whether this platform launcher supports shortcut badges. Doing this check causes the side + * effect of resetting the counter if it's supported, so this method should be followed by + * a call that actually sets the counter to the desired value, if the counter is supported. + */ + public static boolean isBadgeCounterSupported(Context context) { + // Checking outside synchronized block to avoid synchronization in the common case (flag + // already set), and improve perf. + if (sIsBadgeCounterSupported == null) { + synchronized (sCounterSupportedLock) { + // Checking again inside synch block to avoid setting the flag twice. + if (sIsBadgeCounterSupported == null) { + String lastErrorMessage = null; + for (int i = 0; i < SUPPORTED_CHECK_ATTEMPTS; i++) { + try { + Log.i(LOG_TAG, "Checking if platform supports badge counters, attempt " + + String.format("%d/%d.", i + 1, SUPPORTED_CHECK_ATTEMPTS)); + if (initBadger(context)) { + sShortcutBadger.executeBadge(context, sComponentName, 0); + sIsBadgeCounterSupported = true; + Log.i(LOG_TAG, "Badge counter is supported in this platform."); + break; + } else { + lastErrorMessage = "Failed to initialize the badge counter."; + } + } catch (Exception e) { + // Keep retrying as long as we can. No need to dump the stack trace here + // because this error will be the norm, not exception, for unsupported + // platforms. So we just save the last error message to display later. + lastErrorMessage = e.getMessage(); + } + } + + if (sIsBadgeCounterSupported == null) { + Log.w(LOG_TAG, "Badge counter seems not supported for this platform: " + + lastErrorMessage); + sIsBadgeCounterSupported = false; + } + } + } + } + return sIsBadgeCounterSupported; + } + + /** + * @param context Caller context + * @param notification + * @param badgeCount + */ + public static void applyNotification(Context context, Notification notification, int badgeCount) { + if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { + try { + Field field = notification.getClass().getDeclaredField("extraNotification"); + Object extraNotification = field.get(notification); + Method method = extraNotification.getClass().getDeclaredMethod("setMessageCount", int.class); + method.invoke(extraNotification, badgeCount); + } catch (Exception e) { + if (Log.isLoggable(LOG_TAG, Log.DEBUG)) { + Log.d(LOG_TAG, "Unable to execute badge", e); + } + } + } + } + + // Initialize Badger if a launcher is availalble (eg. set as default on the device) + // Returns true if a launcher is available, in this case, the Badger will be set and sShortcutBadger will be non null. + private static boolean initBadger(Context context) { + Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); + if (launchIntent == null) { + Log.e(LOG_TAG, "Unable to find launch intent for package " + context.getPackageName()); + return false; + } + + sComponentName = launchIntent.getComponent(); + + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + List resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + + for (ResolveInfo resolveInfo : resolveInfos) { + String currentHomePackage = resolveInfo.activityInfo.packageName; + + for (Class badger : BADGERS) { + Badger shortcutBadger = null; + try { + shortcutBadger = badger.newInstance(); + } catch (Exception ignored) { + } + if (shortcutBadger != null && shortcutBadger.getSupportLaunchers().contains(currentHomePackage)) { + sShortcutBadger = shortcutBadger; + break; + } + } + if (sShortcutBadger != null) { + break; + } + } + + eu.faircode.email.EntityLog.log(context, "Badger selected=" + + (sShortcutBadger == null ? null : sShortcutBadger.getClass())); + + if (sShortcutBadger == null) { + if (Build.MANUFACTURER.equalsIgnoreCase("ZUK")) + sShortcutBadger = new ZukHomeBadger(); + else if (Build.MANUFACTURER.equalsIgnoreCase("OPPO")) + sShortcutBadger = new OPPOHomeBader(); + else if (Build.MANUFACTURER.equalsIgnoreCase("VIVO")) + sShortcutBadger = new VivoHomeBadger(); + else if (Build.MANUFACTURER.equalsIgnoreCase("ZTE")) + sShortcutBadger = new ZTEHomeBadger(); + else + sShortcutBadger = new DefaultBadger(); + } + + eu.faircode.email.EntityLog.log(context, "Badger using=" + sShortcutBadger.getClass()); + + return true; + } + + // Avoid anybody to instantiate this class + private ShortcutBadgerAlt() { + + } +} \ No newline at end of file