Last active
May 7, 2017 14:20
-
-
Save tcw165/5fc8cc306f5b96146e30f67b9cc1b1aa to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class DlibModelHelper { | |
private static final String BASE_URL = "http://dlib.net/files/"; | |
private static final String FACE68_ZIP_FILE = "shape_predictor_68_face_landmarks.dat.bz2"; | |
private static final String FACE68_FILE = "shape_predictor_68_face_landmarks.dat"; | |
private static final String PREF_KEY_FACE68_ZIP = FACE68_ZIP_FILE; | |
private static DlibModelHelper sSingleton = null; | |
public static DlibModelHelper getService() { | |
if (sSingleton == null) { | |
synchronized (DlibModelHelper.class) { | |
if (sSingleton == null) { | |
sSingleton = new DlibModelHelper(); | |
} | |
} | |
} | |
return sSingleton; | |
} | |
public Observable<File> downloadFace68Model(final Context context, | |
final String dirName) { | |
// Get DownloadManager | |
final DownloadManager downloadManager = (DownloadManager) | |
context.getSystemService(Context.DOWNLOAD_SERVICE); | |
// Get the task ID from shared-preference. | |
long downloadId = PrefUtil.getLong(context, PREF_KEY_FACE68_ZIP); | |
// Create a subject for later emitting the download status. | |
final PublishSubject<File> subject = PublishSubject.create(); | |
if (downloadId == -1) { | |
// Create a new download request. | |
downloadId = downloadManager.enqueue(getFace68Request(dirName)); | |
// Set the task in the share-preference. | |
Log.d("xyz", "Set new downloadId=" + downloadId); | |
PrefUtil.setLong(context, PREF_KEY_FACE68_ZIP, downloadId); | |
} | |
// Register a new download broadcast receiver. | |
final DownloadStatusReceiver receiver = new DownloadStatusReceiver(downloadId, subject); | |
IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); | |
context.registerReceiver(receiver, intentFilter); | |
Log.d("xyz", "Register the download broadcast receiver."); | |
// Create a cold observable subscribing to the download subject. | |
final Observable<File> ob = subject | |
// Unregister the broadcast receiver once the stream chain is | |
// disposed. | |
.doOnDispose(new Action() { | |
@Override | |
public void run() throws Exception { | |
// Unregister the download broadcast receiver. | |
context.unregisterReceiver(receiver); | |
Log.d("xyz", "Unregister the download broadcast receiver."); | |
} | |
}) | |
.doOnError(new Consumer<Throwable>() { | |
@Override | |
public void accept(Throwable throwable) | |
throws Exception { | |
// Set the task in the share-preference. | |
Log.d("xyz", "Unset download task ID."); | |
PrefUtil.setLong(context, PREF_KEY_FACE68_ZIP, -1); | |
} | |
}) | |
// Unpack the bz2 file. | |
// TODO: Run the unpacking in a IntentService so that the process | |
// TODO: continues when app enters background. | |
.observeOn(Schedulers.io()) | |
.map(new Function<File, File>() { | |
@Override | |
public File apply(File downloadFile) | |
throws Exception { | |
final File unpackFile = new File(downloadFile.getParent(), FACE68_FILE); | |
if (!unpackFile.exists()) { | |
Log.d("xyz", "Unpacking the archived model..."); | |
final BZip2CompressorInputStream bzIs = | |
new BZip2CompressorInputStream( | |
new BufferedInputStream( | |
new FileInputStream(downloadFile))); | |
FileUtil.copy(bzIs, new FileOutputStream(unpackFile)); | |
Log.d("xyz", "Unpacking the archived model... done"); | |
// Register the unpacked file to DownloadManager. | |
downloadManager.addCompletedDownload( | |
unpackFile.getName(), unpackFile.getName(), true, "*/*", | |
unpackFile.getAbsolutePath(), unpackFile.length(), true); | |
} | |
return unpackFile; | |
} | |
}); | |
if (downloadId != -1) { | |
// Check the download task if the task ID is present: | |
// Case 1. The download is completed already. Calling this function | |
// make the subject emit the result. | |
// Case 2. The download is yet completed. However, I register a | |
// broadcast receiver. Calling this function won't harm. | |
checkDownloadTask(downloadManager, downloadId, subject); | |
} | |
return ob; | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Protected / Private Methods //////////////////////////////////////////// | |
private DlibModelHelper() { | |
// DO NOTHING. | |
} | |
private static void checkDownloadTask(final DownloadManager downloadManager, | |
final long taskId, | |
final Subject<File> subject) { | |
if (downloadManager == null || subject == null) return; | |
Observable | |
.fromCallable(new Callable<Boolean>() { | |
@Override | |
public Boolean call() throws Exception { | |
// The task cursor. | |
final Cursor cursor = downloadManager.query( | |
new DownloadManager.Query().setFilterById(taskId)); | |
if (cursor != null && cursor.moveToFirst()) { | |
try { | |
final int statusCol = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS); | |
final int uriCol = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI); | |
if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(statusCol)) { | |
final File file = new File(Uri.parse(cursor.getString(uriCol)) | |
.getPath()); | |
Log.d("xyz", "" + file + " is downloaded."); | |
subject.onNext(file); | |
subject.onComplete(); | |
} | |
} catch (Throwable err) { | |
// Remove the task from DownloadManager. | |
downloadManager.remove(taskId); | |
subject.onError(err); | |
} finally { | |
cursor.close(); | |
} | |
} else { | |
// Remove the task from DownloadManager. | |
downloadManager.remove(taskId); | |
subject.onError(new RuntimeException("Empty cursor.")); | |
} | |
return true; | |
} | |
}) | |
.subscribeOn(Schedulers.io()) | |
// Capture any error where the callable block doesn't catch. | |
.doOnError(new Consumer<Throwable>() { | |
@Override | |
public void accept(Throwable err) | |
throws Exception { | |
subject.onError(err); | |
} | |
}) | |
.subscribe(); | |
} | |
/** | |
* Get {@link DownloadManager.Request} for getting face68 model. | |
*/ | |
private DownloadManager.Request getFace68Request(final String dirName) { | |
return new DownloadManager.Request(Uri.parse(BASE_URL + FACE68_ZIP_FILE)) | |
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, | |
dirName + File.separator + FACE68_ZIP_FILE) | |
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | | |
DownloadManager.Request.NETWORK_WIFI) | |
.setTitle("Downloading " + FACE68_ZIP_FILE) | |
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) | |
.setVisibleInDownloadsUi(true); | |
} | |
/////////////////////////////////////////////////////////////////////////// | |
// Clazz ////////////////////////////////////////////////////////////////// | |
// TODO: Refactor this in case of that too many services are registered. | |
private static class DownloadStatusReceiver extends BroadcastReceiver { | |
private final long mTaskId; | |
private final Subject<File> mSubject; | |
public DownloadStatusReceiver(final long taskId, | |
final Subject<File> subject) { | |
mTaskId = taskId; | |
mSubject = subject; | |
} | |
@Override | |
public void onReceive(Context context, | |
Intent intent) { | |
final DownloadManager downloadManager = (DownloadManager) | |
context.getSystemService(Context.DOWNLOAD_SERVICE); | |
// The task ID. | |
final long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L); | |
if (mTaskId != id) return; | |
DlibModelHelper.checkDownloadTask(downloadManager, id, mSubject); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment