NearestFavoriteStationWidgetService: migrate periodic refresh from JobIntentService to JobScheduler

* Receive MY_PACKAGE_REPLACED and MY_PACKAGE_UNSUSPENDED system broadcasts to schedule our job.
* Remove the foreground notification, as it's not necessary any more.
This commit is contained in:
Andreas Schildbach 2022-08-23 10:06:30 +02:00
parent 7634c116ae
commit 2034119b71
11 changed files with 81 additions and 93 deletions

View file

@ -248,6 +248,12 @@
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/nearest_favorite_station_widget" />

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
</vector>

View file

@ -6,5 +6,4 @@
android:minResizeHeight="55dp"
android:minWidth="294dp"
android:previewImage="@drawable/nearest_favorite_station_widget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000" />
android:resizeMode="horizontal|vertical" />

View file

@ -17,13 +17,9 @@
package de.schildbach.oeffi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.preference.PreferenceManager;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
@ -35,7 +31,6 @@ import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import com.google.common.base.Stopwatch;
import de.schildbach.oeffi.directions.QueryHistoryProvider;
import de.schildbach.oeffi.stations.FavoriteStationsProvider;
import de.schildbach.oeffi.stations.NearestFavoriteStationWidgetService;
import de.schildbach.oeffi.util.ErrorReporter;
import de.schildbach.pte.NetworkId;
import okhttp3.OkHttpClient;
@ -121,8 +116,6 @@ public class Application extends android.app.Application {
QueryHistoryProvider.deleteQueryHistory(this, SBB);
log.info("Migrations took {}", watch);
initNotificationManager();
}
private void initLogging() {
@ -178,20 +171,6 @@ public class Application extends android.app.Application {
config.setUserAgentValue(getPackageName());
}
private void initNotificationManager() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final Stopwatch watch = Stopwatch.createStarted();
final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
final NotificationChannel appwidget = new NotificationChannel(
NearestFavoriteStationWidgetService.NOTIFICATION_CHANNEL_ID_APPWIDGET,
getString(R.string.notification_channel_appwidget_name), NotificationManager.IMPORTANCE_LOW);
nm.createNotificationChannel(appwidget);
log.info("created notification channels, took {}", watch);
}
}
private void migrateSelectedNetwork(final String fromName, final NetworkId to) {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);

View file

@ -121,7 +121,7 @@ public class FavoriteStationsActivity extends OeffiActivity
} else if (menuItemId == R.id.station_context_remove_favorite) {
adapter.removeEntry(adapterPosition);
updateGUI();
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
return true;
} else {
return false;

View file

@ -17,12 +17,8 @@
package de.schildbach.oeffi.stations;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import de.schildbach.pte.NetworkId;
@ -71,17 +67,4 @@ public class FavoriteUtils {
return favorites;
}
public static void notifyFavoritesChanged(final Context context) {
// notify widgets
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (final AppWidgetProviderInfo providerInfo : appWidgetManager.getInstalledProviders()) {
// limit to own widgets
if (providerInfo.provider.getPackageName().equals(context.getPackageName())) {
final Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS,
appWidgetManager.getAppWidgetIds(providerInfo.provider));
context.sendBroadcast(intent);
}
}
}
}

View file

@ -17,7 +17,6 @@
package de.schildbach.oeffi.stations;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
@ -32,15 +31,6 @@ public class NearestFavoriteStationWidgetProvider extends AppWidgetProvider {
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
log.info("got broadcast: {}", action);
if (Intent.ACTION_BOOT_COMPLETED.equals(action))
NearestFavoriteStationWidgetService.enqueueWork(context, new Intent());
else
super.onReceive(context, intent);
}
@Override
public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) {
NearestFavoriteStationWidgetService.enqueueWork(context, new Intent());
NearestFavoriteStationWidgetService.schedulePeriodic(context);
}
}

View file

@ -19,6 +19,10 @@ package de.schildbach.oeffi.stations;
import android.Manifest;
import android.app.PendingIntent;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
@ -38,10 +42,10 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.RemoteViews;
import androidx.core.app.JobIntentService;
import androidx.core.app.NotificationCompat;
import androidx.annotation.WorkerThread;
import androidx.core.content.ContextCompat;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.SettableFuture;
@ -68,24 +72,59 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class NearestFavoriteStationWidgetService extends JobIntentService {
public class NearestFavoriteStationWidgetService extends JobService {
private AppWidgetManager appWidgetManager;
private LocationManager locationManager;
private ContentResolver contentResolver;
private Executor executor = Executors.newFixedThreadPool(2);
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private static final int JOB_ID = 1;
public static final String NOTIFICATION_CHANNEL_ID_APPWIDGET = "appwidget";
private static final int NOTIFICATION_ID_APPWIDGET_UPDATE = 1;
private static final int JOB_ID_PERIODIC = 0;
private static final int JOB_ID_IMMEDIATE = 1;
private static final Logger log = LoggerFactory.getLogger(NearestFavoriteStationWidgetService.class);
public static void enqueueWork(final Context context, final Intent work) {
enqueueWork(context, NearestFavoriteStationWidgetService.class, JOB_ID, work);
public static void schedulePeriodic(final Context context) {
final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
final ComponentName providerName = new ComponentName(context, NearestFavoriteStationWidgetProvider.class);
final boolean haveWidgets = AppWidgetManager.getInstance(context).getAppWidgetIds(providerName).length > 0;
if (haveWidgets) {
final JobInfo.Builder jobInfo = new JobInfo.Builder(JOB_ID_PERIODIC, new ComponentName(context,
NearestFavoriteStationWidgetService.class));
jobInfo.setPeriodic(DateUtils.MINUTE_IN_MILLIS * 15);
jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
final JobInfo job = jobInfo.build();
jobScheduler.schedule(job);
log.info("Scheduled periodic job: {}", job);
} else {
jobScheduler.cancelAll();
}
}
public static void scheduleImmediate(final Context context) {
final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
final ComponentName providerName = new ComponentName(context, NearestFavoriteStationWidgetProvider.class);
final boolean haveWidgets = AppWidgetManager.getInstance(context).getAppWidgetIds(providerName).length > 0;
if (haveWidgets) {
final JobInfo.Builder jobInfo = new JobInfo.Builder(JOB_ID_IMMEDIATE, new ComponentName(context,
NearestFavoriteStationWidgetService.class));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
jobInfo.setExpedited(true);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
jobInfo.setImportantWhileForeground(true);
jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
final JobInfo job = jobInfo.build();
jobScheduler.schedule(job);
log.info("Scheduled immediate job: {}", job);
} else {
jobScheduler.cancelAll();
}
}
@Override
@ -110,25 +149,28 @@ public class NearestFavoriteStationWidgetService extends JobIntentService {
private RemoteViews views;
@Override
protected void onHandleWork(final Intent intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final NotificationCompat.Builder notification = new NotificationCompat.Builder(this,
NOTIFICATION_CHANNEL_ID_APPWIDGET);
notification.setSmallIcon(R.drawable.ic_stat_notify_sync_24dp);
notification.setWhen(System.currentTimeMillis());
notification.setOngoing(true);
startForeground(NOTIFICATION_ID_APPWIDGET_UPDATE, notification.build());
}
handleIntent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
stopForeground(true);
public boolean onStartJob(final JobParameters params) {
log.info("Job started: {}", params);
executor.execute(() -> {
runJob();
jobFinished(params, false);
log.info("Job finished: {}", params);
});
return true;
}
private void handleIntent() {
@Override
public boolean onStopJob(final JobParameters params) {
log.info("Job stopped: {}", params);
return false;
}
@WorkerThread
private void runJob() {
final ComponentName providerName = new ComponentName(this, NearestFavoriteStationWidgetProvider.class);
final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(providerName);
if (appWidgetIds.length == 0)
return;
views = new RemoteViews(getPackageName(), R.layout.station_widget_content);

View file

@ -53,7 +53,7 @@ public class NearestFavoriteStationsWidgetPermissionActivity extends Activity {
for (int i = 0; i < permissions.length; i++)
log.info("{}{} granted",
permissions[i], grantResults[i] == PackageManager.PERMISSION_GRANTED ? "" : " " + "not");
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
finish();
}
}

View file

@ -157,13 +157,13 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa
FavoriteStationsProvider.TYPE_FAVORITE, selectedNetwork, selectedStation);
if (rowUri != null) {
selectedFavState = FavoriteStationsProvider.TYPE_FAVORITE;
FavoriteUtils.notifyFavoritesChanged(StationDetailsActivity.this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
}
} else {
final int numRows = FavoriteUtils.delete(getContentResolver(), selectedNetwork, selectedStation.id);
if (numRows > 0) {
selectedFavState = null;
FavoriteUtils.notifyFavoritesChanged(StationDetailsActivity.this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
}
}
});

View file

@ -696,7 +696,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
if (rowUri != null) {
favorites.put(location.id, FavoriteStationsProvider.TYPE_FAVORITE);
postLoadNextVisible(0);
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
return true;
} else {
return false;
@ -707,7 +707,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
final int numRows = FavoriteUtils.delete(getContentResolver(), network, location.id);
if (numRows > 0) {
favorites.remove(location.id);
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
return true;
} else {
return false;
@ -719,7 +719,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
network, location);
if (rowUriIgnored != null) {
favorites.put(location.id, FavoriteStationsProvider.TYPE_IGNORE);
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
return true;
} else {
return false;
@ -731,7 +731,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware
if (numRowsIgnored > 0) {
favorites.remove(location.id);
postLoadNextVisible(0);
FavoriteUtils.notifyFavoritesChanged(this);
NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget
return true;
} else {
return false;