package ml.docilealligator.infinityforreddit.services; import android.app.Notification; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.NetworkRequest; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.os.PersistableBundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; import androidx.core.app.NotificationChannelCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import com.bumptech.glide.Glide; import org.greenrobot.eventbus.EventBus; import java.io.FileNotFoundException; import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; import jp.wasabeef.glide.transformations.CropTransformation; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; import ml.docilealligator.infinityforreddit.events.SubmitChangeAvatarEvent; import ml.docilealligator.infinityforreddit.events.SubmitChangeBannerEvent; import ml.docilealligator.infinityforreddit.events.SubmitSaveProfileEvent; import ml.docilealligator.infinityforreddit.utils.EditProfileUtils; import ml.docilealligator.infinityforreddit.utils.NotificationUtils; import retrofit2.Retrofit; public class EditProfileService extends JobService { public static final String EXTRA_ACCESS_TOKEN = "EAT"; public static final String EXTRA_ACCOUNT_NAME = "EAN"; public static final String EXTRA_DISPLAY_NAME = "EDN"; public static final String EXTRA_ABOUT_YOU = "EAY"; public static final String EXTRA_POST_TYPE = "EPT"; public static final String EXTRA_MEDIA_URI = "EU"; public static final int EXTRA_POST_TYPE_UNKNOWN = 0x500; public static final int EXTRA_POST_TYPE_CHANGE_BANNER = 0x501; public static final int EXTRA_POST_TYPE_CHANGE_AVATAR = 0x502; public static final int EXTRA_POST_TYPE_SAVE_EDIT_PROFILE = 0x503; private static final int MAX_BANNER_WIDTH = 1280; private static final int MIN_BANNER_WIDTH = 640; private static final int AVATAR_SIZE = 256; private static int JOB_ID = 10000; @Inject @Named("oauth") Retrofit mOauthRetrofit; @Inject CustomThemeWrapper mCustomThemeWrapper; @Inject Executor mExecutor; private Handler handler; public static JobInfo constructJobInfo(Context context, long contentEstimatedBytes, PersistableBundle extras) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { return new JobInfo.Builder(JOB_ID++, new ComponentName(context, EditProfileService.class)) .setUserInitiated(true) .setRequiredNetwork(new NetworkRequest.Builder().clearCapabilities().build()) .setEstimatedNetworkBytes(0, contentEstimatedBytes + 500) .setExtras(extras) .build(); } else { return new JobInfo.Builder(JOB_ID++, new ComponentName(context, EditProfileService.class)) .setOverrideDeadline(0) .setExtras(extras) .build(); } } @Override public void onCreate() { super.onCreate(); ((Infinity) getApplication()).getAppComponent().inject(this); handler = new Handler(); } @Override public boolean onStartJob(JobParameters params) { NotificationChannelCompat serviceChannel = new NotificationChannelCompat.Builder( NotificationUtils.CHANNEL_SUBMIT_POST, NotificationManagerCompat.IMPORTANCE_LOW) .setName(NotificationUtils.CHANNEL_SUBMIT_POST) .build(); NotificationManagerCompat manager = NotificationManagerCompat.from(this); manager.createNotificationChannel(serviceChannel); int randomNotificationIdOffset = new Random().nextInt(10000); PersistableBundle bundle = params.getExtras(); String accessToken = bundle.getString(EXTRA_ACCESS_TOKEN); String accountName = bundle.getString(EXTRA_ACCOUNT_NAME); final int postType = bundle.getInt(EXTRA_POST_TYPE, EXTRA_POST_TYPE_UNKNOWN); switch (postType) { case EXTRA_POST_TYPE_CHANGE_BANNER: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { setNotification(params, NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_change_banner), JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); } else { manager.notify(NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_change_banner)); } break; case EXTRA_POST_TYPE_CHANGE_AVATAR: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { setNotification(params, NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_change_avatar), JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); } else { manager.notify(NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_change_avatar)); } break; case EXTRA_POST_TYPE_SAVE_EDIT_PROFILE: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { setNotification(params, NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_save_profile), JobService.JOB_END_NOTIFICATION_POLICY_REMOVE); } else { manager.notify(NotificationUtils.EDIT_PROFILE_SERVICE_NOTIFICATION_ID + randomNotificationIdOffset, createNotification(R.string.submit_save_profile)); } break; default: case EXTRA_POST_TYPE_UNKNOWN: return false; } mExecutor.execute(() -> { switch (postType) { case EXTRA_POST_TYPE_CHANGE_BANNER: submitChangeBannerSync(params, accessToken, Uri.parse(bundle.getString(EXTRA_MEDIA_URI)), accountName); break; case EXTRA_POST_TYPE_CHANGE_AVATAR: submitChangeAvatarSync(params, accessToken, Uri.parse(bundle.getString(EXTRA_MEDIA_URI)), accountName); break; case EXTRA_POST_TYPE_SAVE_EDIT_PROFILE: submitSaveEditProfileSync( params, accessToken, accountName, bundle.getString(EXTRA_DISPLAY_NAME), bundle.getString(EXTRA_ABOUT_YOU) ); break; } }); return true; } @Override public boolean onStopJob(JobParameters params) { return false; } @WorkerThread private void submitChangeBannerSync(JobParameters parameters, String accessToken, Uri mediaUri, String accountName) { try { final int width = getWidthBanner(mediaUri); final int height = Math.round(width * 3 / 10f); // ratio 10:3 CropTransformation bannerCrop = new CropTransformation(width, height, CropTransformation.CropType.CENTER); Bitmap resource = Glide.with(this).asBitmap().skipMemoryCache(true) .load(mediaUri).transform(bannerCrop).submit().get(); String potentialError = EditProfileUtils.uploadBannerSync(mOauthRetrofit, accessToken, accountName, resource); if (potentialError == null) { //Successful handler.post(() -> EventBus.getDefault().post(new SubmitChangeBannerEvent(true, ""))); jobFinished(parameters, false); } else { handler.post(() -> EventBus.getDefault().post(new SubmitChangeBannerEvent(false, potentialError))); jobFinished(parameters, false); } } catch (InterruptedException | ExecutionException | FileNotFoundException e) { e.printStackTrace(); handler.post(() -> EventBus.getDefault().post(new SubmitChangeBannerEvent(false, e.getLocalizedMessage()))); jobFinished(parameters, false); } } @WorkerThread private void submitChangeAvatarSync(JobParameters parameters, String accessToken, Uri mediaUri, String accountName) { try { final CropTransformation avatarCrop = new CropTransformation(AVATAR_SIZE, AVATAR_SIZE, CropTransformation.CropType.CENTER); final Bitmap resource = Glide.with(this).asBitmap().skipMemoryCache(true) .load(mediaUri).transform(avatarCrop).submit().get(); String potentialError = EditProfileUtils.uploadAvatarSync(mOauthRetrofit, accessToken, accountName, resource); if (potentialError == null) { //Successful handler.post(() -> EventBus.getDefault().post(new SubmitChangeAvatarEvent(true, ""))); jobFinished(parameters, false); } else { handler.post(() -> EventBus.getDefault().post(new SubmitChangeAvatarEvent(false, potentialError))); jobFinished(parameters, false); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); handler.post(() -> EventBus.getDefault().post(new SubmitChangeAvatarEvent(false, e.getLocalizedMessage()))); jobFinished(parameters, false); } } @WorkerThread private void submitSaveEditProfileSync(JobParameters parameters, @Nullable String accessToken, @NonNull String accountName, String displayName, String publicDesc ) { String potentialError = EditProfileUtils.updateProfileSync(mOauthRetrofit, accessToken, accountName, displayName, publicDesc); if (potentialError == null) { //Successful handler.post(() -> EventBus.getDefault().post(new SubmitSaveProfileEvent(true, ""))); jobFinished(parameters, false); } else { handler.post(() -> EventBus.getDefault().post(new SubmitSaveProfileEvent(false, potentialError))); jobFinished(parameters, false); } } private Notification createNotification(int stringResId) { return new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_SUBMIT_POST) .setContentTitle(getString(stringResId)) .setContentText(getString(R.string.please_wait)) .setSmallIcon(R.drawable.ic_notification) .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) .build(); } private int getWidthBanner(Uri mediaUri) throws FileNotFoundException { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(getContentResolver().openInputStream(mediaUri), null, options); return Math.max(Math.min(options.outWidth, MAX_BANNER_WIDTH), MIN_BANNER_WIDTH); } }