From 74c391847ad5ac87f070fad1e0ab63f7dfa75572 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Tue, 25 Oct 2022 16:34:36 +0200 Subject: [PATCH 1/2] Refactorize weather widget implementation Simplify WeatherUpdateService and introduce 2 workers: - OneTimeWeatherRequestWorker - ForceWeatherRequestWorker WeatherUpdateService schedules every 10 seconds a OneTimeWeatherRequestWorker that checks if a weather request is needed according to the period set in the widget settings. Default period has been adjusted to 15 minutes. The widget update button triggers a ForceWeatherRequestWorker which forces a high accuracy location request. --- app/build.gradle.kts | 3 + .../weather/WeatherUpdateService.java | 4 - .../apiNougat/res/xml/preferences_weather.xml | 2 +- .../features/weather/DeviceStatusService.java | 8 - .../features/weather/ForecastBuilder.java | 1 + .../weather/WeatherUpdateService.java | 564 +----------------- .../worker/ForceWeatherRequestWorker.java | 99 +++ .../worker/OneShotWeatherRequestWorker.java | 49 ++ .../weather/worker/WeatherRequestWorker.java | 70 +++ .../apiOreo/res/xml/preferences_weather.xml | 2 +- app/src/main/AndroidManifest.xml | 1 + .../e/blisslauncher/BlissLauncher.java | 2 + .../features/launcher/LauncherActivity.java | 6 - .../features/weather/WeatherInfoView.java | 12 +- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-eu/strings.xml | 1 + app/src/main/res/values-fi/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-gl/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/arrays.xml | 2 + app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 1 + 25 files changed, 273 insertions(+), 562 deletions(-) create mode 100644 app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/ForceWeatherRequestWorker.java create mode 100644 app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/OneShotWeatherRequestWorker.java create mode 100644 app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/WeatherRequestWorker.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4cd51a270f..58414af701 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -184,4 +184,7 @@ dependencies { // elib implementation(libs.elib) + + // workmanager + implementation(libs.workmanager) } diff --git a/app/src/apiNougat/java/foundation/e/blisslauncher/features/weather/WeatherUpdateService.java b/app/src/apiNougat/java/foundation/e/blisslauncher/features/weather/WeatherUpdateService.java index 28b453cfd5..8f66be5e7a 100644 --- a/app/src/apiNougat/java/foundation/e/blisslauncher/features/weather/WeatherUpdateService.java +++ b/app/src/apiNougat/java/foundation/e/blisslauncher/features/weather/WeatherUpdateService.java @@ -399,11 +399,7 @@ public class WeatherUpdateService extends Service { finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, updateCancelled); mContext.sendBroadcast(finishedIntent); - if (D) - Log.d(TAG, "RELEASING WAKELOCK"); - mWakeLock.release(); mIsProcessingWeatherUpdate = false; - mContext.stopService(new Intent(mContext, WeatherUpdateService.class)); } @Override diff --git a/app/src/apiNougat/res/xml/preferences_weather.xml b/app/src/apiNougat/res/xml/preferences_weather.xml index ca0449dca6..04b07acfdd 100644 --- a/app/src/apiNougat/res/xml/preferences_weather.xml +++ b/app/src/apiNougat/res/xml/preferences_weather.xml @@ -31,7 +31,7 @@ { - final Context context = getApplicationContext(); - final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(context); - final String activeProviderLabel = weatherManager.getActiveWeatherServiceProviderLabel(); - final String noData = getString(R.string.weather_cannot_reach_provider, activeProviderLabel); - Toast.makeText(context, noData, Toast.LENGTH_SHORT).show(); - }); - } - stopSelf(); - return START_NOT_STICKY; - } - - boolean force = ACTION_FORCE_UPDATE.equals(intent.getAction()); - if (!shouldUpdate(force)) { - Log.d(TAG, "Service started, but shouldn't update ... stopping"); - sendCancelledBroadcast(); - stopSelf(); - return START_NOT_STICKY; - } - - mWorkerThread.getHandler().obtainMessage(WorkerThread.MSG_ON_NEW_WEATHER_REQUEST).sendToTarget(); - - return START_REDELIVER_INTENT; + executePeriodicRequest(); } - private boolean shouldUpdate(boolean force) { - final LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(getApplicationContext()); - if (weatherManager.getActiveWeatherServiceProviderLabel() == null) { - // Why bother if we don't even have an active provider - if (D) - Log.d(TAG, "No active weather service provider found, skip"); - return false; - } - - final long interval = Preferences.weatherRefreshIntervalInMs(this); - if (interval == 0 && !force) { - if (D) - Log.v(TAG, "Interval set to manual and update not forced, skip"); - return false; - } - - if (!WeatherPreferences.hasLocationPermission(this)) { - if (D) - Log.v(TAG, "Application does not have the location permission, skip"); - return false; - } - - ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo info = cm.getActiveNetworkInfo(); - if (info == null || !info.isConnected() || !info.isAvailable()) { - if (D) - Log.d(TAG, "Network is not available, skip"); - return false; - } else { - if (force) { - if (D) - Log.d(TAG, "Forcing weather update"); - return true; - } else { - final long now = SystemClock.elapsedRealtime(); - final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(this); - final long due = lastUpdate + interval; - if (D) { - Log.d(TAG, "Now " + now + " Last update " + lastUpdate + " interval " + interval); - } - - if (lastUpdate == 0 || due - now < 0) { - if (D) - Log.d(TAG, "Should update"); - return true; - } else { - if (D) - Log.v(TAG, "Next weather update due in " + (due - now) + " ms, skip"); - return false; - } - } - } - } - - private class WorkerThread extends HandlerThread implements LineageWeatherManager.WeatherUpdateRequestListener { - - public static final int MSG_ON_NEW_WEATHER_REQUEST = 1; - public static final int MSG_ON_WEATHER_REQUEST_COMPLETED = 2; - public static final int MSG_WEATHER_REQUEST_FAILED = 3; - public static final int MSG_CANCEL_UPDATE_WEATHER_REQUEST = 4; - - private Handler mHandler; - private boolean mIsProcessingWeatherUpdate = false; - private WakeLock mWakeLock; - private PendingIntent mTimeoutPendingIntent; - private int mRequestId; - private final LineageWeatherManager mWeatherManager; - private final Context mContext; - - public WorkerThread(Context context) { - super("weather-service-worker"); - mContext = context; - mWeatherManager = LineageWeatherManager.getInstance(mContext); - } - - public synchronized void prepareHandler() { - mHandler = new Handler(getLooper()) { - @Override - public void handleMessage(Message msg) { - if (D) - Log.d(TAG, "Msg " + msg.what); - switch (msg.what) { - case MSG_ON_NEW_WEATHER_REQUEST : - onNewWeatherRequest(); - break; - case MSG_ON_WEATHER_REQUEST_COMPLETED : - WeatherInfo info = (WeatherInfo) msg.obj; - onWeatherRequestCompleted(info); - break; - case MSG_WEATHER_REQUEST_FAILED : - int status = msg.arg1; - onWeatherRequestFailed(status); - break; - case MSG_CANCEL_UPDATE_WEATHER_REQUEST : - onCancelUpdateWeatherRequest(); - break; - default : - // Unknown message, pass it on... - super.handleMessage(msg); - } - } - }; - } - - private void startTimeoutAlarm() { - Intent intent = new Intent(mContext, WeatherUpdateService.class); - intent.setAction(ACTION_CANCEL_UPDATE_WEATHER_REQUEST); - - mTimeoutPendingIntent = PendingIntent.getService(mContext, 0, intent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); - - AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE); - long elapseTime = SystemClock.elapsedRealtime() + WEATHER_UPDATE_REQUEST_TIMEOUT_MS; - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapseTime, mTimeoutPendingIntent); - if (D) - Log.v(TAG, "Timeout alarm set to expire in " + elapseTime + " ms"); - } - - private void cancelTimeoutAlarm() { - Log.d(TAG, "cancelTimeoutAlarm() called"); - if (mTimeoutPendingIntent != null) { - AlarmManager am = (AlarmManager) mContext.getSystemService(ALARM_SERVICE); - am.cancel(mTimeoutPendingIntent); - mTimeoutPendingIntent = null; - if (D) - Log.v(TAG, "Timeout alarm cancelled"); - } - } - - public synchronized Handler getHandler() { - return mHandler; - } - - @SuppressLint("WakelockTimeout") - private void onNewWeatherRequest() { - if (mIsProcessingWeatherUpdate) { - Log.d(TAG, "Already processing weather update, discarding request..."); - return; - } - - mIsProcessingWeatherUpdate = true; - final PowerManager pm = (PowerManager) mContext.getSystemService(POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "blisslauncher:WeatherUpdateService"); - mWakeLock.setReferenceCounted(false); - if (D) - Log.v(TAG, "ACQUIRING WAKELOCK"); - mWakeLock.acquire(); - - WeatherLocation customWeatherLocation = null; - if (Preferences.useCustomWeatherLocation(mContext)) { - customWeatherLocation = Preferences.getCustomWeatherLocation(mContext); - } - if (customWeatherLocation != null) { - mRequestId = mWeatherManager.requestWeatherUpdate(customWeatherLocation, this); - if (D) - Log.d(TAG, "Request submitted using WeatherLocation"); - startTimeoutAlarm(); - } else { - final Location location = getCurrentLocation(); - if (location != null) { - mRequestId = mWeatherManager.requestWeatherUpdate(location, this); - if (D) - Log.d(TAG, "Request submitted using Location"); - startTimeoutAlarm(); - } else { - // work with cached location from last request for now - // a listener to update it is already scheduled if possible - WeatherInfo cachedInfo = Preferences.getCachedWeatherInfo(mContext); - if (cachedInfo != null) { - mHandler.obtainMessage(MSG_ON_WEATHER_REQUEST_COMPLETED, cachedInfo).sendToTarget(); - if (D) - Log.d(TAG, "Returning cached weather data [ " + cachedInfo.toString() + " ]"); - } else { - mHandler.obtainMessage(MSG_WEATHER_REQUEST_FAILED, LineageWeatherManager.RequestStatus.FAILED, - 0).sendToTarget(); - } - } - } - } - - public void tearDown() { - if (D) - Log.d(TAG, "Tearing down worker thread"); - if (isProcessing()) - mWeatherManager.cancelRequest(mRequestId); - quit(); - } - - public boolean isProcessing() { - return mIsProcessingWeatherUpdate; - } - - private Location getCurrentLocation() { - final LocationManager lm = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - Location location = null; - try { - location = lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER); - } catch (SecurityException e) { - e.printStackTrace(); - } - if (D && location != null) - Log.v(TAG, "Current location is " + location + ", accuracy: " + location.getAccuracy()); - - if (location != null && location.getAccuracy() > LOCATION_ACCURACY_THRESHOLD_METERS) { - if (D) - Log.d(TAG, "Ignoring inaccurate location"); - location = null; - } - - // If lastKnownLocation is not present (because none of the apps in the - // device has requested the current location to the system yet) or outdated, - // then try to get the current location use the provider that best matches the - // criteria. - boolean needsUpdate = location == null; - if (location != null) { - long delta = System.currentTimeMillis() - location.getTime(); - needsUpdate = delta > OUTDATED_LOCATION_THRESHOLD_MILLIS; - } - if (needsUpdate) { - if (D) - Log.d(TAG, "Getting best location provider"); - String locationProvider = lm.getBestProvider(sLocationCriteria, true); - if (TextUtils.isEmpty(locationProvider)) { - Log.e(TAG, "No available location providers matching criteria."); - } else { - WeatherLocationListener.registerIfNeeded(mContext, locationProvider); - } - } - return location; - } - - private void onWeatherRequestCompleted(WeatherInfo result) { - Log.d(TAG, "onWeatherRequestCompleted() called with: result = [" + result + "]"); - cancelTimeoutAlarm(); - long now = SystemClock.elapsedRealtime(); - Preferences.setCachedWeatherInfo(mContext, now, result); - Preferences.setLastWeatherUpdateTimestamp(mContext, now); - scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false); - - Intent updateIntent = new Intent(WeatherUpdateService.ACTION_UPDATE_FINISHED); - LocalBroadcastManager.getInstance(WeatherUpdateService.this).sendBroadcast(updateIntent); - broadcastAndCleanUp(false); - } - - private void onWeatherRequestFailed(int status) { - if (D) - Log.d(TAG, "Weather refresh failed [" + status + "]"); - cancelTimeoutAlarm(); - if (status == LineageWeatherManager.RequestStatus.ALREADY_IN_PROGRESS) { - if (D) - Log.d(TAG, "A request is already in progress, no need to schedule again"); - } else if (status == LineageWeatherManager.RequestStatus.FAILED) { - // Something went wrong, let's schedule an update at the next interval from now - // A force update might happen earlier anyway - scheduleUpdate(mContext, Preferences.weatherRefreshIntervalInMs(mContext), false); - } else { - // Wait until the next update is due - scheduleNextUpdate(mContext, false); - } - broadcastAndCleanUp(true); - } - - private void onCancelUpdateWeatherRequest() { - if (D) - Log.d(TAG, "Cancelling active weather request"); - if (mIsProcessingWeatherUpdate) { - cancelTimeoutAlarm(); - mWeatherManager.cancelRequest(mRequestId); - broadcastAndCleanUp(true); - } - } - - private void broadcastAndCleanUp(boolean updateCancelled) { - Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED); - finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, updateCancelled); - mContext.sendBroadcast(finishedIntent); - - if (D) - Log.d(TAG, "RELEASING WAKELOCK"); - mWakeLock.release(); - mIsProcessingWeatherUpdate = false; - mContext.stopService(new Intent(mContext, WeatherUpdateService.class)); + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (intent != null && ACTION_FORCE_UPDATE.equals(intent.getAction())) { + ForceWeatherRequestWorker.start(this); } - @Override - public void onWeatherRequestCompleted(int state, WeatherInfo weatherInfo) { - if (state == LineageWeatherManager.RequestStatus.COMPLETED) { - mHandler.obtainMessage(WorkerThread.MSG_ON_WEATHER_REQUEST_COMPLETED, weatherInfo).sendToTarget(); - } else { - mHandler.obtainMessage(WorkerThread.MSG_WEATHER_REQUEST_FAILED, state, 0).sendToTarget(); - } - } + return START_STICKY; } - private void sendCancelledBroadcast() { - Intent finishedIntent = new Intent(ACTION_UPDATE_FINISHED); - finishedIntent.putExtra(EXTRA_UPDATE_CANCELLED, true); - sendBroadcast(finishedIntent); + private void executePeriodicRequest() { + OneShotWeatherRequestWorker.start(this); + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::executePeriodicRequest, UPDATE_PERIOD_IN_MS); } + @Nullable @Override public IBinder onBind(Intent intent) { return null; @@ -422,159 +59,8 @@ public class WeatherUpdateService extends Service { @Override public void onDestroy() { - Log.d(TAG, "onDestroy"); - mWorkerThread.tearDown(); - } - - private static class WeatherLocationListener implements LocationListener { - private WeakReference mContext; - private PendingIntent mTimeoutIntent; - private static WeatherLocationListener sInstance = null; - - @SuppressLint("MissingPermission") - static void registerIfNeeded(Context context, String provider) { - synchronized (WeatherLocationListener.class) { - if (D) - Log.d(TAG, "Registering location listener"); - if (sInstance == null) { - final Context appContext = context.getApplicationContext(); - final LocationManager locationManager = (LocationManager) appContext - .getSystemService(Context.LOCATION_SERVICE); - - // Check location provider after set sInstance, so, if the provider is not - // supported, we never enter here again. - sInstance = new WeatherLocationListener(appContext); - // Check whether the provider is supported. - // NOTE!!! Actually only WeatherUpdateService class is calling this function - // with the NETWORK_PROVIDER, so setting the instance is safe. We must - // change this if this call receive different providers - LocationProvider lp = locationManager.getProvider(provider); - if (lp != null) { - if (D) - Log.d(TAG, "LocationManager - Requesting single update"); - locationManager.requestSingleUpdate(provider, sInstance, appContext.getMainLooper()); - sInstance.setTimeoutAlarm(); - } - } - } - } - - static void cancel(Context context) { - synchronized (WeatherLocationListener.class) { - if (sInstance != null) { - final Context appContext = context.getApplicationContext(); - final LocationManager locationManager = (LocationManager) appContext - .getSystemService(Context.LOCATION_SERVICE); - if (D) - Log.d(TAG, "Aborting location request after timeout"); - locationManager.removeUpdates(sInstance); - sInstance.cancelTimeoutAlarm(); - sInstance = null; - } - } - } - - private WeatherLocationListener(Context context) { - super(); - mContext = new WeakReference<>(context); - } - - private void setTimeoutAlarm() { - Intent intent = new Intent(mContext.get(), WeatherUpdateService.class); - intent.setAction(ACTION_CANCEL_LOCATION_UPDATE); - - mTimeoutIntent = PendingIntent.getService(mContext.get(), 0, intent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); - - AlarmManager am = (AlarmManager) mContext.get().getSystemService(ALARM_SERVICE); - long elapseTime = SystemClock.elapsedRealtime() + LOCATION_REQUEST_TIMEOUT; - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, elapseTime, mTimeoutIntent); - } - - private void cancelTimeoutAlarm() { - Log.d(TAG, "cancelTimeoutAlarm2() called"); - if (mTimeoutIntent != null) { - AlarmManager am = (AlarmManager) mContext.get().getSystemService(ALARM_SERVICE); - am.cancel(mTimeoutIntent); - mTimeoutIntent = null; - } - } - - @Override - public void onLocationChanged(Location location) { - // Now, we have a location to use. Schedule a weather update right now. - if (D) - Log.d(TAG, "The location has changed, schedule an update "); - synchronized (WeatherLocationListener.class) { - scheduleUpdate(mContext.get(), 0, true); - cancelTimeoutAlarm(); - sInstance = null; - } - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - // Now, we have a location to use. Schedule a weather update right now. - if (D) - Log.d(TAG, "The location service has become available, schedule an update "); - if (status == LocationProvider.AVAILABLE) { - synchronized (WeatherLocationListener.class) { - scheduleUpdate(mContext.get(), 0, true); - cancelTimeoutAlarm(); - sInstance = null; - } - } - } - - @Override - public void onProviderEnabled(String provider) { - // Not used - } - - @Override - public void onProviderDisabled(String provider) { - // Not used - } - } - - private static void scheduleUpdate(Context context, long millisFromNow, boolean force) { - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - long due = SystemClock.elapsedRealtime() + millisFromNow; - if (D) - Log.d(TAG, "Next update scheduled at " + new Date(System.currentTimeMillis() + millisFromNow)); - am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, due, getUpdateIntent(context, force)); - } - - public static void scheduleNextUpdate(Context context, boolean force) { - if (force) { - if (D) - Log.d(TAG, "Scheduling next update immediately"); - scheduleUpdate(context, 0, true); - } else { - final long lastUpdate = Preferences.lastWeatherUpdateTimestamp(context); - final long interval = Preferences.weatherRefreshIntervalInMs(context); - final long now = SystemClock.elapsedRealtime(); - long due = (interval + lastUpdate) - now; - if (due < 0) - due = 0; - if (D) - Log.d(TAG, "Scheduling in " + due + " ms"); - scheduleUpdate(context, due, false); - } - } - - public static PendingIntent getUpdateIntent(Context context, boolean force) { - Intent i = new Intent(context, WeatherUpdateService.class); - if (force) { - i.setAction(ACTION_FORCE_UPDATE); - } - return PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); - } - - public static void cancelUpdates(Context context) { - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - am.cancel(getUpdateIntent(context, true)); - am.cancel(getUpdateIntent(context, false)); - WeatherLocationListener.cancel(context); + mHandler.removeCallbacksAndMessages(null); + mHandlerThread.quitSafely(); + super.onDestroy(); } } diff --git a/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/ForceWeatherRequestWorker.java b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/ForceWeatherRequestWorker.java new file mode 100644 index 0000000000..779216de8e --- /dev/null +++ b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/ForceWeatherRequestWorker.java @@ -0,0 +1,99 @@ +package foundation.e.blisslauncher.features.weather.worker; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.location.Location; +import android.location.LocationManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.location.LocationManagerCompat; +import androidx.work.ExistingWorkPolicy; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.WorkerParameters; + +import java.util.concurrent.Executors; + +import foundation.e.blisslauncher.core.Preferences; + +public class ForceWeatherRequestWorker extends WeatherRequestWorker { + + private static final String TAG = "ForceWeatherRequestWorker"; + + private Location mGpsLocation; + private Location mNetworkLocation; + + public ForceWeatherRequestWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } + + public static void start(Context context) { + OneTimeWorkRequest weatherUpdateWorkRequest = new OneTimeWorkRequest.Builder(ForceWeatherRequestWorker.class) + .build(); + + WorkManager.getInstance(context).enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, weatherUpdateWorkRequest); + } + + @NonNull + @Override + public Result doWork() { + + Context context = getApplicationContext(); + + if (Preferences.useCustomWeatherLocation(context)) { + requestCustomWeatherUpdate(context, Preferences.getCustomWeatherLocation(context)); + } else { + fetchNewLocation(context); + } + + return Result.success(); + } + + @SuppressLint("MissingPermission") + private void fetchNewLocation(Context context) { + if (hasMissingPermissions(context)) { + Log.e(TAG, "Could not fetch location for missing permission"); + return; + } + + LocationManagerCompat.getCurrentLocation(locationManager, LocationManager.GPS_PROVIDER, null, + Executors.newFixedThreadPool(1), this::onNewLocationFetched); + + LocationManagerCompat.getCurrentLocation(locationManager, LocationManager.NETWORK_PROVIDER, null, + Executors.newFixedThreadPool(1), this::onNewLocationFetched); + } + + private synchronized void onNewLocationFetched(Location location) { + if (location == null) { + return; + } + + Log.i(TAG, "New location fetched:" + location); + + if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { + mGpsLocation = location; + } else if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) { + mNetworkLocation = location; + requestWeatherUpdate(getApplicationContext(), getMostRecentLocation()); + } + } + + private Location getMostRecentLocation() { + if (mNetworkLocation == null && mGpsLocation == null) { + throw new IllegalStateException(); + } + + if (mGpsLocation == null) { + return mNetworkLocation; + } + + if (mNetworkLocation == null) { + return mGpsLocation; + } + + long gpsTime = mGpsLocation.getTime(); + long networkTime = mNetworkLocation.getTime(); + return gpsTime >= networkTime ? mGpsLocation : mNetworkLocation; + } +} diff --git a/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/OneShotWeatherRequestWorker.java b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/OneShotWeatherRequestWorker.java new file mode 100644 index 0000000000..ef0de8732c --- /dev/null +++ b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/OneShotWeatherRequestWorker.java @@ -0,0 +1,49 @@ +package foundation.e.blisslauncher.features.weather.worker; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.SystemClock; + +import androidx.annotation.NonNull; +import androidx.work.ExistingWorkPolicy; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; +import androidx.work.WorkerParameters; + +import foundation.e.blisslauncher.core.Preferences; + +public class OneShotWeatherRequestWorker extends WeatherRequestWorker { + + public static final String TAG = "OneShotWeatherRequestWorker"; + + public OneShotWeatherRequestWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } + + public static void start(Context context) { + OneTimeWorkRequest weatherUpdateWorkRequest = new OneTimeWorkRequest.Builder(OneShotWeatherRequestWorker.class) + .build(); + + WorkManager.getInstance(context).enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, weatherUpdateWorkRequest); + } + + @NonNull + @SuppressLint("MissingPermission") + @Override + public Result doWork() { + Context context = getApplicationContext(); + + if (isWeatherRequestAllowed(context)) { + ForceWeatherRequestWorker.start(context); + } + + return Result.success(); + } + + private static Boolean isWeatherRequestAllowed(Context context) { + long refreshPeriod = Preferences.weatherRefreshIntervalInMs(context); + long elapsedTime = SystemClock.elapsedRealtime() - Preferences.lastWeatherUpdateTimestamp(context); + + return refreshPeriod != 0 && elapsedTime >= refreshPeriod; + } +} diff --git a/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/WeatherRequestWorker.java b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/WeatherRequestWorker.java new file mode 100644 index 0000000000..ce1724e48c --- /dev/null +++ b/app/src/apiOreo/java/foundation/e/blisslauncher/features/weather/worker/WeatherRequestWorker.java @@ -0,0 +1,70 @@ +package foundation.e.blisslauncher.features.weather.worker; + +import android.Manifest.permission; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationManager; +import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import foundation.e.blisslauncher.core.Preferences; +import foundation.e.blisslauncher.features.weather.WeatherUpdateService; +import lineageos.weather.LineageWeatherManager; +import lineageos.weather.WeatherInfo; +import lineageos.weather.WeatherLocation; + +public abstract class WeatherRequestWorker extends Worker { + + private static final String TAG = "WeatherRequestWorker"; + + protected final LocationManager locationManager; + + public WeatherRequestWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + @NonNull + public abstract Result doWork(); + + protected static void requestWeatherUpdate(Context context, Location location) { + Log.i(TAG, "Requesting weather info for location: " + location); + LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(context); + weatherManager.requestWeatherUpdate(location, (info, weatherInfo) -> notifyUi(context, weatherInfo)); + } + + protected static void requestCustomWeatherUpdate(Context context, WeatherLocation location) { + Log.i(TAG, "Requesting weather info for location: " + location); + LineageWeatherManager weatherManager = LineageWeatherManager.getInstance(context); + weatherManager.requestWeatherUpdate(location, (info, weatherInfo) -> notifyUi(context, weatherInfo)); + } + + protected static void notifyUi(@NonNull Context context, @Nullable WeatherInfo weatherInfo) { + if (weatherInfo == null) { + return; + } + + Log.i(TAG, "WeatherInfo=" + weatherInfo); + long now = SystemClock.elapsedRealtime(); + Preferences.setCachedWeatherInfo(context, now, weatherInfo); + Preferences.setLastWeatherUpdateTimestamp(context, now); + Intent updateIntent = new Intent(WeatherUpdateService.ACTION_UPDATE_FINISHED); + LocalBroadcastManager.getInstance(context).sendBroadcast(updateIntent); + } + + protected static boolean hasMissingPermissions(Context context) { + return ActivityCompat.checkSelfPermission(context, + permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED + && ActivityCompat.checkSelfPermission(context, + permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED; + } +} diff --git a/app/src/apiOreo/res/xml/preferences_weather.xml b/app/src/apiOreo/res/xml/preferences_weather.xml index 63f5ed1756..f54c5b8457 100644 --- a/app/src/apiOreo/res/xml/preferences_weather.xml +++ b/app/src/apiOreo/res/xml/preferences_weather.xml @@ -31,7 +31,7 @@ + diff --git a/app/src/main/java/foundation/e/blisslauncher/BlissLauncher.java b/app/src/main/java/foundation/e/blisslauncher/BlissLauncher.java index 114ae806d2..150574bc5e 100755 --- a/app/src/main/java/foundation/e/blisslauncher/BlissLauncher.java +++ b/app/src/main/java/foundation/e/blisslauncher/BlissLauncher.java @@ -8,6 +8,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.provider.Settings; + import foundation.e.blisslauncher.core.DeviceProfile; import foundation.e.blisslauncher.core.IconsHandler; import foundation.e.blisslauncher.core.blur.BlurWallpaperProvider; @@ -43,6 +44,7 @@ public class BlissLauncher extends Application { } }; getContentResolver().registerContentObserver(NOTIFICATION_BADGING_URI, false, notificationSettingsObserver); + } private void onNotificationSettingsChanged() { diff --git a/app/src/main/java/foundation/e/blisslauncher/features/launcher/LauncherActivity.java b/app/src/main/java/foundation/e/blisslauncher/features/launcher/LauncherActivity.java index 939f68730d..3707413adc 100755 --- a/app/src/main/java/foundation/e/blisslauncher/features/launcher/LauncherActivity.java +++ b/app/src/main/java/foundation/e/blisslauncher/features/launcher/LauncherActivity.java @@ -1201,12 +1201,6 @@ public class LauncherActivity extends AppCompatActivity currentPageNumber = page; if (currentPageNumber == 0) { refreshSuggestedApps(widgetsPage, forceRefreshSuggestedApps); - if (Preferences.weatherRefreshIntervalInMs(LauncherActivity.this) == 0) { - Intent intent = new Intent(LauncherActivity.this, WeatherUpdateService.class); - intent.setAction(WeatherUpdateService.ACTION_FORCE_UPDATE); - startService(intent); - } - mInsetsController.hide(WindowInsetsCompat.Type.statusBars()); } else { mInsetsController.show(WindowInsetsCompat.Type.statusBars()); diff --git a/app/src/main/java/foundation/e/blisslauncher/features/weather/WeatherInfoView.java b/app/src/main/java/foundation/e/blisslauncher/features/weather/WeatherInfoView.java index 6b8388ddc4..245f731f79 100644 --- a/app/src/main/java/foundation/e/blisslauncher/features/weather/WeatherInfoView.java +++ b/app/src/main/java/foundation/e/blisslauncher/features/weather/WeatherInfoView.java @@ -5,22 +5,25 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.widget.LinearLayout; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import foundation.e.blisslauncher.R; import foundation.e.blisslauncher.core.Preferences; import foundation.e.blisslauncher.features.launcher.LauncherActivity; +import foundation.e.blisslauncher.features.weather.worker.ForceWeatherRequestWorker; public class WeatherInfoView extends LinearLayout { private View mWeatherPanel; private View mWeatherSetupTextView; + private Context mContext; private final BroadcastReceiver mWeatherReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (!intent.getBooleanExtra(WeatherUpdateService.EXTRA_UPDATE_CANCELLED, false)) { + if (WeatherUpdateService.ACTION_UPDATE_FINISHED.equals(intent.getAction())) { updateWeatherPanel(); } } @@ -34,6 +37,7 @@ public class WeatherInfoView extends LinearLayout { public WeatherInfoView(Context context, AttributeSet attrs) { super(context, attrs); + mContext = context; } @Override @@ -49,8 +53,9 @@ public class WeatherInfoView extends LinearLayout { } }); findViewById(R.id.weather_setting_imageview).setOnClickListener(v -> startWeatherPreferences()); - findViewById(R.id.weather_refresh_imageview) - .setOnClickListener(v -> WeatherUpdateService.scheduleNextUpdate(getContext(), true)); + findViewById(R.id.weather_refresh_imageview).setOnClickListener(v -> { + ForceWeatherRequestWorker.start(mContext); + }); } @Override @@ -73,6 +78,7 @@ public class WeatherInfoView extends LinearLayout { private void updateWeatherPanel() { if (Preferences.getCachedWeatherInfo(getContext()) == null) { + Log.i("Weather", "getCacheWeatherInfo is null"); mWeatherSetupTextView.setVisibility(VISIBLE); mWeatherPanel.setVisibility(GONE); mWeatherSetupTextView.setOnClickListener(v -> startWeatherPreferences()); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 699d8ae219..d89e6a51e4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -54,6 +54,7 @@ 2 Stunden 60 Minuten 30 Minuten + 15 Minuten Manuell Tippen, um auszuwählen Keine Quelle für Wetterdaten ausgewählt diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 8f6542c590..3ca9226164 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -11,6 +11,7 @@ 2 horas 60 minutos 30 minutos + 15 minutos Manual Toque para seleccionar uno No se ha seleccionado fuente meteorológica diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 615bc93884..fef07f993d 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -58,6 +58,7 @@ 2 ordu 60 minutu 30 minutu + 15 minutu Eskuz Sakatu bat hautatzeko Ez da eguraldi-iragartzailerik hautatu diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 33d1296b16..7deff31ccc 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -61,6 +61,7 @@ 2 tuntia 60 minuuttia 30 minuuttia + 15 minuuttia Manuaalinen Valitse lähde napauttamalla Säälähdettä ei ole valittu diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 667f71220b..617bdb5fae 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -73,6 +73,7 @@ 2 heures 60 minutes 30 minutes + 15 minutes Manuellement Toucher pour sélectionner Aucun service météo sélectionné diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 858c94dcc5..c05be6bbb4 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -60,6 +60,7 @@ 2 horas 60 minutos 30 minutos + 15 minutos Manual Prema para seleccionar un Ningún servizo de meteoroloxía seleccionado diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 075b972cfa..89edaf8f8a 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -33,6 +33,7 @@ 2 uren 60 minuten 30 minuten + 15 minuten Tik om één te selecteren Geen weer informatiebron geselecteerd Kan %s op dit moment niet bereiken diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ea6dc84015..0ed870d60b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -119,6 +119,7 @@ 2 часа 60 минут 30 минут + 15 минут Вручную Нажми, чтобы выбрать один Не выбран источник погоды diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7317ba4572..b7a0212655 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -18,6 +18,7 @@ @string/weather_refresh_manual + @string/weather_refresh_15min @string/weather_refresh_30min @string/weather_refresh_60min @string/weather_refresh_2hrs @@ -28,6 +29,7 @@ 0 + 15 30 60 120 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c0e249228..ad6afc9791 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,6 +88,7 @@ Tap to select one Manual + 15 minutes 30 minutes 60 minutes 2 hours diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a523f7a174..ddda995b30 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,6 +62,7 @@ tools-desugar = "com.android.tools:desugar_jdk_libs:1.1.5" timber = "com.jakewharton.timber:timber:4.7.1" restriction-bypass = "com.github.ChickenHook:RestrictionBypass:2.2" elib = "foundation.e:elib:0.0.1-alpha11" +workmanager = "androidx.work:work-runtime:2.7.1" [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -- GitLab From a126db3c0aea862306c966441d8007f2ae612431 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Fri, 28 Oct 2022 15:48:35 +0200 Subject: [PATCH 2/2] Remove Nougat implementation since it is deprecated --- app/build.gradle.kts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 58414af701..d64556aa57 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -72,11 +72,6 @@ android { flavorDimensions.add("api") productFlavors { - create("apiNougat") { - dimension = "api" - minSdk = 24 - } - create("apiOreo") { dimension = "api" minSdk = 26 @@ -138,7 +133,6 @@ android { } dependencies { - "apiNougatImplementation"("org.cyanogenmod:platform.sdk:6.0") "apiOreoImplementation"(files("libs/lineage-sdk-oreo.jar")) "apiQImplementation"(files("libs/lineage-sdk-q.jar")) "apiRImplementation"(files("libs/lineage-sdk-r.jar")) -- GitLab