diff --git a/app/src/main/java/org/dystopia/email/FragmentAbout.java b/app/src/main/java/org/dystopia/email/FragmentAbout.java index fa8bffd0..a72fcc46 100644 --- a/app/src/main/java/org/dystopia/email/FragmentAbout.java +++ b/app/src/main/java/org/dystopia/email/FragmentAbout.java @@ -36,6 +36,9 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.FragmentTransaction; + +import org.dystopia.email.util.CompatibilityUtils; + import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; @@ -69,7 +72,9 @@ public class FragmentAbout extends FragmentEx { btnLog = view.findViewById(R.id.btnLog); btnDebugInfo = view.findViewById(R.id.btnDebugInfo); - tvVersion.setText(getString(R.string.title_version, BuildConfig.VERSION_NAME)); + int version = R.string.title_version; + String versionName = getString(version, BuildConfig.VERSION_NAME); + tvVersion.setText(versionName); btnLog.setOnClickListener( new View.OnClickListener() { @@ -126,8 +131,7 @@ public class FragmentAbout extends FragmentEx { sb.append(String.format(locale, "Id: %s\r\n", Build.ID)); sb.append("\r\n"); - PowerManager pm = getContext().getSystemService(PowerManager.class); - boolean ignoring = pm.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID); + boolean ignoring = CompatibilityUtils.isIgnoringOptimizations(getContext()); sb.append(String.format(locale, "Battery optimizations: %b\r\n", !ignoring)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { diff --git a/app/src/main/java/org/dystopia/email/FragmentCompose.java b/app/src/main/java/org/dystopia/email/FragmentCompose.java index 659defdf..0f220263 100644 --- a/app/src/main/java/org/dystopia/email/FragmentCompose.java +++ b/app/src/main/java/org/dystopia/email/FragmentCompose.java @@ -100,6 +100,8 @@ import javax.mail.Session; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; + +import org.dystopia.email.util.CompatibilityUtils; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; @@ -483,9 +485,9 @@ public class FragmentCompose extends FragmentEx { break; case R.id.menu_link: Uri uri = null; - ClipboardManager cbm = getContext().getSystemService(ClipboardManager.class); - if (cbm.hasPrimaryClip()) { - String link = cbm.getPrimaryClip().getItemAt(0).coerceToText(getContext()).toString(); + ClipboardManager clipboardManager = CompatibilityUtils.getClipboardManager(getContext()); + if (clipboardManager.hasPrimaryClip()) { + String link = clipboardManager.getPrimaryClip().getItemAt(0).coerceToText(getContext()).toString(); uri = Uri.parse(link); if (uri.getScheme() == null) { uri = null; diff --git a/app/src/main/java/org/dystopia/email/FragmentEx.java b/app/src/main/java/org/dystopia/email/FragmentEx.java index 34670dc7..d93c8f48 100644 --- a/app/src/main/java/org/dystopia/email/FragmentEx.java +++ b/app/src/main/java/org/dystopia/email/FragmentEx.java @@ -20,6 +20,7 @@ package org.dystopia.email; Copyright 2018-2020, Distopico (dystopia project) and contributors */ +import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; @@ -32,6 +33,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.lifecycle.Lifecycle; +import org.dystopia.email.util.CompatibilityUtils; + public class FragmentEx extends Fragment { private String title = ""; private String subtitle = " "; @@ -113,10 +116,10 @@ public class FragmentEx extends Fragment { public void onDetach() { super.onDetach(); - InputMethodManager im = getContext().getSystemService(InputMethodManager.class); + InputMethodManager inputMethodManager = CompatibilityUtils.getInputMethodManager(getContext()); View focused = getActivity().getCurrentFocus(); if (focused != null) { - im.hideSoftInputFromWindow(focused.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); + inputMethodManager.hideSoftInputFromWindow(focused.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); } } diff --git a/app/src/main/java/org/dystopia/email/FragmentSetup.java b/app/src/main/java/org/dystopia/email/FragmentSetup.java index de004004..04b499f9 100644 --- a/app/src/main/java/org/dystopia/email/FragmentSetup.java +++ b/app/src/main/java/org/dystopia/email/FragmentSetup.java @@ -67,6 +67,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import org.dystopia.email.util.CompatibilityUtils; import org.json.JSONArray; import org.json.JSONObject; @@ -369,8 +370,14 @@ public class FragmentSetup extends FragmentEx { public void onResume() { super.onResume(); - PowerManager pm = getContext().getSystemService(PowerManager.class); - boolean ignoring = pm.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID); + boolean ignoring = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Intent intent = new Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS); + PackageManager packageManager = getContext().getPackageManager(); + if (intent.resolveActivity(packageManager) != null) { // system whitelisted + ignoring = CompatibilityUtils.isIgnoringOptimizations(getContext()); + } + } btnDoze.setEnabled(!ignoring); tvDozeDone.setText(ignoring ? R.string.title_setup_done : R.string.title_setup_to_do); tvDozeDone.setCompoundDrawablesWithIntrinsicBounds(ignoring ? check : null, null, null, null); diff --git a/app/src/main/java/org/dystopia/email/JobDaily.java b/app/src/main/java/org/dystopia/email/JobDaily.java index af7b0a13..89dba059 100644 --- a/app/src/main/java/org/dystopia/email/JobDaily.java +++ b/app/src/main/java/org/dystopia/email/JobDaily.java @@ -26,6 +26,9 @@ import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.util.Log; + +import org.dystopia.email.util.CompatibilityUtils; + import java.io.File; import java.util.Date; import java.util.concurrent.ExecutorService; @@ -45,7 +48,7 @@ public class JobDaily extends JobService { .setPeriodic(CLEANUP_INTERVAL) .setRequiresDeviceIdle(true); - JobScheduler scheduler = context.getSystemService(JobScheduler.class); + JobScheduler scheduler = CompatibilityUtils.getJobScheduler(context); scheduler.cancel(Helper.JOB_DAILY); if (scheduler.schedule(job.build()) == JobScheduler.RESULT_SUCCESS) { Log.i(Helper.TAG, "Scheduled daily job"); diff --git a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java index a6727511..60581014 100644 --- a/app/src/main/java/org/dystopia/email/ServiceSynchronize.java +++ b/app/src/main/java/org/dystopia/email/ServiceSynchronize.java @@ -114,6 +114,8 @@ import javax.mail.internet.MimeMessage; import javax.mail.search.ComparisonTerm; import javax.mail.search.ReceivedDateTerm; import javax.net.ssl.SSLException; + +import org.dystopia.email.util.CompatibilityUtils; import org.json.JSONArray; import org.json.JSONException; @@ -160,8 +162,8 @@ public class ServiceSynchronize extends LifecycleService { db.account().liveStats().observe(this, new Observer() { @Override public void onChanged(@Nullable TupleAccountStats stats) { - NotificationManager nm = getSystemService(NotificationManager.class); - nm.notify(NOTIFICATION_SYNCHRONIZE, getNotificationService(stats).build()); + NotificationManager notificationManager = CompatibilityUtils.getNotificationManger(getBaseContext()); + notificationManager.notify(NOTIFICATION_SYNCHRONIZE, getNotificationService(stats).build()); } }); @@ -171,7 +173,7 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onChanged(List messages) { - NotificationManager nm = getSystemService(NotificationManager.class); + NotificationManager notificationManager = CompatibilityUtils.getNotificationManger(getBaseContext()); LongSparseArray> messagesByAccount = new LongSparseArray<>(); LongSparseArray> removed = notifying.clone(); @@ -179,7 +181,7 @@ public class ServiceSynchronize extends LifecycleService { setWidgetUnseen(messages); if (messages.size() == 0) { - nm.cancelAll(); + notificationManager.cancelAll(); return; } @@ -232,7 +234,7 @@ public class ServiceSynchronize extends LifecycleService { Integer id = (int) notification.extras.getLong("id", 0); if ((id == 0 && added.size() > 0) || added.contains(id)) { - nm.notify(tag, id, notification); + notificationManager.notify(tag, id, notification); } } @@ -247,7 +249,7 @@ public class ServiceSynchronize extends LifecycleService { String tag = "unseen-" + accountId; for (Integer id : notifyRemove) { - nm.cancel(tag, id); + notificationManager.cancel(tag, id); } } } @@ -267,8 +269,8 @@ public class ServiceSynchronize extends LifecycleService { stopForeground(true); - NotificationManager nm = getSystemService(NotificationManager.class); - nm.cancel(NOTIFICATION_SYNCHRONIZE); + NotificationManager notificationManager = CompatibilityUtils.getNotificationManger(this); + notificationManager.cancel(NOTIFICATION_SYNCHRONIZE); super.onDestroy(); } @@ -574,10 +576,10 @@ public class ServiceSynchronize extends LifecycleService { PendingIntent piTrash = PendingIntent.getService(this, PI_TRASH, trash, PendingIntent.FLAG_UPDATE_CURRENT); Notification.Action.Builder actionSeen = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.baseline_visibility_24), getString(R.string.title_seen), piSeen); + R.drawable.baseline_visibility_24, getString(R.string.title_seen), piSeen); Notification.Action.Builder actionTrash = new Notification.Action.Builder( - Icon.createWithResource(this, R.drawable.baseline_delete_24), getString(R.string.title_trash), piTrash); + R.drawable.baseline_delete_24, getString(R.string.title_trash), piTrash); Notification.Builder mbuilder = Helper.getNotificationBuilder(this, channelId); Notification.InboxStyle mstyle = new Notification.InboxStyle(); @@ -652,8 +654,8 @@ public class ServiceSynchronize extends LifecycleService { EntityLog.log(this, action + " " + Helper.formatThrowable(ex)); if (ex instanceof SendFailedException) { - NotificationManager nm = getSystemService(NotificationManager.class); - nm.notify(action, 1, getNotificationError(action, ex).build()); + NotificationManager notificationManager = CompatibilityUtils.getNotificationManger(this); + notificationManager.notify(action, 1, getNotificationError(action, ex).build()); } if (BuildConfig.DEBUG && !(ex instanceof SendFailedException) && !(ex instanceof MailConnectException) @@ -666,14 +668,14 @@ public class ServiceSynchronize extends LifecycleService { && !(ex instanceof MessagingException && ex.getCause() instanceof SocketTimeoutException) && !(ex instanceof MessagingException && ex.getCause() instanceof SSLException) && !(ex instanceof MessagingException && "connection failure".equals(ex.getMessage()))) { - NotificationManager nm = getSystemService(NotificationManager.class); - nm.notify(action, 1, getNotificationError(action, ex).build()); + NotificationManager notificationManager = CompatibilityUtils.getNotificationManger(this); + notificationManager.notify(action, 1, getNotificationError(action, ex).build()); } } private void monitorAccount(final EntityAccount account, final ServiceState state) throws NoSuchProviderException { - final PowerManager pm = getSystemService(PowerManager.class); - final PowerManager.WakeLock wl0 = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + final PowerManager powerManager = CompatibilityUtils.getPowerManager(getBaseContext()); + final PowerManager.WakeLock wl0 = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":account." + account.id + ".monitor"); try { wl0.acquire(); @@ -702,7 +704,7 @@ public class ServiceSynchronize extends LifecycleService { try { // Listen for store events istore.addStoreListener(new StoreListener() { - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":account." + account.id + ".store"); @Override @@ -721,7 +723,7 @@ public class ServiceSynchronize extends LifecycleService { // Listen for folder events istore.addFolderListener(new FolderAdapter() { - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":account." + account.id + ".folder"); @Override @@ -822,7 +824,7 @@ public class ServiceSynchronize extends LifecycleService { // Synchronize folder Thread sync = new Thread(new Runnable() { - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":account." + account.id + ".sync"); @Override @@ -1033,7 +1035,7 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onReceive(Context context, final Intent intent) { executor.submit(new Runnable() { - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":account." + account.id + ".process"); @Override @@ -1150,13 +1152,13 @@ public class ServiceSynchronize extends LifecycleService { registerReceiver(alarm, new IntentFilter(id)); // Keep alive - AlarmManager am = getSystemService(AlarmManager.class); + AlarmManager alarmManager = CompatibilityUtils.getAlarmManager(this); try { while (state.running) { // Schedule keep alive alarm EntityLog.log(this, account.name + " wait=" + account.poll_interval); - am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + account.poll_interval * 60 * 1000L, pi); + CompatibilityUtils.setAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + account.poll_interval * 60 * 1000L, pi); try { wl0.release(); @@ -1185,7 +1187,7 @@ public class ServiceSynchronize extends LifecycleService { } } finally { // Cleanup - am.cancel(pi); + alarmManager.cancel(pi); unregisterReceiver(alarm); lbm.unregisterReceiver(processFolder); } @@ -2027,8 +2029,8 @@ public class ServiceSynchronize extends LifecycleService { List attachments = db.attachment().getAttachments(message.id); MessageHelper helper = new MessageHelper(imessage); - ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); - boolean metered = (cm == null || cm.isActiveNetworkMetered()); + ConnectivityManager connectivityManager = CompatibilityUtils.getConnectivityManager(context); + boolean metered = (connectivityManager == null || connectivityManager.isActiveNetworkMetered()); boolean fetch = false; if (!message.content) { @@ -2102,8 +2104,8 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onAvailable(Network network) { - ConnectivityManager cm = getSystemService(ConnectivityManager.class); - NetworkInfo ni = cm.getNetworkInfo(network); + ConnectivityManager connectivityManager = CompatibilityUtils.getConnectivityManager(ServiceSynchronize.this); + NetworkInfo ni = connectivityManager.getNetworkInfo(network); EntityLog.log(ServiceSynchronize.this, "Network available " + network + " running=" + running + " " + ni); @@ -2124,8 +2126,8 @@ public class ServiceSynchronize extends LifecycleService { EntityLog.log(ServiceSynchronize.this, "Network lost " + network + " running=" + running); if (running) { - ConnectivityManager cm = getSystemService(ConnectivityManager.class); - NetworkInfo ani = (network == null ? null : cm.getActiveNetworkInfo()); + ConnectivityManager connectivityManager = CompatibilityUtils.getConnectivityManager(ServiceSynchronize.this); + NetworkInfo ani = (network == null ? null : connectivityManager.getActiveNetworkInfo()); EntityLog.log(ServiceSynchronize.this, "Network active=" + (ani == null ? null : ani.toString())); if (ani == null || !ani.isConnected()) { @@ -2151,9 +2153,9 @@ public class ServiceSynchronize extends LifecycleService { @Override public void run() { - PowerManager pm = getSystemService(PowerManager.class); + PowerManager powerManager = CompatibilityUtils.getPowerManager(ServiceSynchronize.this); PowerManager.WakeLock wl = - pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":start"); + powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":start"); try { wl.acquire(); @@ -2259,9 +2261,9 @@ public class ServiceSynchronize extends LifecycleService { } private void stop() { - PowerManager pm = getSystemService(PowerManager.class); + PowerManager powerManager = CompatibilityUtils.getPowerManager(ServiceSynchronize.this); PowerManager.WakeLock wl = - pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":stop"); + powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":stop"); try { wl.acquire(); EntityLog.log(ServiceSynchronize.this, "Main stop"); @@ -2324,8 +2326,8 @@ public class ServiceSynchronize extends LifecycleService { executor.submit(new Runnable() { @Override public void run() { - PowerManager pm = getSystemService(PowerManager.class); - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, + PowerManager powerManager = CompatibilityUtils.getPowerManager(context); + PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":outbox"); try { diff --git a/app/src/main/java/org/dystopia/email/util/CompatibilityUtils.java b/app/src/main/java/org/dystopia/email/util/CompatibilityUtils.java new file mode 100644 index 00000000..157633e4 --- /dev/null +++ b/app/src/main/java/org/dystopia/email/util/CompatibilityUtils.java @@ -0,0 +1,104 @@ +package org.dystopia.email.util; + +/* + This file is part of SimpleEmail. + + SimpleEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SimpleEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SimpleEmail. If not, see . + + Copyright 2018-2020, Distopico (dystopia project) and contributors +*/ + +import android.app.AlarmManager; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.job.JobScheduler; +import android.content.ClipboardManager; +import android.content.Context; +import android.net.ConnectivityManager; +import android.os.Build; +import android.os.PowerManager; +import android.view.inputmethod.InputMethodManager; + +import org.dystopia.email.BuildConfig; + +public class CompatibilityUtils { + + static public NotificationManager getNotificationManger(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(NotificationManager.class); + } + return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + } + + static public PowerManager getPowerManager(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(PowerManager.class); + } + return (PowerManager) context.getSystemService(Context.POWER_SERVICE); + } + + static public AlarmManager getAlarmManager(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(AlarmManager.class); + } + return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + } + + static public ConnectivityManager getConnectivityManager(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(ConnectivityManager.class); + } + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + + static public InputMethodManager getInputMethodManager(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(InputMethodManager.class); + } + return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + } + + static public JobScheduler getJobScheduler(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return context.getSystemService(JobScheduler.class); + } else { + return (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + } + } + + static public ClipboardManager getClipboardManager(Context context) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + return context.getSystemService(ClipboardManager.class); + } + return (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + } + + static public Boolean isIgnoringOptimizations(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + PowerManager powerManager = getPowerManager(context); + if (powerManager != null) { + return powerManager.isIgnoringBatteryOptimizations(BuildConfig.APPLICATION_ID); + } + } + return true; + } + + static public void setAndAllowWhileIdle(AlarmManager alarmManager, int type, long triggerAtMillis, PendingIntent operation) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmManager.setAndAllowWhileIdle(type, triggerAtMillis, operation); + return; + } + alarmManager.setExact(type, triggerAtMillis, operation); + } +}