[android] Remove legacy notifications implementation

Use NotificationManagerCompat from the compat library

Signed-off-by: Roman Tsisyk <roman@tsisyk.com>
This commit is contained in:
Roman Tsisyk 2023-08-29 11:02:05 +03:00
parent e19f11b59d
commit 677b6b5906
12 changed files with 158 additions and 269 deletions

View file

@ -86,6 +86,7 @@ dependencies {
// > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
// We don't use Kotlin, but some dependencies are actively using it.
implementation(platform('org.jetbrains.kotlin:kotlin-bom:1.8.21'))
implementation 'androidx.core:core:1.10.1'
implementation 'androidx.annotation:annotation:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

View file

@ -125,7 +125,7 @@
<color name="bg_editor_light">#FFF2F6F6</color>
<color name="bg_editor_light_pressed">#3D000000</color>
<color name="notification">#82E510</color>
<color name="notification">@color/bg_primary</color>
<color name="yellow">#FFC800</color>
<color name="elevation_profile_dot_enabled">#FF9600</color>

View file

@ -8,6 +8,7 @@ import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
@ -37,7 +38,7 @@ import androidx.lifecycle.ViewModelProvider;
import app.organicmaps.Framework.PlacePageActivationListener;
import app.organicmaps.api.Const;
import app.organicmaps.background.AppBackgroundTracker;
import app.organicmaps.background.Notifier;
import app.organicmaps.downloader.DownloaderNotifier;
import app.organicmaps.base.BaseMwmFragmentActivity;
import app.organicmaps.base.CustomNavigateUpListener;
import app.organicmaps.base.NoConnectionListener;
@ -110,6 +111,7 @@ import java.util.Stack;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static app.organicmaps.location.LocationState.LOCATION_TAG;
@ -206,6 +208,10 @@ public class MwmActivity extends BaseMwmFragmentActivity
@NonNull
private ActivityResultLauncher<String[]> mLocationPermissionRequest;
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
private ActivityResultLauncher<String> mPostNotificationPermissionRequest;
@SuppressWarnings("NotNullFieldNotInitialized")
@NonNull
private ActivityResultLauncher<IntentSenderRequest> mLocationResolutionRequest;
@ -411,6 +417,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
this::onLocationPermissionsResult);
mLocationResolutionRequest = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(),
this::onLocationResolutionResult);
mPostNotificationPermissionRequest = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
this::onPostNotificationPermissionResult);
boolean isConsumed = savedInstanceState == null && processIntent(getIntent());
boolean isFirstLaunch = Counters.isFirstLaunch(this);
@ -943,8 +951,7 @@ public class MwmActivity extends BaseMwmFragmentActivity
if (intent == null)
return false;
final Notifier notifier = Notifier.from(getApplication());
notifier.processNotificationExtras(intent);
DownloaderNotifier.processNotificationExtras(getApplicationContext(), intent);
if (intent.hasExtra(EXTRA_TASK))
{
@ -1087,6 +1094,8 @@ public class MwmActivity extends BaseMwmFragmentActivity
mLocationPermissionRequest = null;
mLocationResolutionRequest.unregister();
mLocationResolutionRequest = null;
mPostNotificationPermissionRequest.unregister();
mPostNotificationPermissionRequest = null;
}
@Override
@ -1765,6 +1774,22 @@ public class MwmActivity extends BaseMwmFragmentActivity
});
}
/**
* Request POST_NOTIFICATIONS permission.
*/
public void requestPostNotificationsPermission()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ActivityCompat.checkSelfPermission(this, POST_NOTIFICATIONS) == PERMISSION_GRANTED)
{
Logger.i(TAG, "Permissions POST_NOTIFICATIONS is granted");
return;
}
Logger.i(TAG, "Requesting POST_NOTIFICATIONS permission");
mPostNotificationPermissionRequest.launch(POST_NOTIFICATIONS);
}
/**
* Resume location services when entering the foreground.
*/
@ -1861,6 +1886,19 @@ public class MwmActivity extends BaseMwmFragmentActivity
.show();
}
/**
* Called on the result of the POST_NOTIFICATIONS request.
* @param granted true if permission has been granted.
*/
@UiThread
private void onPostNotificationPermissionResult(boolean granted)
{
if (granted)
Logger.i(TAG, "Permission POST_NOTIFICATIONS has been granted");
else
Logger.w(TAG, "Permission POST_NOTIFICATIONS has been refused");
}
/**
* Called by GoogleFusedLocationProvider to request to GPS and/or Wi-Fi.
* @param pendingIntent an intent to launch.

View file

@ -8,9 +8,7 @@ import android.os.Message;
import androidx.annotation.NonNull;
import app.organicmaps.background.AppBackgroundTracker;
import app.organicmaps.background.NotificationChannelFactory;
import app.organicmaps.background.NotificationChannelProvider;
import app.organicmaps.background.Notifier;
import app.organicmaps.downloader.DownloaderNotifier;
import app.organicmaps.base.MediaPlayerWrapper;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.downloader.CountryItem;
@ -116,7 +114,7 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
ConnectionState.INSTANCE.initialize(this);
CrashlyticsUtils.INSTANCE.initialize(this);
initNotificationChannels();
DownloaderNotifier.createNotificationChannel(this);
mBackgroundTracker = new AppBackgroundTracker(this);
mSubwayManager = new SubwayManager(this);
@ -125,12 +123,6 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
mPlayer = new MediaPlayerWrapper(this);
}
private void initNotificationChannels()
{
NotificationChannelProvider channelProvider = NotificationChannelFactory.createProvider(this);
channelProvider.setDownloadingChannel();
}
/**
* Initialize native core of application: platform and framework.
*
@ -281,13 +273,12 @@ public class MwmApplication extends Application implements AppBackgroundTracker.
@Override
public void onStatusChanged(List<MapManager.StorageCallbackData> data)
{
Notifier notifier = Notifier.from(MwmApplication.this);
for (MapManager.StorageCallbackData item : data)
if (item.isLeafNode && item.newStatus == CountryItem.STATUS_FAILED)
{
if (MapManager.nativeIsAutoretryFailed())
{
notifier.notifyDownloadFailed(item.countryId, MapManager.nativeGetName(item.countryId));
DownloaderNotifier.notifyDownloadFailed(MwmApplication.this, item.countryId);
}
return;

View file

@ -1,17 +0,0 @@
package app.organicmaps.background;
import android.app.Application;
import androidx.annotation.NonNull;
import app.organicmaps.util.Utils;
public class NotificationChannelFactory
{
@NonNull
public static NotificationChannelProvider createProvider(@NonNull Application app)
{
return Utils.isOreoOrLater() ? new OreoCompatNotificationChannelProvider(app)
: new StubNotificationChannelProvider(app);
}
}

View file

@ -1,11 +0,0 @@
package app.organicmaps.background;
import androidx.annotation.NonNull;
public interface NotificationChannelProvider
{
@NonNull
String getDownloadingChannel();
void setDownloadingChannel();
}

View file

@ -1,127 +0,0 @@
package app.organicmaps.background;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.util.StringUtils;
import app.organicmaps.util.UiUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public final class Notifier
{
private static final String EXTRA_CANCEL_NOTIFICATION = "extra_cancel_notification";
private static final String EXTRA_NOTIFICATION_CLICKED = "extra_notification_clicked";
public static final int ID_NONE = 0;
public static final int ID_DOWNLOAD_FAILED = 1;
public static final int ID_IS_NOT_AUTHENTICATED = 2;
public static final int ID_LEAVE_REVIEW = 3;
@NonNull
private final Application mContext;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ ID_NONE, ID_DOWNLOAD_FAILED, ID_IS_NOT_AUTHENTICATED, ID_LEAVE_REVIEW })
public @interface NotificationId
{
}
private Notifier(@NonNull Application context)
{
mContext = context;
}
public void notifyDownloadFailed(@Nullable String id, @Nullable String name)
{
String title = mContext.getString(R.string.app_name);
String content = mContext.getString(R.string.download_country_failed, name);
Intent intent = MwmActivity.createShowMapIntent(mContext, id)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final int flags = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT;
PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, flags);
String channel = NotificationChannelFactory.createProvider(mContext).getDownloadingChannel();
placeNotification(title, content, pi, ID_DOWNLOAD_FAILED, channel);
}
public void cancelNotification(@NotificationId int id)
{
if (id == ID_NONE)
return;
getNotificationManager().cancel(id);
}
public void processNotificationExtras(@Nullable Intent intent)
{
if (intent == null)
return;
if (intent.hasExtra(Notifier.EXTRA_CANCEL_NOTIFICATION))
{
@Notifier.NotificationId
int notificationId = intent.getIntExtra(Notifier.EXTRA_CANCEL_NOTIFICATION, Notifier.ID_NONE);
cancelNotification(notificationId);
}
}
private void placeNotification(String title, String content, PendingIntent pendingIntent,
int notificationId, @NonNull String channel)
{
final Notification notification = getBuilder(title, content, pendingIntent, channel).build();
getNotificationManager().notify(notificationId, notification);
}
@NonNull
private NotificationCompat.Builder getBuilder(String title, String content,
PendingIntent pendingIntent, @NonNull String channel)
{
return new NotificationCompat.Builder(mContext, channel)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(mContext, R.color.notification))
.setContentTitle(title)
.setContentText(content)
.setTicker(getTicker(title, content))
.setContentIntent(pendingIntent);
}
@NonNull
private CharSequence getTicker(String title, String content)
{
int templateResId = StringUtils.isRtl() ? R.string.notification_ticker_rtl
: R.string.notification_ticker_ltr;
return mContext.getString(templateResId, title, content);
}
@SuppressWarnings("ConstantConditions")
@NonNull
private NotificationManager getNotificationManager()
{
return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
@NonNull
public static Notifier from(Application application)
{
return new Notifier(application);
}
}

View file

@ -1,43 +0,0 @@
package app.organicmaps.background;
import android.annotation.TargetApi;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import androidx.annotation.NonNull;
import app.organicmaps.R;
import java.util.Objects;
@TargetApi(Build.VERSION_CODES.O)
public class OreoCompatNotificationChannelProvider extends StubNotificationChannelProvider
{
private static final String DOWNLOADING_NOTIFICATION_CHANNEL = "downloading_notification_channel";
OreoCompatNotificationChannelProvider(@NonNull Application app)
{
super(app, DOWNLOADING_NOTIFICATION_CHANNEL);
}
private void setChannelInternal(@NonNull String id, @NonNull String name)
{
NotificationManager notificationManager = getApplication().getSystemService(NotificationManager.class);
NotificationChannel channel = Objects.requireNonNull(notificationManager)
.getNotificationChannel(id);
if (channel == null)
channel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_DEFAULT);
else
channel.setName(name);
notificationManager.createNotificationChannel(channel);
}
@Override
public void setDownloadingChannel()
{
String name = getApplication().getString(R.string.notification_channel_downloader);
setChannelInternal(getDownloadingChannel(), name);
}
}

View file

@ -1,47 +0,0 @@
package app.organicmaps.background;
import android.app.Application;
import androidx.annotation.NonNull;
public class StubNotificationChannelProvider implements NotificationChannelProvider
{
private static final String DEFAULT_NOTIFICATION_CHANNEL = "default_notification_channel";
@NonNull
private final Application mApplication;
@NonNull
private final String mDownloadingChannel;
StubNotificationChannelProvider(@NonNull Application context, @NonNull String downloadingChannel)
{
mApplication = context;
mDownloadingChannel = downloadingChannel;
}
StubNotificationChannelProvider(@NonNull Application context)
{
this(context, DEFAULT_NOTIFICATION_CHANNEL);
}
@NonNull
@Override
public String getDownloadingChannel()
{
return mDownloadingChannel;
}
@Override
public void setDownloadingChannel()
{
/*Do nothing */
}
@NonNull
protected Application getApplication()
{
return mApplication;
}
}

View file

@ -0,0 +1,104 @@
package app.organicmaps.downloader;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.util.StringUtils;
import app.organicmaps.util.log.Logger;
public abstract class DownloaderNotifier
{
private static final String TAG = DownloaderNotifier.class.getSimpleName();
private static final String CHANNEL_ID = "downloader";
private static final String EXTRA_CANCEL_NOTIFICATION = "extra_cancel_downloader_notification";
private static final int NOTIFICATION_ID = 1;
public static void createNotificationChannel(@NonNull Context context)
{
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
final NotificationChannelCompat channel = new NotificationChannelCompat.Builder(CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setName(context.getString(R.string.notification_channel_downloader))
.setShowBadge(true)
.build();
notificationManager.createNotificationChannel(channel);
}
public static void notifyDownloadFailed(@NonNull Context context, @Nullable String countryId)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(context, POST_NOTIFICATIONS) != PERMISSION_GRANTED)
{
Logger.w(TAG, "Permission POST_NOTIFICATIONS is not granted, skipping notification");
return;
}
final String title = context.getString(R.string.app_name);
final String countryName = MapManager.nativeGetName(countryId);
final String content = context.getString(R.string.download_country_failed, countryName);
final int FLAG_IMMUTABLE = Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? 0 : PendingIntent.FLAG_IMMUTABLE;
final Intent contentIntent = MwmActivity.createShowMapIntent(context, countryId);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent contentPendingIntent = PendingIntent.getActivity(context, 0, contentIntent,
PendingIntent.FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE);
final Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
.setAutoCancel(true)
.setCategory(NotificationCompat.CATEGORY_ERROR)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.notification))
.setContentTitle(title)
.setContentText(content)
.setShowWhen(true)
.setTicker(getTicker(context, title, content))
.setContentIntent(contentPendingIntent)
.build();
Logger.i(TAG, "Notifying about failed map download");
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, notification);
}
static void cancelNotification(@NonNull Context context)
{
Logger.i(TAG, "Cancelling notification about failed map download");
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(NOTIFICATION_ID);
}
public static void processNotificationExtras(@NonNull Context context, @Nullable Intent intent)
{
if (!intent.hasExtra(EXTRA_CANCEL_NOTIFICATION))
return;
cancelNotification(context);
}
@NonNull
private static CharSequence getTicker(@NonNull Context context, @NonNull String title, @NonNull String content)
{
@StringRes final int templateResId = StringUtils.isRtl() ? R.string.notification_ticker_rtl
: R.string.notification_ticker_ltr;
return context.getString(templateResId, title, content);
}
}

View file

@ -12,7 +12,6 @@ import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import app.organicmaps.MwmActivity;
import app.organicmaps.R;
import app.organicmaps.background.Notifier;
import app.organicmaps.location.LocationHelper;
import app.organicmaps.routing.RoutingController;
import app.organicmaps.widget.WheelProgressView;
@ -169,6 +168,7 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
MapManager.nativeHasSpaceToDownloadCountry(country))
{
MapManager.nativeDownload(mCurrentCountry.id);
mActivity.requestPostNotificationsPermission();
}
}
}
@ -209,19 +209,22 @@ public class OnmapDownloader implements MwmActivity.LeftAnimationTrackListener
setAutodownloadLocked(true);
}
});
final Notifier notifier = Notifier.from(activity.getApplication());
mButton.setOnClickListener(v -> MapManager.warnOn3g(mActivity, mCurrentCountry == null ? null : mCurrentCountry.id, () -> {
mButton.setOnClickListener(v -> MapManager.warnOn3g(mActivity, mCurrentCountry == null ? null :
mCurrentCountry.id, () -> {
if (mCurrentCountry == null)
return;
boolean retry = (mCurrentCountry.status == CountryItem.STATUS_FAILED);
if (retry)
{
notifier.cancelNotification(Notifier.ID_DOWNLOAD_FAILED);
DownloaderNotifier.cancelNotification(mActivity.getApplicationContext());
MapManager.nativeRetry(mCurrentCountry.id);
}
else
{
MapManager.nativeDownload(mCurrentCountry.id);
mActivity.requestPostNotificationsPermission();
}
}));
ViewCompat.setOnApplyWindowInsetsListener(mFrame, (view, windowInsets) -> {

View file

@ -4,8 +4,6 @@ import android.app.Application;
import androidx.annotation.NonNull;
import app.organicmaps.background.Notifier;
public class RetryFailedDownloadConfirmationListener implements Runnable
{
@NonNull
@ -19,7 +17,6 @@ public class RetryFailedDownloadConfirmationListener implements Runnable
@Override
public void run()
{
final Notifier notifier = Notifier.from(mApplication);
notifier.cancelNotification(Notifier.ID_DOWNLOAD_FAILED);
DownloaderNotifier.cancelNotification(mApplication);
}
}