diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ebd72d3a6..7f9fd2992 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ android:name="android.hardware.touchscreen" android:required="false" /> + diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 7ebe4885b..5d60aa981 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -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; } diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index b90e7880f..f7f698a69 100755 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -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); + } } diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java deleted file mode 100644 index ad5c091fc..000000000 --- a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java +++ /dev/null @@ -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 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 { - } - } -} diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.kt b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.kt new file mode 100644 index 000000000..52d58f032 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.kt @@ -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(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(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(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(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() + 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.java b/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.java index fa42be226..84e3bfa44 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/CommonOperations.java @@ -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 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 galleryInfos, boolean forceDefault) { final DownloadManager dm = EhApplication.getDownloadManager(activity); LongList toStart = new LongList(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/EhActivity.kt b/app/src/main/java/com/hippo/ehviewer/ui/EhActivity.kt index 7db1df032..a15b3341c 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/EhActivity.kt +++ b/app/src/main/java/com/hippo/ehviewer/ui/EhActivity.kt @@ -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 + } + } +} \ No newline at end of file