NewPipe/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java

481 lines
19 KiB
Java
Executable File

package us.shandian.giga.service;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.content.PermissionChecker;
import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadActivity;
import org.schabi.newpipe.player.helper.LockManager;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.service.DownloadManager.NetworkState;
import static org.schabi.newpipe.BuildConfig.APPLICATION_ID;
import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManagerService extends Service {
private static final String TAG = DownloadManagerService.class.getSimpleName();
public static final int MESSAGE_RUNNING = 1;
public static final int MESSAGE_PAUSED = 2;
public static final int MESSAGE_FINISHED = 3;
public static final int MESSAGE_PROGRESS = 4;
public static final int MESSAGE_ERROR = 5;
private static final int FOREGROUND_NOTIFICATION_ID = 1000;
private static final int DOWNLOADS_NOTIFICATION_ID = 1001;
private static final String EXTRA_URLS = "DownloadManagerService.extra.urls";
private static final String EXTRA_NAME = "DownloadManagerService.extra.name";
private static final String EXTRA_LOCATION = "DownloadManagerService.extra.location";
private static final String EXTRA_KIND = "DownloadManagerService.extra.kind";
private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads";
private static final String EXTRA_POSTPROCESSING_NAME = "DownloadManagerService.extra.postprocessingName";
private static final String EXTRA_POSTPROCESSING_ARGS = "DownloadManagerService.extra.postprocessingArgs";
private static final String EXTRA_SOURCE = "DownloadManagerService.extra.source";
private static final String EXTRA_NEAR_LENGTH = "DownloadManagerService.extra.nearLength";
private static final String ACTION_RESET_DOWNLOAD_COUNT = APPLICATION_ID + ".reset_download_count";
private DMBinder mBinder;
private DownloadManager mManager;
private Notification mNotification;
private Handler mHandler;
private int downloadDoneCount = 0;
private Builder downloadDoneNotification = null;
private StringBuilder downloadDoneList = null;
NotificationManager notificationManager = null;
private boolean mForeground = false;
private final ArrayList<Handler> mEchoObservers = new ArrayList<>(1);
private BroadcastReceiver mNetworkStateListener;
private SharedPreferences mPrefs = null;
private final SharedPreferences.OnSharedPreferenceChangeListener mPrefChangeListener = this::handlePreferenceChange;
private boolean wakeLockAcquired = false;
private LockManager wakeLock = null;
private int downloadFailedNotificationID = DOWNLOADS_NOTIFICATION_ID + 1;
private Bitmap icLauncher;
private Bitmap icDownloadDone;
/**
* notify media scanner on downloaded media file ...
*
* @param file the downloaded file
*/
private void notifyMediaScanner(File file) {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
}
@Override
public void onCreate() {
super.onCreate();
if (DEBUG) {
Log.d(TAG, "onCreate");
}
mBinder = new DMBinder();
mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
DownloadManagerService.this.handleMessage(msg);
}
};
mManager = new DownloadManager(this, mHandler);
Intent openDownloadListIntent = new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
openDownloadListIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
icLauncher = BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher);
Builder builder = new Builder(this, getString(R.string.notification_channel_id))
.setContentIntent(pendingIntent)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setLargeIcon(icLauncher)
.setContentTitle(getString(R.string.msg_running))
.setContentText(getString(R.string.msg_running_detail));
mNotification = builder.build();
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNetworkStateListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
handleConnectivityChange(null);
return;
}
handleConnectivityChange(intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO));
}
};
registerReceiver(mNetworkStateListener, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.registerOnSharedPreferenceChangeListener(mPrefChangeListener);
wakeLock = new LockManager(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) {
if (intent == null) {
Log.d(TAG, "Restarting");
return START_NOT_STICKY;
}
Log.d(TAG, "Starting");
}
Log.i(TAG, "Got intent: " + intent);
String action = intent.getAction();
if (action != null) {
if (action.equals(Intent.ACTION_RUN)) {
String[] urls = intent.getStringArrayExtra(EXTRA_URLS);
String name = intent.getStringExtra(EXTRA_NAME);
String location = intent.getStringExtra(EXTRA_LOCATION);
int threads = intent.getIntExtra(EXTRA_THREADS, 1);
char kind = intent.getCharExtra(EXTRA_KIND, '?');
String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME);
String[] psArgs = intent.getStringArrayExtra(EXTRA_POSTPROCESSING_ARGS);
String source = intent.getStringExtra(EXTRA_SOURCE);
long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0);
mHandler.post(() -> mManager.startMission(urls, location, name, kind, threads, source, psName, psArgs, nearLength));
} else if (downloadDoneNotification != null && action.equals(ACTION_RESET_DOWNLOAD_COUNT)) {
downloadDoneCount = 0;
downloadDoneList.setLength(0);
}
}
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (DEBUG) {
Log.d(TAG, "Destroying");
}
stopForeground(true);
if (notificationManager != null && downloadDoneNotification != null) {
downloadDoneNotification.setDeleteIntent(null);// prevent NewPipe running when is killed, cleared from recent, etc
notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build());
}
mManager.pauseAllMissions();
if (wakeLockAcquired) wakeLock.releaseWifiAndCpu();
unregisterReceiver(mNetworkStateListener);
mPrefs.unregisterOnSharedPreferenceChangeListener(mPrefChangeListener);
icDownloadDone.recycle();
icLauncher.recycle();
}
@Override
public IBinder onBind(Intent intent) {
int permissionCheck;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
if (permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (read)", Toast.LENGTH_SHORT).show();
}
}
permissionCheck = PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionCheck == PermissionChecker.PERMISSION_DENIED) {
Toast.makeText(this, "Permission denied (write)", Toast.LENGTH_SHORT).show();
}
return mBinder;
}
public void handleMessage(Message msg) {
DownloadMission mission = (DownloadMission) msg.obj;
switch (msg.what) {
case MESSAGE_FINISHED:
notifyMediaScanner(mission.getDownloadedFile());
notifyFinishedDownload(mission.name);
mManager.setFinished(mission);
updateForegroundState(mManager.runAnotherMission());
break;
case MESSAGE_RUNNING:
case MESSAGE_PROGRESS:
updateForegroundState(true);
break;
case MESSAGE_ERROR:
notifyFailedDownload(mission.name);
updateForegroundState(mManager.runAnotherMission());
break;
case MESSAGE_PAUSED:
updateForegroundState(mManager.getRunningMissionsCount() > 0);
break;
}
synchronized (mEchoObservers) {
Iterator<Handler> iterator = mEchoObservers.iterator();
while (iterator.hasNext()) {
Handler handler = iterator.next();
if (handler.getLooper().getThread().isAlive()) {
Message echo = new Message();
echo.what = msg.what;
echo.obj = msg.obj;
handler.sendMessage(echo);
} else {
iterator.remove();// ¿missing call to removeMissionEventListener()?
}
}
}
}
private void handleConnectivityChange(NetworkInfo info) {
NetworkState status;
if (info == null) {
status = NetworkState.Unavailable;
Log.i(TAG, "actual connectivity status is unavailable");
} else if (!info.isAvailable() || !info.isConnected()) {
status = NetworkState.Unavailable;
Log.i(TAG, "actual connectivity status is not available and not connected");
} else {
int type = info.getType();
if (type == ConnectivityManager.TYPE_MOBILE || type == ConnectivityManager.TYPE_MOBILE_DUN) {
status = NetworkState.MobileOperating;
} else if (type == ConnectivityManager.TYPE_WIFI) {
status = NetworkState.WifiOperating;
} else if (type == ConnectivityManager.TYPE_WIMAX ||
type == ConnectivityManager.TYPE_ETHERNET ||
type == ConnectivityManager.TYPE_BLUETOOTH) {
status = NetworkState.OtherOperating;
} else {
status = NetworkState.Unavailable;
}
Log.i(TAG, "actual connectivity status is " + status.name());
}
if (mManager == null) return;// avoid race-conditions while the service is starting
mManager.handleConnectivityChange(status);
}
private void handlePreferenceChange(SharedPreferences prefs, String key) {
if (key.equals(getString(R.string.downloads_max_retry))) {
mManager.updateMaximumAttempts(prefs.getInt(key, 3));
}
}
public void updateForegroundState(boolean state) {
if (state == mForeground) return;
if (state) {
startForeground(FOREGROUND_NOTIFICATION_ID, mNotification);
if (!wakeLockAcquired) wakeLock.acquireWifiAndCpu();
} else {
stopForeground(true);
if (wakeLockAcquired) wakeLock.releaseWifiAndCpu();
}
mForeground = state;
}
public static void startMission(Context context, String urls[], String location, String name, char kind,
int threads, String source, String psName, String[] psArgs, long nearLength) {
Intent intent = new Intent(context, DownloadManagerService.class);
intent.setAction(Intent.ACTION_RUN);
intent.putExtra(EXTRA_URLS, urls);
intent.putExtra(EXTRA_NAME, name);
intent.putExtra(EXTRA_LOCATION, location);
intent.putExtra(EXTRA_KIND, kind);
intent.putExtra(EXTRA_THREADS, threads);
intent.putExtra(EXTRA_SOURCE, source);
intent.putExtra(EXTRA_POSTPROCESSING_NAME, psName);
intent.putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs);
intent.putExtra(EXTRA_NEAR_LENGTH, nearLength);
context.startService(intent);
}
public static void checkForRunningMission(Context context, String location, String name, DMChecker check) {
Intent intent = new Intent();
intent.setClass(context, DownloadManagerService.class);
context.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName cname, IBinder service) {
try {
((DMBinder) service).getDownloadManager().checkForRunningMission(location, name, check);
} catch (Exception err) {
Log.w(TAG, "checkForRunningMission() callback is defective", err);
}
// TODO: find a efficient way to unbind the service. This destroy the service due idle, but is started again when the user start a download.
context.unbindService(this);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
public void notifyFinishedDownload(String name) {
if (notificationManager == null) {
return;
}
if (downloadDoneNotification == null) {
downloadDoneList = new StringBuilder(name.length());
icDownloadDone = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done);
downloadDoneNotification = new Builder(this, getString(R.string.notification_channel_id))
.setAutoCancel(true)
.setLargeIcon(icDownloadDone)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setDeleteIntent(PendingIntent.getService(this, (int) System.currentTimeMillis(),
new Intent(this, DownloadManagerService.class)
.setAction(ACTION_RESET_DOWNLOAD_COUNT)
, PendingIntent.FLAG_UPDATE_CURRENT))
.setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1,
new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN),
PendingIntent.FLAG_UPDATE_CURRENT));
}
if (downloadDoneCount < 1) {
downloadDoneList.append(name);
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
downloadDoneNotification.setContentTitle(getString(R.string.app_name));
} else {
downloadDoneNotification.setContentTitle(null);
}
downloadDoneNotification.setContentText(getString(R.string.download_finished));
downloadDoneNotification.setStyle(new NotificationCompat.BigTextStyle()
.setBigContentTitle(getString(R.string.download_finished))
.bigText(name)
);
} else {
downloadDoneList.append('\n');
downloadDoneList.append(name);
downloadDoneNotification.setStyle(new NotificationCompat.BigTextStyle().bigText(downloadDoneList));
downloadDoneNotification.setContentTitle(getString(R.string.download_finished_more, String.valueOf(downloadDoneCount + 1)));
downloadDoneNotification.setContentText(downloadDoneList);
}
notificationManager.notify(DOWNLOADS_NOTIFICATION_ID, downloadDoneNotification.build());
downloadDoneCount++;
}
public void notifyFailedDownload(String name) {
if (icDownloadDone == null) {
// TODO: use a proper icon for failed downloads
icDownloadDone = BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done);
}
Builder notification = new Builder(this, getString(R.string.notification_channel_id))
.setAutoCancel(true)
.setLargeIcon(icDownloadDone)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(PendingIntent.getService(this, (int) System.currentTimeMillis() + 1,
new Intent(this, DownloadActivity.class)
.setAction(Intent.ACTION_MAIN),
PendingIntent.FLAG_UPDATE_CURRENT));
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
notification.setContentTitle(getString(R.string.app_name));
notification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(getString(R.string.download_failed).concat(": ").concat(name)));
} else {
notification.setContentTitle(getString(R.string.download_failed));
notification.setContentText(name);
notification.setStyle(new NotificationCompat.BigTextStyle()
.bigText(name));
}
notificationManager.notify(downloadFailedNotificationID++, notification.build());
}
private void manageObservers(Handler handler, boolean add) {
synchronized (mEchoObservers) {
if (add) {
mEchoObservers.add(handler);
} else {
mEchoObservers.remove(handler);
}
}
}
// Wrapper of DownloadManager
public class DMBinder extends Binder {
public DownloadManager getDownloadManager() {
return mManager;
}
public void addMissionEventListener(Handler handler) {
manageObservers(handler, true);
}
public void removeMissionEventListener(Handler handler) {
manageObservers(handler, false);
}
public void resetFinishedDownloadCount() {
if (notificationManager == null || downloadDoneNotification == null) return;
notificationManager.cancel(DOWNLOADS_NOTIFICATION_ID);
downloadDoneList.setLength(0);
downloadDoneCount = 0;
}
}
public interface DMChecker {
void callback(boolean listed, boolean finished);
}
}