Set Wallpaper in background (#5665)

* Worker added for setting up wallpaper

* Fix crash

* Fix test

* Fix test
This commit is contained in:
Shashank Kumar 2024-03-30 05:34:07 +05:30 committed by GitHub
parent a7a2125e1d
commit 4c687b4335
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 190 additions and 61 deletions

View file

@ -0,0 +1,130 @@
package fr.free.nrw.commons.contributions;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.R;
import java.io.IOException;
import timber.log.Timber;
public class SetWallpaperWorker extends Worker {
private static final String NOTIFICATION_CHANNEL_ID = "set_wallpaper_channel";
private static final int NOTIFICATION_ID = 1;
public SetWallpaperWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
Context context = getApplicationContext();
createNotificationChannel(context);
showProgressNotification(context);
String imageUrl = getInputData().getString("imageUrl");
if (imageUrl == null) {
return Result.failure();
}
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(imageUrl))
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
final DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
if (dataSource.isFinished() && bitmap != null) {
Timber.d("Bitmap loaded from url %s", imageUrl.toString());
setWallpaper(context, Bitmap.createBitmap(bitmap));
dataSource.close();
}
}
@Override
public void onFailureImpl(DataSource dataSource) {
Timber.d("Error getting bitmap from image url %s", imageUrl.toString());
showNotification(context, "Setting Wallpaper Failed", "Failed to download image.");
if (dataSource != null) {
dataSource.close();
}
}
}, CallerThreadExecutor.getInstance());
return Result.success();
}
private void setWallpaper(Context context, Bitmap bitmap) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
try {
wallpaperManager.setBitmap(bitmap);
showNotification(context, "Wallpaper Set", "Wallpaper has been updated successfully.");
} catch (Exception e) {
Timber.e(e, "Error setting wallpaper");
showNotification(context, "Setting Wallpaper Failed", " "+e.getLocalizedMessage());
}
}
private void showProgressNotification(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.commons_logo)
.setContentTitle("Setting Wallpaper")
.setContentText("Please wait...")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setProgress(0, 0, true);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
private void showNotification(Context context, String title, String content) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.commons_logo)
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(false);
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
private void createNotificationChannel(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Wallpaper Setting";
String description = "Notifications for wallpaper setting progress";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}

View file

@ -164,9 +164,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
if (savedInstanceState != null) { if (savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable", false); editable = savedInstanceState.getBoolean("editable", false);
isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false); isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage", false);
if(null != binding.mediaDetailsPager) {
binding.mediaDetailsPager.setCurrentItem(savedInstanceState.getInt("current-page", 0), false);
}
} }
setHasOptionsMenu(true); setHasOptionsMenu(true);
initProvider(); initProvider();

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.utils; package fr.free.nrw.commons.utils;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.app.WallpaperManager; import android.app.WallpaperManager;
import android.content.Context; import android.content.Context;
@ -8,10 +10,14 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference; import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource; import com.facebook.datasource.DataSource;
@ -22,6 +28,7 @@ import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.SetWallpaperWorker;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -195,37 +202,23 @@ public class ImageUtils {
* @param imageUrl Url of the image * @param imageUrl Url of the image
*/ */
public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) { public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) {
showSettingWallpaperProgressBar(context);
Timber.d("Trying to set wallpaper from url %s", imageUrl.toString());
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(imageUrl)
.setAutoRotateEnabled(true)
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline(); enqueueSetWallpaperWork(context, imageUrl);
final DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() { }
@Override private static void createNotificationChannel(Context context) {
public void onNewResultImpl(@Nullable Bitmap bitmap) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (dataSource.isFinished() && bitmap != null) { CharSequence name = "Wallpaper Setting";
Timber.d("Bitmap loaded from url %s", imageUrl.toString()); String description = "Notifications for wallpaper setting progress";
setWallpaper(context, Bitmap.createBitmap(bitmap)); int importance = NotificationManager.IMPORTANCE_DEFAULT;
dataSource.close(); NotificationChannel channel = new NotificationChannel("set_wallpaper_channel", name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
} }
} }
@Override
public void onFailureImpl(DataSource dataSource) {
Timber.d("Error getting bitmap from image url %s", imageUrl.toString());
if (dataSource != null) {
dataSource.close();
}
}
}, CallerThreadExecutor.getInstance());
}
/** /**
* Calls the set avatar api to set the image url as user's avatar * Calls the set avatar api to set the image url as user's avatar
@ -272,23 +265,21 @@ public class ImageUtils {
} }
private static void setWallpaper(Context context, Bitmap bitmap) { public static void enqueueSetWallpaperWork(Context context, Uri imageUrl) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context); createNotificationChannel(context); // Ensure the notification channel is created
try {
wallpaperManager.setBitmap(bitmap); Data inputData = new Data.Builder()
ViewUtil.showLongToast(context, context.getString(R.string.wallpaper_set_successfully)); .putString("imageUrl", imageUrl.toString())
if (progressDialogWallpaper != null && progressDialogWallpaper.isShowing()) { .build();
progressDialogWallpaper.dismiss();
} OneTimeWorkRequest setWallpaperWork = new OneTimeWorkRequest.Builder(SetWallpaperWorker.class)
} catch (IOException e) { .setInputData(inputData)
Timber.e(e, "Error setting wallpaper"); .build();
ViewUtil.showLongToast(context, context.getString(R.string.wallpaper_set_unsuccessfully));
if (progressDialogWallpaper != null) { WorkManager.getInstance(context).enqueue(setWallpaperWork);
progressDialogWallpaper.cancel();
}
}
} }
private static void showSettingWallpaperProgressBar(Context context) { private static void showSettingWallpaperProgressBar(Context context) {
progressDialogWallpaper = ProgressDialog.show(context, context.getString(R.string.setting_wallpaper_dialog_title), progressDialogWallpaper = ProgressDialog.show(context, context.getString(R.string.setting_wallpaper_dialog_title),
context.getString(R.string.setting_wallpaper_dialog_message), true); context.getString(R.string.setting_wallpaper_dialog_message), true);

View file

@ -9,6 +9,7 @@ import android.view.MenuItem
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.work.testing.WorkManagerTestInitHelper
import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.soloader.SoLoader import com.facebook.soloader.SoLoader
import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.any
@ -79,8 +80,11 @@ class MediaDetailPagerFragmentUnitTests {
MockitoAnnotations.openMocks(this) MockitoAnnotations.openMocks(this)
context = ApplicationProvider.getApplicationContext() context = ApplicationProvider.getApplicationContext()
WorkManagerTestInitHelper.initializeTestWorkManager(context)
OkHttpConnectionFactory.CLIENT = createTestClient() OkHttpConnectionFactory.CLIENT = createTestClient()
SoLoader.setInTestMode() SoLoader.setInTestMode()

View file

@ -3,14 +3,22 @@ package fr.free.nrw.commons.utils
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.work.Data
import androidx.work.ListenableWorker
import androidx.work.WorkManager
import androidx.work.testing.TestListenableWorkerBuilder
import androidx.work.testing.WorkManagerTestInitHelper
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.contributions.SetWallpaperWorker
import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
@ -24,6 +32,7 @@ import java.lang.reflect.Field
import java.lang.reflect.Method import java.lang.reflect.Method
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class) @Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED) @LooperMode(LooperMode.Mode.PAUSED)
@ -31,9 +40,6 @@ class ImageUtilsTest {
private lateinit var context: Context private lateinit var context: Context
@Mock
private lateinit var bitmap: Bitmap
@Mock @Mock
private lateinit var progressDialogWallpaper: ProgressDialog private lateinit var progressDialogWallpaper: ProgressDialog
@ -43,10 +49,18 @@ class ImageUtilsTest {
@Mock @Mock
private lateinit var compositeDisposable: CompositeDisposable private lateinit var compositeDisposable: CompositeDisposable
@Mock
private lateinit var imageUri: Uri
private lateinit var workManager: WorkManager
@Before @Before
fun setup() { fun setup() {
MockitoAnnotations.openMocks(this) MockitoAnnotations.openMocks(this)
context = ApplicationProvider.getApplicationContext() context = ApplicationProvider.getApplicationContext()
WorkManagerTestInitHelper.initializeTestWorkManager(context)
workManager = WorkManager.getInstance(context)
} }
@Test @Test
@ -87,20 +101,13 @@ class ImageUtilsTest {
fun testSetWallpaper() { fun testSetWallpaper() {
val mockImageUtils = mock(ImageUtils::class.java) val mockImageUtils = mock(ImageUtils::class.java)
val method: Method = ImageUtils::class.java.getDeclaredMethod( val method: Method = ImageUtils::class.java.getDeclaredMethod(
"setWallpaper", "enqueueSetWallpaperWork",
Context::class.java, Context::class.java,
Bitmap::class.java Uri::class.java
) )
method.isAccessible = true method.isAccessible = true
`when`(progressDialogWallpaper.isShowing).thenReturn(true) method.invoke(mockImageUtils, context, imageUri)
val progressDialogWallpaperField: Field =
ImageUtils::class.java.getDeclaredField("progressDialogWallpaper")
progressDialogWallpaperField.isAccessible = true
progressDialogWallpaperField.set(mockImageUtils, progressDialogWallpaper)
method.invoke(mockImageUtils, context, bitmap)
} }
@Test @Test
@ -190,5 +197,4 @@ class ImageUtilsTest {
method.isAccessible = true method.isAccessible = true
method.invoke(mockImageUtils, null) method.invoke(mockImageUtils, null)
} }
} }