Fix #215
This commit is contained in:
1 parent
e12abb66f7
commit
5513d47b6d
7 files changed
+683
-681
No files matched your search
@@ -23,6 +23,7 @@
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
@@ -61,6 +61,7 @@ import com.hippo.ehviewer.client.parser.EventPaneParser;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
import com.hippo.ehviewer.ui.CommonOperations;
|
||||
import com.hippo.ehviewer.ui.EhActivity;
|
||||
import com.hippo.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
@@ -532,9 +533,9 @@ public class EhApplication extends SceneApplication {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Activity getTopActivity() {
|
||||
public EhActivity getTopActivity() {
|
||||
if (!mActivityList.isEmpty()) {
|
||||
return mActivityList.get(mActivityList.size() - 1);
|
||||
return (EhActivity) mActivityList.get(mActivityList.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -76,6 +76,8 @@ public class Settings {
|
||||
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
|
||||
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
|
||||
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
|
||||
|
||||
public static final String KEY_NOTIFICATION_REQUIRED = "notification_required";
|
||||
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
|
||||
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
|
||||
public static final String KEY_MEDIA_SCAN = "media_scan";
|
||||
@@ -1183,4 +1185,12 @@ public class Settings {
|
||||
LIST_THUMB_SIZE_INITED = true;
|
||||
return size;
|
||||
}
|
||||
|
||||
public static boolean getNotificationRequired() {
|
||||
return getBoolean(KEY_NOTIFICATION_REQUIRED, false);
|
||||
}
|
||||
|
||||
public static void putNotificationRequired() {
|
||||
putBoolean(KEY_NOTIFICATION_REQUIRED, true);
|
||||
}
|
||||
}
|
||||
@@ -1,581 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.ehviewer.download;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationChannelCompat;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.DownloadInfo;
|
||||
import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.ehviewer.ui.scene.DownloadsScene;
|
||||
import com.hippo.scene.StageActivity;
|
||||
import com.hippo.util.ReadableTime;
|
||||
import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
import com.hippo.yorozuya.collect.LongList;
|
||||
import com.hippo.yorozuya.collect.SparseJBArray;
|
||||
import com.hippo.yorozuya.collect.SparseJLArray;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class DownloadService extends Service implements DownloadManager.DownloadListener {
|
||||
|
||||
public static final String ACTION_START = "start";
|
||||
public static final String ACTION_START_RANGE = "start_range";
|
||||
public static final String ACTION_START_ALL = "start_all";
|
||||
public static final String ACTION_STOP = "stop";
|
||||
public static final String ACTION_STOP_RANGE = "stop_range";
|
||||
public static final String ACTION_STOP_CURRENT = "stop_current";
|
||||
public static final String ACTION_STOP_ALL = "stop_all";
|
||||
public static final String ACTION_DELETE = "delete";
|
||||
public static final String ACTION_DELETE_RANGE = "delete_range";
|
||||
|
||||
public static final String ACTION_CLEAR = "clear";
|
||||
|
||||
public static final String KEY_GALLERY_INFO = "gallery_info";
|
||||
public static final String KEY_LABEL = "label";
|
||||
public static final String KEY_GID = "gid";
|
||||
public static final String KEY_GID_LIST = "gid_list";
|
||||
|
||||
private static final int ID_DOWNLOADING = 1;
|
||||
private static final int ID_DOWNLOADED = 2;
|
||||
private static final int ID_509 = 3;
|
||||
private final static SparseJBArray sItemStateArray = new SparseJBArray();
|
||||
private final static SparseJLArray<String> sItemTitleArray = new SparseJLArray<>();
|
||||
private static int sFailedCount;
|
||||
private static int sFinishedCount;
|
||||
private static int sDownloadedCount;
|
||||
@Nullable
|
||||
private NotificationManagerCompat mNotifyManager;
|
||||
@Nullable
|
||||
private DownloadManager mDownloadManager;
|
||||
private NotificationCompat.Builder mDownloadingBuilder;
|
||||
private NotificationCompat.Builder mDownloadedBuilder;
|
||||
private NotificationCompat.Builder m509dBuilder;
|
||||
private NotificationDelay mDownloadingDelay;
|
||||
private NotificationDelay mDownloadedDelay;
|
||||
private NotificationDelay m509Delay;
|
||||
private String CHANNEL_ID;
|
||||
|
||||
public static void clear() {
|
||||
sFailedCount = 0;
|
||||
sFinishedCount = 0;
|
||||
sDownloadedCount = 0;
|
||||
sItemStateArray.clear();
|
||||
sItemTitleArray.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
CHANNEL_ID = getPackageName() + ".download";
|
||||
mNotifyManager = NotificationManagerCompat.from(this);
|
||||
mNotifyManager.createNotificationChannel(
|
||||
new NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName(getString(R.string.download_service))
|
||||
.build());
|
||||
mDownloadManager = EhApplication.getDownloadManager(getApplicationContext());
|
||||
mDownloadManager.setDownloadListener(this);
|
||||
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
mDownloadingBuilder.setContentTitle(getString(R.string.download_service))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true);
|
||||
|
||||
startForeground(ID_DOWNLOADING, mDownloadingBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
mNotifyManager = null;
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.setDownloadListener(null);
|
||||
mDownloadManager = null;
|
||||
}
|
||||
mDownloadingBuilder = null;
|
||||
mDownloadedBuilder = null;
|
||||
m509dBuilder = null;
|
||||
if (mDownloadingDelay != null) {
|
||||
mDownloadingDelay.release();
|
||||
}
|
||||
if (mDownloadedDelay != null) {
|
||||
mDownloadedDelay.release();
|
||||
}
|
||||
if (m509Delay != null) {
|
||||
m509Delay.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
handleIntent(intent);
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
String action = null;
|
||||
if (intent != null) {
|
||||
action = intent.getAction();
|
||||
}
|
||||
|
||||
if (ACTION_START.equals(action)) {
|
||||
GalleryInfo gi = intent.getParcelableExtra(KEY_GALLERY_INFO);
|
||||
String label = intent.getStringExtra(KEY_LABEL);
|
||||
if (gi != null && mDownloadManager != null) {
|
||||
mDownloadManager.startDownload(gi, label);
|
||||
}
|
||||
} else if (ACTION_START_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.startRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_START_ALL.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.startAllDownload();
|
||||
}
|
||||
} else if (ACTION_STOP.equals(action)) {
|
||||
long gid = intent.getLongExtra(KEY_GID, -1);
|
||||
if (gid != -1 && mDownloadManager != null) {
|
||||
mDownloadManager.stopDownload(gid);
|
||||
}
|
||||
} else if (ACTION_STOP_CURRENT.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopCurrentDownload();
|
||||
}
|
||||
} else if (ACTION_STOP_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.stopRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_STOP_ALL.equals(action)) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopAllDownload();
|
||||
}
|
||||
} else if (ACTION_DELETE.equals(action)) {
|
||||
long gid = intent.getLongExtra(KEY_GID, -1);
|
||||
if (gid != -1 && mDownloadManager != null) {
|
||||
mDownloadManager.deleteDownload(gid);
|
||||
}
|
||||
} else if (ACTION_DELETE_RANGE.equals(action)) {
|
||||
LongList gidList = intent.getParcelableExtra(KEY_GID_LIST);
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager.deleteRangeDownload(gidList);
|
||||
}
|
||||
} else if (ACTION_CLEAR.equals(action)) {
|
||||
clear();
|
||||
}
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
throw new IllegalStateException("No bindService");
|
||||
}
|
||||
|
||||
private void ensureDownloadingBuilder() {
|
||||
if (mDownloadingBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent stopAllIntent = new Intent(this, DownloadService.class);
|
||||
stopAllIntent.setAction(ACTION_STOP_ALL);
|
||||
PendingIntent piStopAll = PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadingBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.addAction(R.drawable.v_pause_x24, getString(R.string.stat_download_action_stop_all), piStopAll)
|
||||
.setShowWhen(false)
|
||||
.setChannelId(CHANNEL_ID);
|
||||
|
||||
mDownloadingDelay = new NotificationDelay(this, mNotifyManager, mDownloadingBuilder, ID_DOWNLOADING);
|
||||
}
|
||||
|
||||
private void ensureDownloadedBuilder() {
|
||||
if (mDownloadedBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent clearIntent = new Intent(this, DownloadService.class);
|
||||
clearIntent.setAction(ACTION_CLEAR);
|
||||
PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE);
|
||||
Intent activityIntent = new Intent(this, MainActivity.class);
|
||||
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
|
||||
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadedBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentTitle(getString(R.string.stat_download_done_title))
|
||||
.setDeleteIntent(piClear)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(piActivity);
|
||||
|
||||
mDownloadedDelay = new NotificationDelay(this, mNotifyManager, mDownloadedBuilder, ID_DOWNLOADED);
|
||||
}
|
||||
|
||||
private void ensure509Builder() {
|
||||
if (m509dBuilder != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
m509dBuilder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_baseline_warning_24)
|
||||
.setContentText(getString(R.string.stat_509_alert_title))
|
||||
.setContentText(getString(R.string.stat_509_alert_text))
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text)))
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR);
|
||||
|
||||
m509Delay = new NotificationDelay(this, mNotifyManager, m509dBuilder, ID_509);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGet509() {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager.stopAllDownload();
|
||||
}
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensure509Builder();
|
||||
m509dBuilder.setWhen(System.currentTimeMillis());
|
||||
m509Delay.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putLong(DownloadsScene.KEY_GID, info.gid);
|
||||
Intent activityIntent = new Intent(this, MainActivity.class);
|
||||
activityIntent.setAction(StageActivity.ACTION_START_SCENE);
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene.class.getName());
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle);
|
||||
PendingIntent piActivity = PendingIntent.getActivity(DownloadService.this, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentIntent(piActivity);
|
||||
|
||||
mDownloadingDelay.startForeground();
|
||||
}
|
||||
|
||||
private void onUpdate(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
ensureDownloadingBuilder();
|
||||
|
||||
long speed = info.speed;
|
||||
if (speed < 0) {
|
||||
speed = 0;
|
||||
}
|
||||
String text = FileUtils.humanReadableByteCount(speed, false) + "/s";
|
||||
long remaining = info.remaining;
|
||||
if (remaining >= 0) {
|
||||
text = getString(R.string.download_speed_text_2, text, ReadableTime.getShortTimeInterval(remaining));
|
||||
} else {
|
||||
text = getString(R.string.download_speed_text, text);
|
||||
}
|
||||
mDownloadingBuilder.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(text)
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
||||
.setSubText(info.total == -1 || info.finished == -1 ? null : info.finished + "/" + info.total)
|
||||
.setProgress(info.total, info.finished, false);
|
||||
|
||||
mDownloadingDelay.startForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(DownloadInfo info) {
|
||||
onUpdate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetPage(DownloadInfo info) {
|
||||
onUpdate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay.cancel();
|
||||
}
|
||||
|
||||
ensureDownloadedBuilder();
|
||||
|
||||
boolean finish = info.state == DownloadInfo.STATE_FINISH;
|
||||
long gid = info.gid;
|
||||
int index = sItemStateArray.indexOfKey(gid);
|
||||
if (index < 0) { // Not contain
|
||||
sItemStateArray.put(gid, finish);
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
|
||||
sDownloadedCount++;
|
||||
if (finish) {
|
||||
sFinishedCount++;
|
||||
} else {
|
||||
sFailedCount++;
|
||||
}
|
||||
} else { // Contain
|
||||
boolean oldFinish = sItemStateArray.valueAt(index);
|
||||
sItemStateArray.put(gid, finish);
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info));
|
||||
if (oldFinish && !finish) {
|
||||
sFinishedCount--;
|
||||
sFailedCount++;
|
||||
} else if (!oldFinish && finish) {
|
||||
sFinishedCount++;
|
||||
sFailedCount--;
|
||||
}
|
||||
}
|
||||
|
||||
String text;
|
||||
boolean needStyle;
|
||||
if (sFinishedCount != 0 && sFailedCount == 0) {
|
||||
if (sFinishedCount == 1) {
|
||||
if (sItemTitleArray.size() >= 1) {
|
||||
text = getString(R.string.stat_download_done_line_succeeded, sItemTitleArray.valueAt(0));
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null");
|
||||
text = getString(R.string.error_unknown);
|
||||
}
|
||||
needStyle = false;
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
} else if (sFinishedCount == 0 && sFailedCount != 0) {
|
||||
if (sFailedCount == 1) {
|
||||
if (sItemTitleArray.size() >= 1) {
|
||||
text = getString(R.string.stat_download_done_line_failed, sItemTitleArray.valueAt(0));
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null");
|
||||
text = getString(R.string.error_unknown);
|
||||
}
|
||||
needStyle = false;
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_failed, sFailedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount);
|
||||
needStyle = true;
|
||||
}
|
||||
|
||||
NotificationCompat.InboxStyle style;
|
||||
if (needStyle) {
|
||||
style = new NotificationCompat.InboxStyle();
|
||||
style.setBigContentTitle(getString(R.string.stat_download_done_title));
|
||||
SparseJBArray stateArray = sItemStateArray;
|
||||
for (int i = 0, n = stateArray.size(); i < n; i++) {
|
||||
long id = stateArray.keyAt(i);
|
||||
boolean fin = stateArray.valueAt(i);
|
||||
String title = sItemTitleArray.get(id);
|
||||
if (title == null) {
|
||||
continue;
|
||||
}
|
||||
style.addLine(getString(fin ? R.string.stat_download_done_line_succeeded :
|
||||
R.string.stat_download_done_line_failed, title));
|
||||
}
|
||||
} else {
|
||||
style = null;
|
||||
}
|
||||
|
||||
mDownloadedBuilder.setContentText(text)
|
||||
.setStyle(style)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setNumber(sDownloadedCount);
|
||||
|
||||
mDownloadedDelay.show();
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel(DownloadInfo info) {
|
||||
if (mNotifyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay.cancel();
|
||||
}
|
||||
|
||||
checkStopSelf();
|
||||
}
|
||||
|
||||
private void checkStopSelf() {
|
||||
if (mDownloadManager == null || mDownloadManager.isIdle()) {
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Include all notification in one delay
|
||||
// Avoid frequent notification
|
||||
private static class NotificationDelay implements Runnable {
|
||||
|
||||
private static final int OPS_NOTIFY = 0;
|
||||
private static final int OPS_CANCEL = 1;
|
||||
private static final int OPS_START_FOREGROUND = 2;
|
||||
private static final long DELAY = 1000; // 1s
|
||||
private final NotificationManagerCompat mNotifyManager;
|
||||
private final NotificationCompat.Builder mBuilder;
|
||||
private final int mId;
|
||||
private Service mService;
|
||||
private long mLastTime;
|
||||
private boolean mPosted;
|
||||
// false for show, true for cancel
|
||||
@Ops
|
||||
private int mOps;
|
||||
|
||||
public NotificationDelay(Service service, NotificationManagerCompat notifyManager,
|
||||
NotificationCompat.Builder builder, int id) {
|
||||
mService = service;
|
||||
mNotifyManager = notifyManager;
|
||||
mBuilder = builder;
|
||||
mId = id;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
mService = null;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_NOTIFY;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.notify(mId, mBuilder.build());
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_NOTIFY;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
mLastTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_CANCEL;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.cancel(mId);
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_CANCEL;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startForeground() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_START_FOREGROUND;
|
||||
} else {
|
||||
long now = SystemClock.currentThreadTimeMillis();
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
if (mService != null) {
|
||||
mService.startForeground(mId, mBuilder.build());
|
||||
}
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_START_FOREGROUND;
|
||||
mPosted = true;
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mPosted = false;
|
||||
switch (mOps) {
|
||||
case OPS_NOTIFY:
|
||||
mNotifyManager.notify(mId, mBuilder.build());
|
||||
break;
|
||||
case OPS_CANCEL:
|
||||
mNotifyManager.cancel(mId);
|
||||
break;
|
||||
case OPS_START_FOREGROUND:
|
||||
if (mService != null) {
|
||||
mService.startForeground(mId, mBuilder.build());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef({OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Ops {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.download
|
||||
|
||||
import android.Manifest
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.DownloadInfo
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.scene.DownloadsScene
|
||||
import com.hippo.scene.StageActivity
|
||||
import com.hippo.util.ReadableTime
|
||||
import com.hippo.yorozuya.FileUtils
|
||||
import com.hippo.yorozuya.SimpleHandler
|
||||
import com.hippo.yorozuya.collect.LongList
|
||||
import com.hippo.yorozuya.collect.SparseJBArray
|
||||
import com.hippo.yorozuya.collect.SparseJLArray
|
||||
|
||||
class DownloadService : Service(), DownloadManager.DownloadListener {
|
||||
private var mNotifyManager: NotificationManagerCompat? = null
|
||||
private var mDownloadManager: DownloadManager? = null
|
||||
private var mDownloadingBuilder: NotificationCompat.Builder? = null
|
||||
private var mDownloadedBuilder: NotificationCompat.Builder? = null
|
||||
private var m509dBuilder: NotificationCompat.Builder? = null
|
||||
private var mDownloadingDelay: NotificationDelay? = null
|
||||
private var mDownloadedDelay: NotificationDelay? = null
|
||||
private var m509Delay: NotificationDelay? = null
|
||||
private var mChannelID: String? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mChannelID = "$packageName.download"
|
||||
mNotifyManager = NotificationManagerCompat.from(this)
|
||||
mNotifyManager!!.createNotificationChannel(
|
||||
NotificationChannelCompat.Builder(
|
||||
mChannelID!!,
|
||||
NotificationManagerCompat.IMPORTANCE_LOW
|
||||
)
|
||||
.setName(getString(R.string.download_service))
|
||||
.build()
|
||||
)
|
||||
mDownloadManager = EhApplication.getDownloadManager(applicationContext)
|
||||
mDownloadManager!!.setDownloadListener(this)
|
||||
ensureDownloadingBuilder()
|
||||
mDownloadingBuilder!!.setContentTitle(getString(R.string.download_service))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
startForeground(ID_DOWNLOADING, mDownloadingBuilder!!.build())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
mNotifyManager = null
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.setDownloadListener(null)
|
||||
mDownloadManager = null
|
||||
}
|
||||
mDownloadingBuilder = null
|
||||
mDownloadedBuilder = null
|
||||
m509dBuilder = null
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
handleIntent(intent)
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
var action: String? = null
|
||||
if (intent != null) {
|
||||
action = intent.action
|
||||
}
|
||||
if (ACTION_START == action) {
|
||||
val gi = intent!!.getParcelableExtra<GalleryInfo>(KEY_GALLERY_INFO)
|
||||
val label = intent.getStringExtra(KEY_LABEL)
|
||||
if (gi != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.startDownload(gi, label)
|
||||
}
|
||||
} else if (ACTION_START_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.startRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_START_ALL == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.startAllDownload()
|
||||
}
|
||||
} else if (ACTION_STOP == action) {
|
||||
val gid = intent!!.getLongExtra(KEY_GID, -1)
|
||||
if (gid != -1L && mDownloadManager != null) {
|
||||
mDownloadManager!!.stopDownload(gid)
|
||||
}
|
||||
} else if (ACTION_STOP_CURRENT == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopCurrentDownload()
|
||||
}
|
||||
} else if (ACTION_STOP_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.stopRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_STOP_ALL == action) {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopAllDownload()
|
||||
}
|
||||
} else if (ACTION_DELETE == action) {
|
||||
val gid = intent!!.getLongExtra(KEY_GID, -1)
|
||||
if (gid != -1L && mDownloadManager != null) {
|
||||
mDownloadManager!!.deleteDownload(gid)
|
||||
}
|
||||
} else if (ACTION_DELETE_RANGE == action) {
|
||||
val gidList = intent!!.getParcelableExtra<LongList>(KEY_GID_LIST)
|
||||
if (gidList != null && mDownloadManager != null) {
|
||||
mDownloadManager!!.deleteRangeDownload(gidList)
|
||||
}
|
||||
} else if (ACTION_CLEAR == action) {
|
||||
clear()
|
||||
}
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
throw IllegalStateException("No bindService")
|
||||
}
|
||||
|
||||
private fun ensureDownloadingBuilder() {
|
||||
if (mDownloadingBuilder != null) {
|
||||
return
|
||||
}
|
||||
val stopAllIntent = Intent(this, DownloadService::class.java)
|
||||
stopAllIntent.action = ACTION_STOP_ALL
|
||||
val piStopAll =
|
||||
PendingIntent.getService(this, 0, stopAllIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
mDownloadingBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||
.addAction(
|
||||
R.drawable.v_pause_x24,
|
||||
getString(R.string.stat_download_action_stop_all),
|
||||
piStopAll
|
||||
)
|
||||
.setShowWhen(false)
|
||||
.setChannelId(mChannelID!!)
|
||||
mDownloadingDelay =
|
||||
NotificationDelay(this, mNotifyManager!!, mDownloadingBuilder!!, ID_DOWNLOADING)
|
||||
}
|
||||
|
||||
private fun ensureDownloadedBuilder() {
|
||||
if (mDownloadedBuilder != null) {
|
||||
return
|
||||
}
|
||||
val clearIntent = Intent(this, DownloadService::class.java)
|
||||
clearIntent.action = ACTION_CLEAR
|
||||
val piClear = PendingIntent.getService(this, 0, clearIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE)
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
activityIntent.action = StageActivity.ACTION_START_SCENE
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
|
||||
val piActivity = PendingIntent.getActivity(
|
||||
this@DownloadService, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
mDownloadedBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentTitle(getString(R.string.stat_download_done_title))
|
||||
.setDeleteIntent(piClear)
|
||||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(piActivity)
|
||||
mDownloadedDelay =
|
||||
NotificationDelay(this, mNotifyManager!!, mDownloadedBuilder!!, ID_DOWNLOADED)
|
||||
}
|
||||
|
||||
private fun ensure509Builder() {
|
||||
if (m509dBuilder != null) {
|
||||
return
|
||||
}
|
||||
m509dBuilder = NotificationCompat.Builder(applicationContext, mChannelID!!)
|
||||
.setSmallIcon(R.drawable.ic_baseline_warning_24)
|
||||
.setContentText(getString(R.string.stat_509_alert_title))
|
||||
.setContentText(getString(R.string.stat_509_alert_text))
|
||||
.setStyle(
|
||||
NotificationCompat.BigTextStyle().bigText(getString(R.string.stat_509_alert_text))
|
||||
)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(false)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
m509Delay = NotificationDelay(this, mNotifyManager!!, m509dBuilder!!, ID_509)
|
||||
}
|
||||
|
||||
override fun onGet509() {
|
||||
if (mDownloadManager != null) {
|
||||
mDownloadManager!!.stopAllDownload()
|
||||
}
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensure509Builder()
|
||||
m509dBuilder!!.setWhen(System.currentTimeMillis())
|
||||
m509Delay!!.show()
|
||||
}
|
||||
|
||||
override fun onStart(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensureDownloadingBuilder()
|
||||
val bundle = Bundle()
|
||||
bundle.putLong(DownloadsScene.KEY_GID, info.gid)
|
||||
val activityIntent = Intent(this, MainActivity::class.java)
|
||||
activityIntent.action = StageActivity.ACTION_START_SCENE
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
|
||||
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
|
||||
val piActivity = PendingIntent.getActivity(
|
||||
this@DownloadService, 0,
|
||||
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(null)
|
||||
.setSubText(null)
|
||||
.setProgress(0, 0, true)
|
||||
.setContentIntent(piActivity)
|
||||
mDownloadingDelay!!.startForeground()
|
||||
}
|
||||
|
||||
private fun onUpdate(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
ensureDownloadingBuilder()
|
||||
var speed = info.speed
|
||||
if (speed < 0) {
|
||||
speed = 0
|
||||
}
|
||||
var text = FileUtils.humanReadableByteCount(speed, false) + "/s"
|
||||
val remaining = info.remaining
|
||||
text = if (remaining >= 0) {
|
||||
getString(
|
||||
R.string.download_speed_text_2,
|
||||
text,
|
||||
ReadableTime.getShortTimeInterval(remaining)
|
||||
)
|
||||
} else {
|
||||
getString(R.string.download_speed_text, text)
|
||||
}
|
||||
mDownloadingBuilder!!.setContentTitle(EhUtils.getSuitableTitle(info))
|
||||
.setContentText(text)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
.setSubText(if (info.total == -1 || info.finished == -1) null else info.finished.toString() + "/" + info.total)
|
||||
.setProgress(info.total, info.finished, false)
|
||||
mDownloadingDelay!!.startForeground()
|
||||
}
|
||||
|
||||
override fun onDownload(info: DownloadInfo) {
|
||||
onUpdate(info)
|
||||
}
|
||||
|
||||
override fun onGetPage(info: DownloadInfo) {
|
||||
onUpdate(info)
|
||||
}
|
||||
|
||||
override fun onFinish(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay!!.cancel()
|
||||
}
|
||||
ensureDownloadedBuilder()
|
||||
val finish = info.state == DownloadInfo.STATE_FINISH
|
||||
val gid = info.gid
|
||||
val index = sItemStateArray.indexOfKey(gid)
|
||||
if (index < 0) { // Not contain
|
||||
sItemStateArray.put(gid, finish)
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
|
||||
sDownloadedCount++
|
||||
if (finish) {
|
||||
sFinishedCount++
|
||||
} else {
|
||||
sFailedCount++
|
||||
}
|
||||
} else { // Contain
|
||||
val oldFinish = sItemStateArray.valueAt(index)
|
||||
sItemStateArray.put(gid, finish)
|
||||
sItemTitleArray.put(gid, EhUtils.getSuitableTitle(info))
|
||||
if (oldFinish && !finish) {
|
||||
sFinishedCount--
|
||||
sFailedCount++
|
||||
} else if (!oldFinish && finish) {
|
||||
sFinishedCount++
|
||||
sFailedCount--
|
||||
}
|
||||
}
|
||||
val text: String
|
||||
val needStyle: Boolean
|
||||
if (sFinishedCount != 0 && sFailedCount == 0) {
|
||||
if (sFinishedCount == 1) {
|
||||
text = if (sItemTitleArray.size() >= 1) {
|
||||
getString(
|
||||
R.string.stat_download_done_line_succeeded,
|
||||
sItemTitleArray.valueAt(0)
|
||||
)
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null")
|
||||
getString(R.string.error_unknown)
|
||||
}
|
||||
needStyle = false
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_succeeded, sFinishedCount)
|
||||
needStyle = true
|
||||
}
|
||||
} else if (sFinishedCount == 0 && sFailedCount != 0) {
|
||||
if (sFailedCount == 1) {
|
||||
text = if (sItemTitleArray.size() >= 1) {
|
||||
getString(
|
||||
R.string.stat_download_done_line_failed,
|
||||
sItemTitleArray.valueAt(0)
|
||||
)
|
||||
} else {
|
||||
Log.d("TAG", "WTF, sItemTitleArray is null")
|
||||
getString(R.string.error_unknown)
|
||||
}
|
||||
needStyle = false
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_failed, sFailedCount)
|
||||
needStyle = true
|
||||
}
|
||||
} else {
|
||||
text = getString(R.string.stat_download_done_text_mix, sFinishedCount, sFailedCount)
|
||||
needStyle = true
|
||||
}
|
||||
val style: NotificationCompat.InboxStyle?
|
||||
if (needStyle) {
|
||||
style = NotificationCompat.InboxStyle()
|
||||
style.setBigContentTitle(getString(R.string.stat_download_done_title))
|
||||
val stateArray = sItemStateArray
|
||||
var i = 0
|
||||
val n = stateArray.size()
|
||||
while (i < n) {
|
||||
val id = stateArray.keyAt(i)
|
||||
val fin = stateArray.valueAt(i)
|
||||
val title = sItemTitleArray[id]
|
||||
if (title == null) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
style.addLine(
|
||||
getString(
|
||||
if (fin) R.string.stat_download_done_line_succeeded else R.string.stat_download_done_line_failed,
|
||||
title
|
||||
)
|
||||
)
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
style = null
|
||||
}
|
||||
mDownloadedBuilder!!.setContentText(text)
|
||||
.setStyle(style)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setNumber(sDownloadedCount)
|
||||
mDownloadedDelay!!.show()
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
override fun onCancel(info: DownloadInfo) {
|
||||
if (mNotifyManager == null) {
|
||||
return
|
||||
}
|
||||
if (null != mDownloadingDelay) {
|
||||
mDownloadingDelay!!.cancel()
|
||||
}
|
||||
checkStopSelf()
|
||||
}
|
||||
|
||||
private fun checkStopSelf() {
|
||||
if (mDownloadManager == null || mDownloadManager!!.isIdle) {
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private class NotificationDelay(
|
||||
private var mService: Service,
|
||||
private val mNotifyManager: NotificationManagerCompat,
|
||||
private val mBuilder: NotificationCompat.Builder,
|
||||
private val mId: Int
|
||||
) : Runnable {
|
||||
private var mLastTime: Long = 0
|
||||
private var mPosted = false
|
||||
|
||||
// false for show, true for cancel
|
||||
@Ops
|
||||
private var mOps = 0
|
||||
|
||||
fun show() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_NOTIFY
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
mService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_NOTIFY
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
mLastTime = now
|
||||
}
|
||||
}
|
||||
|
||||
fun cancel() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_CANCEL
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mNotifyManager.cancel(mId)
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_CANCEL
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startForeground() {
|
||||
if (mPosted) {
|
||||
mOps = OPS_START_FOREGROUND
|
||||
} else {
|
||||
val now = SystemClock.currentThreadTimeMillis()
|
||||
if (now - mLastTime > DELAY) {
|
||||
// Wait long enough, do it now
|
||||
mService.startForeground(mId, mBuilder.build())
|
||||
} else {
|
||||
// Too quick, post delay
|
||||
mOps = OPS_START_FOREGROUND
|
||||
mPosted = true
|
||||
SimpleHandler.getInstance().postDelayed(this, DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
mPosted = false
|
||||
when (mOps) {
|
||||
OPS_NOTIFY -> {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
mService,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
mNotifyManager.notify(mId, mBuilder.build())
|
||||
}
|
||||
OPS_CANCEL -> mNotifyManager.cancel(mId)
|
||||
OPS_START_FOREGROUND -> mService.startForeground(mId, mBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef(OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class Ops
|
||||
companion object {
|
||||
private const val OPS_NOTIFY = 0
|
||||
private const val OPS_CANCEL = 1
|
||||
private const val OPS_START_FOREGROUND = 2
|
||||
private const val DELAY: Long = 1000 // 1s
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ACTION_START = "start"
|
||||
const val ACTION_START_RANGE = "start_range"
|
||||
const val ACTION_START_ALL = "start_all"
|
||||
const val ACTION_STOP = "stop"
|
||||
const val ACTION_STOP_RANGE = "stop_range"
|
||||
const val ACTION_STOP_CURRENT = "stop_current"
|
||||
const val ACTION_STOP_ALL = "stop_all"
|
||||
const val ACTION_DELETE = "delete"
|
||||
const val ACTION_DELETE_RANGE = "delete_range"
|
||||
const val ACTION_CLEAR = "clear"
|
||||
const val KEY_GALLERY_INFO = "gallery_info"
|
||||
const val KEY_LABEL = "label"
|
||||
const val KEY_GID = "gid"
|
||||
const val KEY_GID_LIST = "gid_list"
|
||||
private const val ID_DOWNLOADING = 1
|
||||
private const val ID_DOWNLOADED = 2
|
||||
private const val ID_509 = 3
|
||||
private val sItemStateArray = SparseJBArray()
|
||||
private val sItemTitleArray = SparseJLArray<String>()
|
||||
private var sFailedCount = 0
|
||||
private var sFinishedCount = 0
|
||||
private var sDownloadedCount = 0
|
||||
|
||||
@JvmStatic
|
||||
fun clear() {
|
||||
sFailedCount = 0
|
||||
sFinishedCount = 0
|
||||
sDownloadedCount = 0
|
||||
sItemStateArray.clear()
|
||||
sItemTitleArray.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
@@ -42,6 +43,7 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class CommonOperations {
|
||||
|
||||
@@ -110,8 +112,14 @@ public final class CommonOperations {
|
||||
startDownload(activity, Collections.singletonList(galleryInfo), forceDefault);
|
||||
}
|
||||
|
||||
// TODO Add context if activity and context are different style
|
||||
public static void startDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Objects.requireNonNull(EhApplication.getInstance().getTopActivity()).checkAndRequestNotificationPermission();
|
||||
}
|
||||
doStartDownload(activity, galleryInfos, forceDefault);
|
||||
}
|
||||
|
||||
private static void doStartDownload(final MainActivity activity, final List<GalleryInfo> galleryInfos, boolean forceDefault) {
|
||||
final DownloadManager dm = EhApplication.getDownloadManager(activity);
|
||||
|
||||
LongList toStart = new LongList();
|
||||
|
||||
@@ -13,126 +13,139 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
package com.hippo.ehviewer.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StyleRes;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import rikka.core.res.ResourcesKt;
|
||||
import rikka.insets.WindowInsetsHelper;
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory;
|
||||
|
||||
public abstract class EhActivity extends AppCompatActivity {
|
||||
|
||||
private static final String THEME_DEFAULT = "DEFAULT";
|
||||
private static final String THEME_BLACK = "BLACK";
|
||||
|
||||
public static boolean isBlackNightTheme() {
|
||||
return Settings.getBoolean("black_dark_theme", false);
|
||||
}
|
||||
|
||||
public static String getTheme(Context context) {
|
||||
if (isBlackNightTheme() && isNightMode(context.getResources().getConfiguration()))
|
||||
return THEME_BLACK;
|
||||
return THEME_DEFAULT;
|
||||
}
|
||||
|
||||
public static boolean isNightMode(Configuration configuration) {
|
||||
return (configuration.uiMode & Configuration.UI_MODE_NIGHT_YES) > 0;
|
||||
}
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.Theme
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.ui.SecurityActivity.Companion.isAuthenticationSupported
|
||||
import rikka.core.res.resolveColor
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
|
||||
abstract class EhActivity : AppCompatActivity() {
|
||||
@StyleRes
|
||||
public int getThemeStyleRes(Context context) {
|
||||
if (THEME_BLACK.equals(getTheme(context))) {
|
||||
return R.style.ThemeOverlay_Black;
|
||||
}
|
||||
return R.style.ThemeOverlay;
|
||||
fun getThemeStyleRes(context: Context): Int {
|
||||
return if (THEME_BLACK == getTheme(context)) {
|
||||
R.style.ThemeOverlay_Black
|
||||
} else R.style.ThemeOverlay
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
|
||||
if (getParent() == null) {
|
||||
theme.applyStyle(resid, true);
|
||||
override fun onApplyThemeResource(theme: Theme, resid: Int, first: Boolean) {
|
||||
if (parent == null) {
|
||||
theme.applyStyle(resid, true)
|
||||
} else {
|
||||
try {
|
||||
theme.setTo(getParent().getTheme());
|
||||
} catch (Exception ignored) {
|
||||
theme.setTo(parent.theme)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
theme.applyStyle(resid, false);
|
||||
theme.applyStyle(resid, false)
|
||||
}
|
||||
theme.applyStyle(getThemeStyleRes(this), true);
|
||||
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first);
|
||||
theme.applyStyle(getThemeStyleRes(this), true)
|
||||
super.onApplyThemeResource(theme, R.style.ThemeOverlay, first)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNightModeChanged(int mode) {
|
||||
getTheme().applyStyle(getThemeStyleRes(this), true);
|
||||
override fun onNightModeChanged(mode: Int) {
|
||||
theme.applyStyle(getThemeStyleRes(this), true)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
getLayoutInflater().setFactory2((new LayoutInflaterFactory(getDelegate())).addOnViewCreatedListener(WindowInsetsHelper.getLISTENER()));
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
Window window = getWindow();
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
|
||||
window.getDecorView().post(() -> {
|
||||
WindowInsets rootWindowInsets = window.getDecorView().getRootWindowInsets();
|
||||
if (rootWindowInsets != null && rootWindowInsets.getSystemWindowInsetBottom() >= Resources.getSystem().getDisplayMetrics().density * 40) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
layoutInflater.factory2 =
|
||||
LayoutInflaterFactory(delegate).addOnViewCreatedListener(
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
val window = window
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.setNavigationBarDividerColor(getColor(R.color.navigation_bar_divider));
|
||||
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
||||
}
|
||||
window.setNavigationBarColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.navigationBarColor) & 0x00ffffff | -0x20000000);
|
||||
window.navigationBarColor =
|
||||
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.setNavigationBarContrastEnforced(false);
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
} else {
|
||||
window.setNavigationBarColor(Color.TRANSPARENT);
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.setNavigationBarContrastEnforced(true);
|
||||
window.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
((EhApplication) getApplication()).registerActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
((EhApplication) getApplication()).unregisterActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (Settings.getSecurity() && SecurityActivity.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
|
||||
startActivity(new Intent(this, SecurityActivity.class));
|
||||
}
|
||||
super.onResume();
|
||||
(application as EhApplication).registerActivity(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
(application as EhApplication).unregisterActivity(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (Settings.getSecurity() && isAuthenticationSupported(this) && EhApplication.locked) {
|
||||
startActivity(Intent(this, SecurityActivity::class.java))
|
||||
}
|
||||
super.onResume()
|
||||
if (Settings.getEnabledSecurity()) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE);
|
||||
window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE
|
||||
)
|
||||
} else {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val requestPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) {
|
||||
Settings.putNotificationRequired()
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
|
||||
fun checkAndRequestNotificationPermission(){
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (Settings.getNotificationRequired())
|
||||
return
|
||||
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val THEME_DEFAULT = "DEFAULT"
|
||||
private const val THEME_BLACK = "BLACK"
|
||||
private val isBlackNightTheme: Boolean
|
||||
get() = Settings.getBoolean("black_dark_theme", false)
|
||||
|
||||
fun getTheme(context: Context): String {
|
||||
return if (isBlackNightTheme && isNightMode(context.resources.configuration)) THEME_BLACK else THEME_DEFAULT
|
||||
}
|
||||
|
||||
private fun isNightMode(configuration: Configuration): Boolean {
|
||||
return configuration.uiMode and Configuration.UI_MODE_NIGHT_YES > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user