Init
This commit is contained in:
commit
fd5d07b2f2
658 files changed
+57526
No files matched your search
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.google.analytics.tracking.android.EasyTracker;
|
||||
import com.google.analytics.tracking.android.MapBuilder;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Analytics {
|
||||
|
||||
private static final String KEY_GALLERY = "gallery";
|
||||
private static final String KEY_OPEN = "open";
|
||||
private static final String KEY_READ = "read";
|
||||
private static final String KEY_DOWNLOAD = "download";
|
||||
private static final String KEY_ADD_TO_FAVORITE = "add_to_favorite";
|
||||
|
||||
private static void doGallery(Context context, int gid, String token, String method) {
|
||||
if (!Config.getAllowAnalyics())
|
||||
return;
|
||||
|
||||
try {
|
||||
JSONObject js = new JSONObject();
|
||||
js.put("gid", gid).put("token", token);
|
||||
EasyTracker easyTracker = EasyTracker.getInstance(context);
|
||||
easyTracker.send(MapBuilder.createEvent(KEY_GALLERY, method, js.toString(), null).build());
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
public static void openGallery(Context context, int gid, String token) {
|
||||
doGallery(context, gid, token, KEY_OPEN);
|
||||
}
|
||||
|
||||
public static void readGallery(Context context, int gid, String token) {
|
||||
doGallery(context, gid, token, KEY_READ);
|
||||
}
|
||||
|
||||
public static void downloadGallery(Context context, int gid, String token) {
|
||||
doGallery(context, gid, token, KEY_DOWNLOAD);
|
||||
}
|
||||
|
||||
public static void addToFavoriteGallery(Context context, int gid, String token) {
|
||||
doGallery(context, gid, token, KEY_ADD_TO_FAVORITE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.Typeface;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.data.Data;
|
||||
import com.hippo.ehviewer.ehclient.EhClient;
|
||||
import com.hippo.ehviewer.ehclient.EhInfo;
|
||||
import com.hippo.ehviewer.ehclient.ExDownloaderManager;
|
||||
import com.hippo.ehviewer.network.HttpHelper;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Crash;
|
||||
import com.hippo.ehviewer.util.EhUtils;
|
||||
import com.hippo.ehviewer.util.Favorite;
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
import com.hippo.ehviewer.widget.MaterialToast;
|
||||
import com.hippo.ehviewer.widget.SlidingDrawerLayout;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
public class AppContext extends Application implements UncaughtExceptionHandler {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = AppContext.class.getSimpleName();
|
||||
|
||||
private Thread.UncaughtExceptionHandler mDefaultHandler;
|
||||
|
||||
private Typeface mFaceTypeface;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// Do catch error prepare
|
||||
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
Thread.setDefaultUncaughtExceptionHandler(this);
|
||||
|
||||
Context mContextThemeWrapper = new ContextThemeWrapper(this, R.style.AppTheme);
|
||||
ViewUtils.initDialogViewInflater(this);
|
||||
|
||||
// Init everything
|
||||
Ui.init(mContextThemeWrapper);
|
||||
Config.init(mContextThemeWrapper);
|
||||
Log.init(mContextThemeWrapper);
|
||||
Crash.init(mContextThemeWrapper);
|
||||
EhClient.createInstance(mContextThemeWrapper);
|
||||
Favorite.init(mContextThemeWrapper);
|
||||
Data.createInstance(mContextThemeWrapper);
|
||||
ExDownloaderManager.createInstance(mContextThemeWrapper);
|
||||
MaterialToast.setContext(mContextThemeWrapper);
|
||||
|
||||
mFaceTypeface = Typeface.createFromAsset(getAssets(),"fonts/face.ttf");
|
||||
|
||||
// Set drawer margin
|
||||
SlidingDrawerLayout.setDefaultMinDrawerMargin(Ui.ACTION_BAR_HEIGHT * 2);
|
||||
|
||||
// Add .nomedia or delete it
|
||||
File nomedia = new File(Config.getDownloadPath(), ".nomedia");
|
||||
if (Config.getMediaScan()) {
|
||||
nomedia.delete();
|
||||
} else {
|
||||
try {
|
||||
new FileOutputStream(nomedia).close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
// Update proxy urls
|
||||
HttpHelper.updateProxyUrls(mContextThemeWrapper);
|
||||
|
||||
String keyVersionCode = "version_code";
|
||||
int oldVersion = Config.getInt(keyVersionCode, Integer.MAX_VALUE);
|
||||
// Fix <=22 login error
|
||||
if (oldVersion <= 22)
|
||||
EhInfo.getInstance(mContextThemeWrapper).logout();
|
||||
|
||||
// Fix <=25 Update
|
||||
if (oldVersion <= 25) {
|
||||
File downloadDir = new File(Config.getDownloadPath());
|
||||
String cachePath = getExternalCacheDir() != null
|
||||
? getExternalCacheDir().getPath()
|
||||
: getCacheDir().getPath();
|
||||
File oldEdInfoDir = new File(cachePath, "ExDownloaderManager");
|
||||
String[] list = downloadDir.list();
|
||||
if (list != null && list.length != 0) {
|
||||
for (String str : list) {
|
||||
int index = str.indexOf('-');
|
||||
if (index == -1)
|
||||
continue;
|
||||
String gid = str.substring(0, index);
|
||||
File gDir = new File(downloadDir, str);
|
||||
// Move download info file
|
||||
File oldInfoFile = new File(oldEdInfoDir, gid);
|
||||
File newInfoFile = new File(gDir, EhUtils.EH_DOWNLOAD_FILENAME);
|
||||
if (!oldInfoFile.exists())
|
||||
continue;
|
||||
try {
|
||||
FileOutputStream fos = new FileOutputStream(newInfoFile);
|
||||
// Add last page
|
||||
byte[] bs = {'0', '0', '0', '0', '0', '0', '0', '0', '\n'};
|
||||
fos.write(bs);
|
||||
Utils.copy(new FileInputStream(oldInfoFile), fos);
|
||||
} catch (Throwable e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete old exdownload info dir
|
||||
Utils.deleteDirInThread(oldEdInfoDir);
|
||||
}
|
||||
|
||||
// Update version code
|
||||
try {
|
||||
PackageInfo pi= getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
Config.setInt(keyVersionCode, pi.versionCode);
|
||||
} catch (NameNotFoundException e) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
ImageCache.getInstance(this).evictAll();
|
||||
super.onLowMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable ex) {
|
||||
if (!handleException(ex) && mDefaultHandler != null) {
|
||||
mDefaultHandler.uncaughtException(thread, ex);
|
||||
}
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private boolean handleException(Throwable ex) {
|
||||
if (ex == null)
|
||||
return false;
|
||||
Crash.saveCrashInfo2File(ex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Typeface getFaceTypeface() {
|
||||
return mFaceTypeface;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import com.hippo.ehviewer.network.HttpHelper;
|
||||
import com.hippo.ehviewer.util.Constants;
|
||||
|
||||
public class AppHandler extends Handler {
|
||||
|
||||
public static final int HTTP_HELPER_TAG = 0x1;
|
||||
|
||||
private static Handler sInstance;
|
||||
|
||||
public AppHandler(Looper mainLooper) {
|
||||
super(mainLooper);
|
||||
}
|
||||
|
||||
public final static Handler getInstance() {
|
||||
if (sInstance == null)
|
||||
sInstance = new AppHandler(Looper.getMainLooper());
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch(msg.what) {
|
||||
|
||||
case HTTP_HELPER_TAG:
|
||||
HttpHelper.Package p = (HttpHelper.Package)msg.obj;
|
||||
HttpHelper.OnRespondListener listener = p.listener;
|
||||
Object obj = p.obj;
|
||||
if (msg.arg1 == Constants.TRUE) {
|
||||
listener.onSuccess(obj);
|
||||
} else {
|
||||
listener.onFailure((String)obj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.hippo.ehviewer.cache.ImageCache;
|
||||
import com.hippo.ehviewer.network.HttpHelper;
|
||||
import com.hippo.ehviewer.util.BgThread;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
public class ImageLoader {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ImageLoader.class.getSimpleName();
|
||||
|
||||
public static final int STATE_NONE = 0;
|
||||
public static final int STATE_FROM_MEMORY = 1;
|
||||
public static final int STATE_FROM_DISK = 2;
|
||||
public static final int STATE_FROM_NETWORK = 3;
|
||||
|
||||
private static ImageLoader sInstance;
|
||||
|
||||
class LoadTask {
|
||||
public String url;
|
||||
public String key;
|
||||
public OnGetImageListener listener;
|
||||
public Bitmap bitmap;
|
||||
public int state;
|
||||
|
||||
public LoadTask(String url, String key, OnGetImageListener listener) {
|
||||
this.url = url;
|
||||
this.key = key;
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final Stack<LoadTask> mLoadTasks;
|
||||
private final ImageCache mImageCache;
|
||||
private final ImageDownloader mImageDownloader;
|
||||
|
||||
private ImageLoader(Context context) {
|
||||
mLoadTasks = new Stack<LoadTask>();
|
||||
mImageDownloader = new ImageDownloader();
|
||||
|
||||
mContext = context;
|
||||
mImageCache = ImageCache.getInstance(mContext);
|
||||
|
||||
new BgThread(new LoadFromCacheTask()).start();
|
||||
}
|
||||
|
||||
public final static ImageLoader getInstance(final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new ImageLoader(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void add(String url, String key, OnGetImageListener listener) {
|
||||
synchronized (mLoadTasks) {
|
||||
mLoadTasks.push(new LoadTask(url, key, listener));
|
||||
mLoadTasks.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadImageCallback implements Runnable {
|
||||
|
||||
private final LoadTask mLoadTask;
|
||||
|
||||
private LoadImageCallback(LoadTask task) {
|
||||
mLoadTask = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
mLoadTask.listener.onGetImage(mLoadTask.key, mLoadTask.bitmap, mLoadTask.state);
|
||||
}
|
||||
}
|
||||
|
||||
private class LoadFromCacheTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
LoadTask loadTask;
|
||||
while (true) {
|
||||
synchronized (mLoadTasks) {
|
||||
if (mLoadTasks.isEmpty()) {
|
||||
try {
|
||||
mLoadTasks.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
continue;
|
||||
}
|
||||
loadTask = mLoadTasks.pop();
|
||||
}
|
||||
|
||||
int[] state = new int[1];
|
||||
String key = loadTask.key;
|
||||
loadTask.bitmap = mImageCache.getCachedBitmap(key, state);
|
||||
loadTask.state = state[0];
|
||||
|
||||
if (loadTask.bitmap != null)
|
||||
AppHandler.getInstance().post(new LoadImageCallback(loadTask));
|
||||
else
|
||||
mImageDownloader.add(loadTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ImageDownloader {
|
||||
private static final int MAX_DOWNLOAD_THREADS = 5;
|
||||
private final Stack<LoadTask> mDownloadTasks;
|
||||
private int mWorkingThreadNum = 0;
|
||||
|
||||
public ImageDownloader() {
|
||||
mDownloadTasks = new Stack<LoadTask>();
|
||||
}
|
||||
|
||||
public void add(LoadTask loadTask) {
|
||||
synchronized (mDownloadTasks) {
|
||||
mDownloadTasks.push(loadTask);
|
||||
if (mWorkingThreadNum < MAX_DOWNLOAD_THREADS) {
|
||||
new BgThread(new DownloadImageTask()).start();
|
||||
mWorkingThreadNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadImageTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
LoadTask loadTask;
|
||||
HttpHelper httpHelper = new HttpHelper(mContext);
|
||||
while (true) {
|
||||
synchronized (mDownloadTasks) {
|
||||
if (mDownloadTasks.isEmpty()) {
|
||||
loadTask = null;
|
||||
mWorkingThreadNum--;
|
||||
break;
|
||||
}
|
||||
loadTask = mDownloadTasks.pop();
|
||||
}
|
||||
|
||||
// TODO use proxy to get image
|
||||
loadTask.bitmap = httpHelper.getImage(loadTask.url);
|
||||
if (loadTask.bitmap != null) {
|
||||
mImageCache.addBitmapToCache(loadTask.key, loadTask.bitmap);
|
||||
loadTask.state = STATE_FROM_NETWORK;
|
||||
} else {
|
||||
loadTask.state = STATE_NONE;
|
||||
}
|
||||
|
||||
AppHandler.getInstance().post(new LoadImageCallback(loadTask));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnGetImageListener {
|
||||
/**
|
||||
* bmp is null for fail
|
||||
* @param key
|
||||
* @param bmp
|
||||
*/
|
||||
void onGetImage(String key, Bitmap bmp, int state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
public class NotificationId {
|
||||
public static final int UPDATE_ID = -1;
|
||||
public static final int DOWNLOAD_ID = -2;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.content.SearchRecentSuggestionsProvider;
|
||||
|
||||
public class SimpleSuggestionProvider extends SearchRecentSuggestionsProvider {
|
||||
public final static String AUTHORITY = "com.hippo.ehviewer.SimpleSuggestionProvider";
|
||||
public final static int MODE = DATABASE_MODE_QUERIES;
|
||||
|
||||
public SimpleSuggestionProvider() {
|
||||
setupSuggestions(AUTHORITY, MODE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.app.MaterialAlertDialog;
|
||||
import com.hippo.ehviewer.network.Network;
|
||||
import com.hippo.ehviewer.ui.AbsActivity;
|
||||
import com.hippo.ehviewer.ui.GalleryListActivity;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Crash;
|
||||
import com.hippo.ehviewer.util.Secret;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
import com.larvalabs.svgandroid.SVGBuilder;
|
||||
|
||||
public class StartActivity extends AbsActivity {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = StartActivity.class.getSimpleName();
|
||||
|
||||
private static final int CHECK_WARING = 0;
|
||||
private static final int CHECK_ANALYTICS = 1;
|
||||
private static final int CHECK_NETWORK = 2;
|
||||
private static final int CHECK_CRASH = 3;
|
||||
|
||||
private static final String KEY_ALLOWED = "allowed";
|
||||
private static final String KEY_SET_ANALYTICS = "set_analyics";
|
||||
|
||||
private String lastCrash;
|
||||
private boolean isAnimationOver = false;
|
||||
private boolean isCheckOver = false;
|
||||
|
||||
private MaterialAlertDialog createWarningDialog() {
|
||||
return new MaterialAlertDialog.Builder(this).setCancelable(false)
|
||||
.setTitle(R.string.dailog_waring_title)
|
||||
.setMessage(R.string.dailog_waring_plain)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.setButtonListener(new MaterialAlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which) {
|
||||
switch (which) {
|
||||
case MaterialAlertDialog.POSITIVE:
|
||||
Config.setBoolean(KEY_ALLOWED, true);
|
||||
check(CHECK_ANALYTICS);
|
||||
break;
|
||||
case MaterialAlertDialog.NEGATIVE:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
private AlertDialog createAllowAnalyicsDialog() {
|
||||
return new MaterialAlertDialog.Builder(this).setCancelable(false)
|
||||
.setTitle(R.string.dailog_analyics_title)
|
||||
.setMessageAutoLink(Linkify.WEB_URLS)
|
||||
.setMessage(R.string.dailog_analyics_plain)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.setButtonListener(new MaterialAlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which) {
|
||||
switch (which) {
|
||||
case MaterialAlertDialog.POSITIVE:
|
||||
Config.setAllowAnalyics(true);
|
||||
check(CHECK_NETWORK);
|
||||
break;
|
||||
case MaterialAlertDialog.NEGATIVE:
|
||||
Config.setAllowAnalyics(false);
|
||||
check(CHECK_NETWORK);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
private AlertDialog createNetworkDialog(int state) {
|
||||
int mesgId;
|
||||
switch (state) {
|
||||
case Network.NETWORK_STATE_MOBILE:
|
||||
mesgId = R.string.dailog_network_mobile_title;
|
||||
break;
|
||||
case Network.NETWORK_STATE_NONE:
|
||||
default:
|
||||
mesgId = R.string.dailog_network_none_title;
|
||||
break;
|
||||
}
|
||||
|
||||
return new MaterialAlertDialog.Builder(this).setCancelable(false)
|
||||
.setTitle(R.string.warning).setMessage(mesgId)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setNegativeButton(android.R.string.cancel)
|
||||
.setButtonListener(new MaterialAlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which) {
|
||||
switch (which) {
|
||||
case MaterialAlertDialog.POSITIVE:
|
||||
check(CHECK_CRASH);
|
||||
break;
|
||||
case MaterialAlertDialog.NEGATIVE:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
private AlertDialog createSendCrashDialog() {
|
||||
View view = ViewUtils.inflateDialogView(R.layout.send_crash, false);
|
||||
final EditText et = (EditText) view.findViewById(R.id.description_of_user);
|
||||
return new MaterialAlertDialog.Builder(this).setCancelable(false)
|
||||
.setTitle(R.string.dialog_send_crash_title)
|
||||
.setView(view, true)
|
||||
.setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE)
|
||||
.setButtonListener(new MaterialAlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which) {
|
||||
switch (which) {
|
||||
case MaterialAlertDialog.POSITIVE:
|
||||
// A wait dialog
|
||||
new MaterialAlertDialog.Builder(StartActivity.this)
|
||||
.setCancelable(false)
|
||||
.setTitle(R.string.wait)
|
||||
.setMessage(R.string.dialog_wait_send_crash_msg)
|
||||
.setPositiveButton(android.R.string.ok)
|
||||
.setButtonListener(
|
||||
new MaterialAlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which) {
|
||||
checkOver();
|
||||
return true;
|
||||
}
|
||||
}).show();
|
||||
Intent i = new Intent(Intent.ACTION_SENDTO);
|
||||
i.setData(Uri.parse("mailto:ehviewersu@gmail.com"));
|
||||
i.putExtra(Intent.EXTRA_SUBJECT, "I found a bug in EhViewer !");
|
||||
i.putExtra(Intent.EXTRA_TEXT, lastCrash + "======== Description ========\n" + et.getText().toString());
|
||||
startActivity(i);
|
||||
lastCrash = null;
|
||||
break;
|
||||
case MaterialAlertDialog.NEGATIVE:
|
||||
lastCrash = null;
|
||||
checkOver();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}).create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
// Disable animate
|
||||
overridePendingTransition(0, 0);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.start_activity);
|
||||
|
||||
TextView text = (TextView) findViewById(R.id.text);
|
||||
text.setTypeface(Typeface.createFromAsset(getAssets(),"fonts/GloriaHallelujah.ttf"));
|
||||
ImageView image = (ImageView) findViewById(R.id.image);
|
||||
image.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
image.setImageDrawable(new SVGBuilder().readFromResource(getResources(), R.raw.sad_pandroid).build().getDrawable());
|
||||
|
||||
// Update secret image here
|
||||
int state = Network.getNetworkState(this);
|
||||
if (state == Network.NETWORK_STATE_WIFI) {
|
||||
Secret.updateSecretImage(this);
|
||||
}
|
||||
|
||||
AppHandler.getInstance().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isCheckOver)
|
||||
redirectTo();
|
||||
isAnimationOver = true;
|
||||
}
|
||||
}, 3000);
|
||||
check(CHECK_WARING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Order is 1. check waring 2. check analyics 3. check network 4. check
|
||||
* crash 5. check external storage
|
||||
*
|
||||
* @param order
|
||||
*/
|
||||
private void check(int order) {
|
||||
switch (order) {
|
||||
case CHECK_WARING:
|
||||
if (!Config.getBoolean(KEY_ALLOWED, false)) {
|
||||
createWarningDialog().show();
|
||||
return;
|
||||
}
|
||||
case CHECK_ANALYTICS:
|
||||
if (!Config.getBoolean(KEY_SET_ANALYTICS, false)) {
|
||||
Config.setBoolean(KEY_SET_ANALYTICS, true);
|
||||
createAllowAnalyicsDialog().show();
|
||||
return;
|
||||
}
|
||||
case CHECK_NETWORK:
|
||||
int state = Network.getNetworkState(this);
|
||||
if (state != Network.NETWORK_STATE_WIFI) {
|
||||
createNetworkDialog(state).show();
|
||||
return;
|
||||
}
|
||||
case CHECK_CRASH:
|
||||
if ((lastCrash = Crash.getLastCrash()) != null) {
|
||||
createSendCrashDialog().show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkOver();
|
||||
}
|
||||
|
||||
private void checkOver() {
|
||||
if (isAnimationOver)
|
||||
redirectTo();
|
||||
isCheckOver = true;
|
||||
}
|
||||
|
||||
private void redirectTo() {
|
||||
Intent intent = new Intent(this, GalleryListActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.hippo.ehviewer.ehclient.EhClient;
|
||||
import com.hippo.ehviewer.network.HttpHelper;
|
||||
import com.hippo.ehviewer.util.BgThread;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
// TODO return onFailure when downloading update or check update
|
||||
// TODO add disable to achieve
|
||||
|
||||
public class UpdateHelper implements Runnable {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "UpdateHelper";
|
||||
|
||||
private static final String HEADER = "Ehviewer-";
|
||||
private static final String FOOTER = ".apk";
|
||||
|
||||
|
||||
private final AppContext mAppContext;
|
||||
private OnCheckUpdateListener mListener;
|
||||
|
||||
private static boolean mEnabled = true;
|
||||
|
||||
public interface OnCheckUpdateListener {
|
||||
public void onSuccess(String version, long size, String url, String fileName, String info);
|
||||
public void onNoUpdate();
|
||||
public void onFailure(String eMsg);
|
||||
}
|
||||
|
||||
public UpdateHelper(AppContext appContext) {
|
||||
mAppContext = appContext;
|
||||
}
|
||||
|
||||
public UpdateHelper SetOnCheckUpdateListener(OnCheckUpdateListener listener) {
|
||||
this.mListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void autoCheckUpdate() {
|
||||
if (Config.getUpdateDate() < Utils.getDate())
|
||||
checkUpdate();
|
||||
}
|
||||
|
||||
public static void setEnabled(boolean enabled) {
|
||||
mEnabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
class CheckUpdatePackage {
|
||||
public OnCheckUpdateListener listener;
|
||||
public String body;
|
||||
public String emesg;
|
||||
}
|
||||
|
||||
private void send(CheckUpdatePackage _package) {
|
||||
AppHandler.getInstance().post(new Respond(_package));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CheckUpdatePackage _package = new CheckUpdatePackage();
|
||||
_package.listener = mListener;
|
||||
final HttpHelper hp = new HttpHelper(mAppContext);
|
||||
if (!mEnabled) {
|
||||
_package.body = "working";
|
||||
send(_package);
|
||||
return;
|
||||
}
|
||||
|
||||
mEnabled = false;
|
||||
|
||||
PackageManager pm = mAppContext.getPackageManager();
|
||||
PackageInfo pi = null;
|
||||
try {
|
||||
pi = pm.getPackageInfo(mAppContext.getPackageName(), PackageManager.GET_ACTIVITIES);
|
||||
|
||||
JSONObject jo = new JSONObject();
|
||||
jo.put("method", "update");
|
||||
|
||||
JSONObject detailJO = new JSONObject();
|
||||
detailJO.put("version", pi.versionName);
|
||||
detailJO.put("server", Config.getUpdateServer());
|
||||
|
||||
jo.put("detail", detailJO);
|
||||
|
||||
_package.body = hp.postJson(EhClient.API_EHVIEWER, jo);
|
||||
_package.emesg = hp.getEMsg();
|
||||
send(_package);
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
_package.body = "NameNotFound";
|
||||
send(_package);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
_package.body = "JSONException";
|
||||
send(_package);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkUpdate() {
|
||||
new BgThread(this).start();
|
||||
}
|
||||
|
||||
private class Respond implements Runnable {
|
||||
|
||||
CheckUpdatePackage mPackage;
|
||||
|
||||
public Respond(CheckUpdatePackage _package) {
|
||||
mPackage = _package;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
OnCheckUpdateListener listener = mPackage.listener;
|
||||
String body = mPackage.body;
|
||||
if (body != null) {
|
||||
Config.setUpdateDate();
|
||||
|
||||
if (listener == null)
|
||||
return;
|
||||
|
||||
if (body.equals("working")) {
|
||||
listener.onFailure("正在检查更新或者下载更新"); // TODO
|
||||
} else if (body.equals("NameNotFound")) {
|
||||
listener.onFailure("NameNotFound"); // TODO
|
||||
} else if (body.equals("JSONException")) {
|
||||
listener.onFailure("JSONException"); // TODO
|
||||
} else {
|
||||
try {
|
||||
JSONObject jo = new JSONObject(body);
|
||||
JSONObject updateJO = jo.getJSONObject("update");
|
||||
|
||||
if (updateJO.has("error")) {
|
||||
listener.onFailure(updateJO.getString("error"));
|
||||
} else {
|
||||
String version = updateJO.getString("version");
|
||||
if (version.equals("none")) {
|
||||
listener.onNoUpdate();
|
||||
} else {
|
||||
long size = updateJO.getLong("size");
|
||||
String url = updateJO.getString("url");
|
||||
String info = updateJO.getString("info");
|
||||
|
||||
listener.onSuccess(version, size, url, HEADER + version + FOOTER, info);
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
listener.onFailure("JSONException");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UpdateListener implements HttpHelper.OnDownloadListener {
|
||||
private final Context mContext;
|
||||
private final String mFileName;
|
||||
|
||||
private final NotificationManager mNotifyManager;
|
||||
|
||||
public UpdateListener(Context context, String fileName) {
|
||||
mContext = context;
|
||||
mFileName = fileName;
|
||||
mNotifyManager = (NotificationManager)
|
||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStartConnect() {
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(mContext.getString(R.string.start_update))
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true)
|
||||
.setTicker(mContext.getString(R.string.start_update));
|
||||
mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStartDownload(int totalSize) {
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(mContext.getString(R.string.download_update))
|
||||
.setContentText(String.format("%.2f / %.2f KB", 0/1024.0f, totalSize/1024.0f))
|
||||
.setProgress(totalSize, 0, false)
|
||||
.setOngoing(true);
|
||||
mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStatusUpdate(
|
||||
int downloadSize, int totalSize) {
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(mContext.getString(R.string.download_update))
|
||||
.setContentText(String.format("%.2f / %.2f KB", downloadSize/1024.0f, totalSize/1024.0f))
|
||||
.setProgress(totalSize, downloadSize, false)
|
||||
.setOngoing(true);
|
||||
mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadOver(int status, String eMsg) {
|
||||
if (status == HttpHelper.DOWNLOAD_OK_CODE) {
|
||||
mNotifyManager.cancel(NotificationId.UPDATE_ID);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(Uri.fromFile(new File(Config.getDownloadPath(), mFileName)),
|
||||
"application/vnd.android.package-archive");
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(intent);
|
||||
} else {
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(mContext)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
.setContentTitle(mContext.getString(R.string.update_failed))
|
||||
.setContentText(eMsg)
|
||||
.setOngoing(false);
|
||||
mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build());
|
||||
}
|
||||
setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.app;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
import com.hippo.ehviewer.widget.FileExplorerView;
|
||||
|
||||
public class DirSelectDialog extends MaterialAlertDialog {
|
||||
|
||||
private final FileExplorerView mFileExplorerView;
|
||||
|
||||
protected DirSelectDialog(Builder builder, FileExplorerView fileExplorerView) {
|
||||
super(builder);
|
||||
|
||||
mFileExplorerView = fileExplorerView;
|
||||
}
|
||||
|
||||
public static DirSelectDialog create(Builder builder, String path) {
|
||||
View view = ViewUtils.inflateDialogView(R.layout.dir_selection, builder.mDarkTheme);
|
||||
final FileExplorerView fileExplorerView = (FileExplorerView) view.findViewById(R.id.file_list);
|
||||
final TextView warningTextView = (TextView) view.findViewById(R.id.warning);
|
||||
final TextView pathTextView = (TextView) view.findViewById(R.id.path);
|
||||
|
||||
if (path != null)
|
||||
fileExplorerView.setPath(path);
|
||||
fileExplorerView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
fileExplorerView.onItemClick(parent, view, position, id);
|
||||
pathTextView.setText(fileExplorerView.getCurPath());
|
||||
if (fileExplorerView.canWrite())
|
||||
warningTextView.setVisibility(View.GONE);
|
||||
else
|
||||
warningTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
pathTextView.setText(fileExplorerView.getCurPath());
|
||||
if (fileExplorerView.canWrite())
|
||||
warningTextView.setVisibility(View.GONE);
|
||||
else
|
||||
warningTextView.setVisibility(View.VISIBLE);
|
||||
|
||||
builder.setView(view, false,
|
||||
new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, Ui.dp2pix(360)));
|
||||
|
||||
return new DirSelectDialog(builder, fileExplorerView);
|
||||
}
|
||||
|
||||
public String getCurrentPath() {
|
||||
return mFileExplorerView.getCurPath();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
mFileExplorerView.refresh();
|
||||
}
|
||||
|
||||
public boolean canWrite() {
|
||||
return mFileExplorerView.canWrite();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.util.ViewUtils;
|
||||
|
||||
public class MaterialAlertDialog extends AlertDialog implements
|
||||
View.OnClickListener, AdapterView.OnItemClickListener {
|
||||
|
||||
public final static int ACTION = 0x1;
|
||||
public final static int POSITIVE = 0x2;
|
||||
public final static int NEGATIVE = 0x4;
|
||||
public final static int NEUTRAL = 0x8;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final Builder mBuilder;
|
||||
|
||||
private View mRootView;
|
||||
private TextView mTitle;
|
||||
private Button mActionButton;
|
||||
private Button mPositiveButton;
|
||||
private Button mNegativeButton;
|
||||
private Button mNeutralButton;
|
||||
|
||||
protected MaterialAlertDialog(Builder builder) {
|
||||
super(builder.mContext);
|
||||
mContext = builder.mContext;
|
||||
mBuilder = builder;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private void init() {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
mRootView = inflater.inflate(R.layout.alert_dialog_material, null);
|
||||
|
||||
// Check in scroll view or not
|
||||
boolean inScrollView;
|
||||
if (mBuilder.mCustomView != null)
|
||||
inScrollView = mBuilder.mInScrollView;
|
||||
else if (mBuilder.mAdapter != null)
|
||||
inScrollView = false;
|
||||
else
|
||||
inScrollView = true;
|
||||
// Get widget
|
||||
LinearLayout topPanel;
|
||||
TextView message = null;
|
||||
ListView list = null;
|
||||
if (inScrollView) {
|
||||
mRootView.findViewById(R.id.top_panel_noscroll).setVisibility(
|
||||
View.GONE);
|
||||
mTitle = (TextView) mRootView.findViewById(R.id.title);
|
||||
topPanel = (LinearLayout) mRootView.findViewById(R.id.top_panel);
|
||||
message = (TextView) mRootView.findViewById(R.id.message);
|
||||
} else {
|
||||
mRootView.findViewById(R.id.scroll_view).setVisibility(View.GONE);
|
||||
mTitle = (TextView) mRootView.findViewById(R.id.title2);
|
||||
topPanel = (LinearLayout) mRootView
|
||||
.findViewById(R.id.top_panel_noscroll);
|
||||
list = (ListView) mRootView.findViewById(R.id.list);
|
||||
}
|
||||
// Title
|
||||
if (mBuilder.mTitle == null)
|
||||
mTitle.setVisibility(View.GONE);
|
||||
else
|
||||
mTitle.setText(mBuilder.mTitle);
|
||||
// Content
|
||||
if (mBuilder.mCustomView != null) {
|
||||
// CustomView
|
||||
if (message != null)
|
||||
message.setVisibility(View.GONE);
|
||||
if (list != null)
|
||||
list.setVisibility(View.GONE);
|
||||
LinearLayout.LayoutParams lp = mBuilder.mCustomLp;
|
||||
if (lp == null)
|
||||
lp = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
mBuilder.mCustomView.setId(R.id.custom);
|
||||
topPanel.addView(mBuilder.mCustomView, lp);
|
||||
} else if (mBuilder.mAdapter != null) {
|
||||
// List
|
||||
if (message != null)
|
||||
message.setVisibility(View.GONE);
|
||||
list.setAdapter(mBuilder.mAdapter);
|
||||
list.setOnItemClickListener(this);
|
||||
if (mBuilder.mIsSingleChoice) {
|
||||
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||
if (mBuilder.mCheckedItem > -1) {
|
||||
list.setItemChecked(mBuilder.mCheckedItem, true);
|
||||
list.setSelection(mBuilder.mCheckedItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Message
|
||||
if (list != null)
|
||||
list.setVisibility(View.GONE);
|
||||
if (mBuilder.mAutoLinkMask != -1)
|
||||
message.setAutoLinkMask(mBuilder.mAutoLinkMask);
|
||||
message.setText(mBuilder.mMessage);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
View buttonPanel = mRootView.findViewById(R.id.button_panel);
|
||||
View nonActionPanel = mRootView.findViewById(R.id.non_action_panel);
|
||||
mActionButton = (Button) mRootView.findViewById(R.id.button_action);
|
||||
mPositiveButton = (Button) mRootView.findViewById(R.id.button_positive);
|
||||
mNegativeButton = (Button) mRootView.findViewById(R.id.button_negative);
|
||||
mNeutralButton = (Button) mRootView.findViewById(R.id.button_neutral);
|
||||
boolean hasAction = mBuilder.mActionButtonText != null;
|
||||
boolean hasNonAction = mBuilder.mPositiveButtonText != null
|
||||
|| mBuilder.mNegativeButtonText != null
|
||||
|| mBuilder.mNeutralButtonText != null;
|
||||
boolean hasButton = hasAction || hasNonAction;
|
||||
if (hasButton) {
|
||||
if (hasAction) {
|
||||
mActionButton.setText(mBuilder.mActionButtonText);
|
||||
mActionButton.setOnClickListener(this);
|
||||
} else {
|
||||
mActionButton.setVisibility(View.GONE);
|
||||
}
|
||||
if (hasNonAction) {
|
||||
// Positive
|
||||
if (mBuilder.mPositiveButtonText != null) {
|
||||
mPositiveButton.setText(mBuilder.mPositiveButtonText);
|
||||
mPositiveButton.setOnClickListener(this);
|
||||
} else {
|
||||
mPositiveButton.setVisibility(View.GONE);
|
||||
}
|
||||
// Negative
|
||||
if (mBuilder.mNegativeButtonText != null) {
|
||||
mNegativeButton.setText(mBuilder.mNegativeButtonText);
|
||||
mNegativeButton.setOnClickListener(this);
|
||||
} else {
|
||||
mNegativeButton.setVisibility(View.GONE);
|
||||
}
|
||||
// Neutral
|
||||
if (mBuilder.mNeutralButtonText != null) {
|
||||
mNeutralButton.setText(mBuilder.mNeutralButtonText);
|
||||
mNeutralButton.setOnClickListener(this);
|
||||
} else {
|
||||
mNeutralButton.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
nonActionPanel.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
buttonPanel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setView(mRootView);
|
||||
|
||||
setCancelable(mBuilder.mCancelable);
|
||||
if (mBuilder.mOnCancelListener != null)
|
||||
setOnCancelListener(mBuilder.mOnCancelListener);
|
||||
if (mBuilder.mOnDismissListener != null)
|
||||
setOnDismissListener(mBuilder.mOnDismissListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mTitle.setText(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(int resId) {
|
||||
setTitle(getContext().getString(resId));
|
||||
}
|
||||
|
||||
public void setView(View view, boolean inScrollView) {
|
||||
setView(view, inScrollView, new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT));
|
||||
}
|
||||
|
||||
public void setView(View view, boolean inScrollView,
|
||||
LinearLayout.LayoutParams lp) {
|
||||
mRootView.findViewById(inScrollView ? R.id.top_panel_noscroll : R.id.scroll_view).setVisibility(View.GONE);
|
||||
mRootView.findViewById(inScrollView ? R.id.scroll_view : R.id.top_panel_noscroll).setVisibility(View.VISIBLE);
|
||||
mRootView.findViewById(R.id.message).setVisibility(View.GONE);
|
||||
mRootView.findViewById(R.id.list).setVisibility(View.GONE);
|
||||
LinearLayout topPanel = (LinearLayout) mRootView.findViewById(inScrollView ? R.id.top_panel : R.id.top_panel_noscroll);
|
||||
topPanel.addView(view, lp);
|
||||
// Title
|
||||
TextView oldTitle = mTitle;
|
||||
mTitle = (TextView) mRootView.findViewById(inScrollView ? R.id.title : R.id.title2);
|
||||
if (mTitle != oldTitle && oldTitle != null)
|
||||
mTitle.setText(oldTitle.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean close = true;
|
||||
|
||||
if (mBuilder.mButtonListener != null) {
|
||||
if (v == mActionButton)
|
||||
close = mBuilder.mButtonListener.onClick(this, ACTION);
|
||||
else if (v == mPositiveButton)
|
||||
close = mBuilder.mButtonListener.onClick(this, POSITIVE);
|
||||
else if (v == mNegativeButton)
|
||||
close = mBuilder.mButtonListener.onClick(this, NEGATIVE);
|
||||
else if (v == mNeutralButton)
|
||||
close = mBuilder.mButtonListener.onClick(this, NEUTRAL);
|
||||
}
|
||||
|
||||
if (close)
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
|
||||
boolean close = false;
|
||||
|
||||
if (mBuilder.mOnClickListener != null)
|
||||
close = mBuilder.mOnClickListener.onClick(this, position);
|
||||
|
||||
if (close)
|
||||
dismiss();
|
||||
}
|
||||
|
||||
public static interface OnClickListener {
|
||||
public boolean onClick(MaterialAlertDialog dialog, int which);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
protected Context mContext;
|
||||
|
||||
protected boolean mDarkTheme = false;
|
||||
protected CharSequence mTitle;
|
||||
protected CharSequence mMessage;
|
||||
protected CharSequence mActionButtonText;
|
||||
protected CharSequence mPositiveButtonText;
|
||||
protected CharSequence mNegativeButtonText;
|
||||
protected CharSequence mNeutralButtonText;
|
||||
protected OnClickListener mButtonListener;
|
||||
protected View mCustomView;
|
||||
protected LinearLayout.LayoutParams mCustomLp;
|
||||
protected boolean mInScrollView = true;
|
||||
protected boolean mCancelable = true;
|
||||
protected int mAutoLinkMask = -1;
|
||||
|
||||
protected OnCancelListener mOnCancelListener;
|
||||
|
||||
protected ListAdapter mAdapter;
|
||||
protected OnClickListener mOnClickListener;
|
||||
protected int mCheckedItem;
|
||||
protected boolean mIsSingleChoice;
|
||||
|
||||
protected DialogInterface.OnDismissListener mOnDismissListener;
|
||||
|
||||
public Builder(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public Builder setDrakTheme(boolean darkTheme) {
|
||||
mDarkTheme = darkTheme;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTitle(CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTitle(int resId) {
|
||||
return setTitle(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setMessage(CharSequence message) {
|
||||
mMessage = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessage(int resId) {
|
||||
return setMessage(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setActionButton(CharSequence text) {
|
||||
mActionButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setActionButton(int resId) {
|
||||
return setActionButton(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setPositiveButton(CharSequence text) {
|
||||
mPositiveButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPositiveButton(int resId) {
|
||||
return setPositiveButton(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setNegativeButton(CharSequence text) {
|
||||
mNegativeButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNegativeButton(int resId) {
|
||||
return setNegativeButton(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setNeutralButton(CharSequence text) {
|
||||
mNeutralButtonText = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNeutralButton(int resId) {
|
||||
return setNeutralButton(mContext.getString(resId));
|
||||
}
|
||||
|
||||
public Builder setDefaultButton(int b) {
|
||||
if ((b & POSITIVE) == POSITIVE)
|
||||
mPositiveButtonText = mContext.getString(android.R.string.ok);
|
||||
if ((b & NEGATIVE) == NEGATIVE)
|
||||
mNegativeButtonText = mContext.getString(android.R.string.cancel);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setButtonListener(OnClickListener l) {
|
||||
mButtonListener = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom view id will be R.id.custom
|
||||
*
|
||||
* @param view
|
||||
* @param inScrollView
|
||||
* @return
|
||||
*/
|
||||
public Builder setView(View view, boolean inScrollView) {
|
||||
mCustomView = view;
|
||||
mInScrollView = inScrollView;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom view id will be R.id.custom
|
||||
*
|
||||
* @param view
|
||||
* @param inScrollView
|
||||
* @param lp
|
||||
* @return
|
||||
*/
|
||||
public Builder setView(View view, boolean inScrollView,
|
||||
LinearLayout.LayoutParams lp) {
|
||||
mCustomView = view;
|
||||
mInScrollView = inScrollView;
|
||||
mCustomLp = lp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setView(int resId, boolean inScrollView, ViewHolder vh) {
|
||||
mCustomView = ViewUtils.inflateDialogView(resId, mDarkTheme);
|
||||
mInScrollView = inScrollView;
|
||||
if (vh != null)
|
||||
vh.setView(mCustomView);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCancelable(boolean cancelable) {
|
||||
mCancelable = cancelable;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
|
||||
mOnCancelListener = onCancelListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMessageAutoLink(int mask) {
|
||||
mAutoLinkMask = mask;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setItems(int itemsId, final OnClickListener listener) {
|
||||
mAdapter = new ArrayAdapter<CharSequence>(
|
||||
mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper,
|
||||
R.layout.select_dialog_item, android.R.id.text1, mContext
|
||||
.getResources().getTextArray(itemsId));
|
||||
mOnClickListener = listener;
|
||||
mIsSingleChoice = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setItems(CharSequence[] items,
|
||||
final OnClickListener listener) {
|
||||
mAdapter = new ArrayAdapter<CharSequence>(
|
||||
mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper,
|
||||
R.layout.select_dialog_item, android.R.id.text1, items);
|
||||
mOnClickListener = listener;
|
||||
mIsSingleChoice = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAdapter(final ListAdapter adapter,
|
||||
final OnClickListener listener) {
|
||||
mAdapter = adapter;
|
||||
mOnClickListener = listener;
|
||||
mIsSingleChoice = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSingleChoiceItems(int itemsId, int checkedItem,
|
||||
final OnClickListener listener) {
|
||||
mAdapter = new ArrayAdapter<CharSequence>(
|
||||
mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper,
|
||||
R.layout.select_dialog_singlechoice, android.R.id.text1,
|
||||
mContext.getResources().getTextArray(itemsId));
|
||||
mOnClickListener = listener;
|
||||
mCheckedItem = checkedItem;
|
||||
mIsSingleChoice = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSingleChoiceItems(CharSequence[] items,
|
||||
int checkedItem, final OnClickListener listener) {
|
||||
mAdapter = new ArrayAdapter<CharSequence>(
|
||||
mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper,
|
||||
R.layout.select_dialog_singlechoice, android.R.id.text1,
|
||||
items);
|
||||
mOnClickListener = listener;
|
||||
mCheckedItem = checkedItem;
|
||||
mIsSingleChoice = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSingleChoiceItems(ListAdapter adapter,
|
||||
int checkedItem, final OnClickListener listener) {
|
||||
mAdapter = adapter;
|
||||
mOnClickListener = listener;
|
||||
mCheckedItem = checkedItem;
|
||||
mIsSingleChoice = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOnDismissListener(DialogInterface.OnDismissListener l) {
|
||||
mOnDismissListener = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MaterialAlertDialog create() {
|
||||
mContext = new ContextThemeWrapper(mContext, mDarkTheme ?
|
||||
R.style.AppTheme_Dark_Dialog : R.style.AppTheme_Dialog);
|
||||
return new MaterialAlertDialog(this);
|
||||
}
|
||||
|
||||
public MaterialAlertDialog show() {
|
||||
MaterialAlertDialog dialog = create();
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder {
|
||||
private View mView;
|
||||
|
||||
private void setView(View v) {
|
||||
mView = v;
|
||||
}
|
||||
|
||||
public View getView() {
|
||||
return mView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.app;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
public class MaterialProgressDialog extends MaterialAlertDialog {
|
||||
|
||||
|
||||
/** Creates a ProgressDialog with a circular, spinning progress
|
||||
* bar. This is the default.
|
||||
*/
|
||||
public static final int STYLE_SPINNER = 0;
|
||||
|
||||
/** Creates a ProgressDialog with a horizontal progress bar.
|
||||
*/
|
||||
public static final int STYLE_HORIZONTAL = 1;
|
||||
|
||||
private ProgressBar mProgress;
|
||||
private TextView mMessageView;
|
||||
|
||||
private int mProgressStyle = STYLE_HORIZONTAL;
|
||||
private TextView mProgressNumber;
|
||||
private String mProgressNumberFormat;
|
||||
private TextView mProgressPercent;
|
||||
private NumberFormat mProgressPercentFormat;
|
||||
|
||||
private int mMax;
|
||||
private int mProgressVal;
|
||||
private int mSecondaryProgressVal;
|
||||
private boolean mIndeterminate;
|
||||
|
||||
private boolean mHasStarted;
|
||||
private Handler mViewUpdateHandler;
|
||||
|
||||
|
||||
@SuppressLint({ "InflateParams", "HandlerLeak" })
|
||||
protected MaterialProgressDialog(Builder builder) {
|
||||
super(builder);
|
||||
initFormats();
|
||||
|
||||
LayoutInflater inflater = LayoutInflater.from(builder.mContext);
|
||||
if (mProgressStyle == STYLE_HORIZONTAL) {
|
||||
|
||||
/* Use a separate handler to update the text views as they
|
||||
* must be updated on the same thread that created them.
|
||||
*/
|
||||
mViewUpdateHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
|
||||
/* Update the number and percent */
|
||||
int progress = mProgress.getProgress();
|
||||
int max = mProgress.getMax();
|
||||
if (mProgressNumberFormat != null) {
|
||||
String format = mProgressNumberFormat;
|
||||
mProgressNumber.setText(String.format(format, progress, max));
|
||||
} else {
|
||||
mProgressNumber.setText("");
|
||||
}
|
||||
if (mProgressPercentFormat != null) {
|
||||
double percent = (double) progress / (double) max;
|
||||
SpannableString tmp = new SpannableString(mProgressPercentFormat.format(percent));
|
||||
tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
|
||||
0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
mProgressPercent.setText(tmp);
|
||||
} else {
|
||||
mProgressPercent.setText("");
|
||||
}
|
||||
}
|
||||
};
|
||||
View view = inflater.inflate(R.layout.alert_dialog_progress, null);
|
||||
mProgress = (ProgressBar) view.findViewById(R.id.progress);
|
||||
mProgressNumber = (TextView) view.findViewById(R.id.progress_number);
|
||||
mProgressPercent = (TextView) view.findViewById(R.id.progress_percent);
|
||||
setView(view, false);
|
||||
} else {
|
||||
View view = inflater.inflate(R.layout.progress_dialog, null);
|
||||
mProgress = (ProgressBar) view.findViewById(R.id.progress);
|
||||
mMessageView = (TextView) view.findViewById(R.id.message);
|
||||
setView(view, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void initFormats() {
|
||||
mProgressNumberFormat = "%1d/%2d";
|
||||
mProgressPercentFormat = NumberFormat.getPercentInstance();
|
||||
mProgressPercentFormat.setMaximumFractionDigits(0);
|
||||
}
|
||||
|
||||
|
||||
public static MaterialProgressDialog create(Context context, CharSequence title) {
|
||||
return create(context, title, false, null);
|
||||
}
|
||||
|
||||
public static MaterialProgressDialog create(Context context, CharSequence title,
|
||||
boolean cancelable) {
|
||||
return create(context, title, cancelable, null);
|
||||
}
|
||||
|
||||
public static MaterialProgressDialog create(Context context, CharSequence title,
|
||||
boolean cancelable, OnCancelListener cancelListener) {
|
||||
|
||||
MaterialProgressDialog.Builder builder = new MaterialProgressDialog.Builder(context);
|
||||
builder.setTitle(title);
|
||||
builder.setCancelable(cancelable);
|
||||
builder.setOnCancelListener(cancelListener);
|
||||
return new MaterialProgressDialog(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
mHasStarted = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mHasStarted = false;
|
||||
}
|
||||
|
||||
public void setProgress(int value) {
|
||||
if (mHasStarted) {
|
||||
mProgress.setProgress(value);
|
||||
onProgressChanged();
|
||||
} else {
|
||||
mProgressVal = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSecondaryProgress(int secondaryProgress) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setSecondaryProgress(secondaryProgress);
|
||||
onProgressChanged();
|
||||
} else {
|
||||
mSecondaryProgressVal = secondaryProgress;
|
||||
}
|
||||
}
|
||||
|
||||
public int getProgress() {
|
||||
if (mProgress != null) {
|
||||
return mProgress.getProgress();
|
||||
}
|
||||
return mProgressVal;
|
||||
}
|
||||
|
||||
public int getSecondaryProgress() {
|
||||
if (mProgress != null) {
|
||||
return mProgress.getSecondaryProgress();
|
||||
}
|
||||
return mSecondaryProgressVal;
|
||||
}
|
||||
|
||||
public int getMax() {
|
||||
if (mProgress != null) {
|
||||
return mProgress.getMax();
|
||||
}
|
||||
return mMax;
|
||||
}
|
||||
|
||||
public void setMax(int max) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setMax(max);
|
||||
onProgressChanged();
|
||||
} else {
|
||||
mMax = max;
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementProgressBy(int diff) {
|
||||
if (mProgress != null) {
|
||||
mProgress.incrementProgressBy(diff);
|
||||
onProgressChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementSecondaryProgressBy(int diff) {
|
||||
if (mProgress != null) {
|
||||
mProgress.incrementSecondaryProgressBy(diff);
|
||||
onProgressChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressDrawable(Drawable d) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgressDrawable(d);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIndeterminateDrawable(Drawable d) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setIndeterminateDrawable(d);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setIndeterminate(indeterminate);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isIndeterminate() {
|
||||
if (mProgress != null) {
|
||||
return mProgress.isIndeterminate();
|
||||
}
|
||||
return mIndeterminate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(CharSequence message) {
|
||||
if (mProgress != null) {
|
||||
if (mProgressStyle == STYLE_HORIZONTAL) {
|
||||
super.setMessage(message);
|
||||
} else {
|
||||
mMessageView.setText(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgressStyle(int style) {
|
||||
mProgressStyle = style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the format of the small text showing current and maximum units
|
||||
* of progress. The default is "%1d/%2d".
|
||||
* Should not be called during the number is progressing.
|
||||
* @param format A string passed to {@link String#format String.format()};
|
||||
* use "%1d" for the current number and "%2d" for the maximum. If null,
|
||||
* nothing will be shown.
|
||||
*/
|
||||
public void setProgressNumberFormat(String format) {
|
||||
mProgressNumberFormat = format;
|
||||
onProgressChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the format of the small text showing the percentage of progress.
|
||||
* The default is
|
||||
* {@link NumberFormat#getPercentInstance() NumberFormat.getPercentageInstnace().}
|
||||
* Should not be called during the number is progressing.
|
||||
* @param format An instance of a {@link NumberFormat} to generate the
|
||||
* percentage text. If null, nothing will be shown.
|
||||
*/
|
||||
public void setProgressPercentFormat(NumberFormat format) {
|
||||
mProgressPercentFormat = format;
|
||||
onProgressChanged();
|
||||
}
|
||||
|
||||
private void onProgressChanged() {
|
||||
if (mProgressStyle == STYLE_HORIZONTAL) {
|
||||
if (mViewUpdateHandler != null && !mViewUpdateHandler.hasMessages(0)) {
|
||||
mViewUpdateHandler.sendEmptyMessage(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,969 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project 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.cache;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
****************************************************************************** Taken from the JB source code, can be found in:
|
||||
* libcore/luni/src/main/java/libcore/io/DiskLruCache.java or direct link:
|
||||
* https:
|
||||
* //android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/
|
||||
* main/java/libcore/io/DiskLruCache.java A cache that uses a bounded amount of
|
||||
* space on a filesystem. Each cache entry has a string key and a fixed number
|
||||
* of values. Values are byte sequences, accessible as streams or files. Each
|
||||
* value must be between {@code 0} and {@code Integer.MAX_VALUE} bytes in
|
||||
* length.
|
||||
* <p>
|
||||
* The cache stores its data in a directory on the filesystem. This directory
|
||||
* must be exclusive to the cache; the cache may delete or overwrite files from
|
||||
* its directory. It is an error for multiple processes to use the same cache
|
||||
* directory at the same time.
|
||||
* <p>
|
||||
* This cache limits the number of bytes that it will store on the filesystem.
|
||||
* When the number of stored bytes exceeds the limit, the cache will remove
|
||||
* entries in the background until the limit is satisfied. The limit is not
|
||||
* strict: the cache may temporarily exceed it while waiting for files to be
|
||||
* deleted. The limit does not include filesystem overhead or the cache journal
|
||||
* so space-sensitive applications should set a conservative limit.
|
||||
* <p>
|
||||
* Clients call {@link #edit} to create or update the values of an entry. An
|
||||
* entry may have only one editor at one time; if a value is not available to be
|
||||
* edited then {@link #edit} will return null.
|
||||
* <ul>
|
||||
* <li>When an entry is being <strong>created</strong> it is necessary to supply
|
||||
* a full set of values; the empty value should be used as a placeholder if
|
||||
* necessary.
|
||||
* <li>When an entry is being <strong>edited</strong>, it is not necessary to
|
||||
* supply data for every value; values default to their previous value.
|
||||
* </ul>
|
||||
* Every {@link #edit} call must be matched by a call to {@link Editor#commit}
|
||||
* or {@link Editor#abort}. Committing is atomic: a read observes the full set
|
||||
* of values as they were before or after the commit, but never a mix of values.
|
||||
* <p>
|
||||
* Clients call {@link #get} to read a snapshot of an entry. The read will
|
||||
* observe the value at the time that {@link #get} was called. Updates and
|
||||
* removals after the call do not impact ongoing reads.
|
||||
* <p>
|
||||
* This class is tolerant of some I/O errors. If files are missing from the
|
||||
* filesystem, the corresponding entries will be dropped from the cache. If an
|
||||
* error occurs while writing a cache value, the edit will fail silently.
|
||||
* Callers should handle other problems by catching {@code IOException} and
|
||||
* responding appropriately.
|
||||
*/
|
||||
public final class DiskLruCache implements Closeable {
|
||||
static final String JOURNAL_FILE = "journal";
|
||||
|
||||
static final String JOURNAL_FILE_TMP = "journal.tmp";
|
||||
|
||||
static final String MAGIC = "libcore.io.DiskLruCache";
|
||||
|
||||
static final String VERSION_1 = "1";
|
||||
|
||||
static final long ANY_SEQUENCE_NUMBER = -1;
|
||||
|
||||
private static final String CLEAN = "CLEAN";
|
||||
|
||||
private static final String DIRTY = "DIRTY";
|
||||
|
||||
private static final String REMOVE = "REMOVE";
|
||||
|
||||
private static final String READ = "READ";
|
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
/*
|
||||
* This cache uses a journal file named "journal". A typical journal file
|
||||
* looks like this: libcore.io.DiskLruCache 1 100 2 CLEAN
|
||||
* 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY
|
||||
* 335c4c6028171cfddfbaae1a9c313c52 CLEAN 335c4c6028171cfddfbaae1a9c313c52
|
||||
* 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY
|
||||
* 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a
|
||||
* 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ
|
||||
* 3400330d1dfc7f3f7f4b8d4d803dfcf6 The first five lines of the journal form
|
||||
* its header. They are the constant string "libcore.io.DiskLruCache", the
|
||||
* disk cache's version, the application's version, the value count, and a
|
||||
* blank line. Each of the subsequent lines in the file is a record of the
|
||||
* state of a cache entry. Each line contains space-separated values: a
|
||||
* state, a key, and optional state-specific values. o DIRTY lines track
|
||||
* that an entry is actively being created or updated. Every successful
|
||||
* DIRTY action should be followed by a CLEAN or REMOVE action. DIRTY lines
|
||||
* without a matching CLEAN or REMOVE indicate that temporary files may need
|
||||
* to be deleted. o CLEAN lines track a cache entry that has been
|
||||
* successfully published and may be read. A publish line is followed by the
|
||||
* lengths of each of its values. o READ lines track accesses for LRU. o
|
||||
* REMOVE lines track entries that have been deleted. The journal file is
|
||||
* appended to as cache operations occur. The journal may occasionally be
|
||||
* compacted by dropping redundant lines. A temporary file named
|
||||
* "journal.tmp" will be used during compaction; that file should be deleted
|
||||
* if it exists when the cache is opened.
|
||||
*/
|
||||
|
||||
private final File directory;
|
||||
|
||||
private final File journalFile;
|
||||
|
||||
private final File journalFileTmp;
|
||||
|
||||
private final int appVersion;
|
||||
|
||||
private final long maxSize;
|
||||
|
||||
private final int valueCount;
|
||||
|
||||
private long size = 0;
|
||||
|
||||
private Writer journalWriter;
|
||||
|
||||
private final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<String, Entry>(0,
|
||||
0.75f, true);
|
||||
|
||||
private int redundantOpCount;
|
||||
|
||||
/**
|
||||
* To differentiate between old and current snapshots, each entry is given a
|
||||
* sequence number each time an edit is committed. A snapshot is stale if
|
||||
* its sequence number is not equal to its entry's sequence number.
|
||||
*/
|
||||
private long nextSequenceNumber = 0;
|
||||
|
||||
/* From java.util.Arrays */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T[] copyOfRange(final T[] original, final int start, final int end) {
|
||||
final int originalLength = original.length; // For exception priority
|
||||
// compatibility.
|
||||
if (start > end) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (start < 0 || start > originalLength) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
final int resultLength = end - start;
|
||||
final int copyLength = Math.min(resultLength, originalLength - start);
|
||||
final T[] result = (T[])Array.newInstance(original.getClass().getComponentType(),
|
||||
resultLength);
|
||||
System.arraycopy(original, start, result, 0, copyLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remainder of 'reader' as a string, closing it when done.
|
||||
*/
|
||||
public static String readFully(final Reader reader) throws IOException {
|
||||
try {
|
||||
final StringWriter writer = new StringWriter();
|
||||
final char[] buffer = new char[1024];
|
||||
int count;
|
||||
while ((count = reader.read(buffer)) != -1) {
|
||||
writer.write(buffer, 0, count);
|
||||
}
|
||||
return writer.toString();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ASCII characters up to but not including the next "\r\n", or
|
||||
* "\n".
|
||||
*
|
||||
* @throws java.io.EOFException if the stream is exhausted before the next
|
||||
* newline character.
|
||||
*/
|
||||
public static String readAsciiLine(final InputStream in) throws IOException {
|
||||
// TODO: support UTF-8 here instead
|
||||
|
||||
final StringBuilder result = new StringBuilder(80);
|
||||
while (true) {
|
||||
final int c = in.read();
|
||||
if (c == -1) {
|
||||
throw new EOFException();
|
||||
} else if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
|
||||
result.append((char)c);
|
||||
}
|
||||
final int length = result.length();
|
||||
if (length > 0 && result.charAt(length - 1) == '\r') {
|
||||
result.setLength(length - 1);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes 'closeable', ignoring any checked exceptions. Does nothing if
|
||||
* 'closeable' is null.
|
||||
*/
|
||||
public static void closeQuietly(final Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (final RuntimeException rethrown) {
|
||||
throw rethrown;
|
||||
} catch (final Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete everything in {@code dir}.
|
||||
*/
|
||||
// TODO: this should specify paths as Strings rather than as Files
|
||||
public static void deleteContents(final File dir) throws IOException {
|
||||
final File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
throw new IllegalArgumentException("not a directory: " + dir);
|
||||
}
|
||||
for (final File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteContents(file);
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete file: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This cache uses a single background thread to evict entries. */
|
||||
private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 60L,
|
||||
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
private final Callable<Void> cleanupCallable = new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (journalWriter == null) {
|
||||
return null; // closed
|
||||
}
|
||||
trimToSize();
|
||||
if (journalRebuildRequired()) {
|
||||
rebuildJournal();
|
||||
redundantOpCount = 0;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private DiskLruCache(final File directory, final int appVersion, final int valueCount,
|
||||
final long maxSize) {
|
||||
this.directory = directory;
|
||||
this.appVersion = appVersion;
|
||||
journalFile = new File(directory, JOURNAL_FILE);
|
||||
journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
|
||||
this.valueCount = valueCount;
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the cache in {@code directory}, creating a cache if none exists
|
||||
* there.
|
||||
*
|
||||
* @param directory a writable directory
|
||||
* @param appVersion
|
||||
* @param valueCount the number of values per cache entry. Must be positive.
|
||||
* @param maxSize the maximum number of bytes this cache should use to store
|
||||
* @throws IOException if reading or writing the cache directory fails
|
||||
*/
|
||||
public static DiskLruCache open(final File directory, final int appVersion,
|
||||
final int valueCount, final long maxSize) throws IOException {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
if (valueCount <= 0) {
|
||||
throw new IllegalArgumentException("valueCount <= 0");
|
||||
}
|
||||
|
||||
// prefer to pick up where we left off
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
if (cache.journalFile.exists()) {
|
||||
try {
|
||||
cache.readJournal();
|
||||
cache.processJournal();
|
||||
cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
|
||||
IO_BUFFER_SIZE);
|
||||
return cache;
|
||||
} catch (final IOException journalIsCorrupt) {
|
||||
// System.logW("DiskLruCache " + directory + " is corrupt: "
|
||||
// + journalIsCorrupt.getMessage() + ", removing");
|
||||
cache.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// create a new empty cache
|
||||
directory.mkdirs();
|
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
|
||||
cache.rebuildJournal();
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void readJournal() throws IOException {
|
||||
final InputStream in = new BufferedInputStream(new FileInputStream(journalFile),
|
||||
IO_BUFFER_SIZE);
|
||||
try {
|
||||
final String magic = readAsciiLine(in);
|
||||
final String version = readAsciiLine(in);
|
||||
final String appVersionString = readAsciiLine(in);
|
||||
final String valueCountString = readAsciiLine(in);
|
||||
final String blank = readAsciiLine(in);
|
||||
if (!MAGIC.equals(magic) || !VERSION_1.equals(version)
|
||||
|| !Integer.toString(appVersion).equals(appVersionString)
|
||||
|| !Integer.toString(valueCount).equals(valueCountString) || !"".equals(blank)) {
|
||||
throw new IOException("unexpected journal header: [" + magic + ", " + version
|
||||
+ ", " + valueCountString + ", " + blank + "]");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
readJournalLine(readAsciiLine(in));
|
||||
} catch (final EOFException endOfJournal) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(in);
|
||||
}
|
||||
}
|
||||
|
||||
private void readJournalLine(final String line) throws IOException {
|
||||
final String[] parts = line.split(" ");
|
||||
if (parts.length < 2) {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
|
||||
final String key = parts[1];
|
||||
if (parts[0].equals(REMOVE) && parts.length == 2) {
|
||||
lruEntries.remove(key);
|
||||
return;
|
||||
}
|
||||
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
}
|
||||
|
||||
if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
|
||||
entry.readable = true;
|
||||
entry.currentEditor = null;
|
||||
entry.setLengths(copyOfRange(parts, 2, parts.length));
|
||||
} else if (parts[0].equals(DIRTY) && parts.length == 2) {
|
||||
entry.currentEditor = new Editor(entry);
|
||||
} else if (parts[0].equals(READ) && parts.length == 2) {
|
||||
// this work was already done by calling lruEntries.get()
|
||||
} else {
|
||||
throw new IOException("unexpected journal line: " + line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the initial size and collects garbage as a part of opening the
|
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted.
|
||||
*/
|
||||
private void processJournal() throws IOException {
|
||||
deleteIfExists(journalFileTmp);
|
||||
for (final Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext();) {
|
||||
final Entry entry = i.next();
|
||||
if (entry.currentEditor == null) {
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
size += entry.lengths[t];
|
||||
}
|
||||
} else {
|
||||
entry.currentEditor = null;
|
||||
for (int t = 0; t < valueCount; t++) {
|
||||
deleteIfExists(entry.getCleanFile(t));
|
||||
deleteIfExists(entry.getDirtyFile(t));
|
||||
}
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new journal that omits redundant information. This replaces the
|
||||
* current journal if it exists.
|
||||
*/
|
||||
private synchronized void rebuildJournal() throws IOException {
|
||||
if (journalWriter != null) {
|
||||
journalWriter.close();
|
||||
}
|
||||
|
||||
final Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
|
||||
writer.write(MAGIC);
|
||||
writer.write("\n");
|
||||
writer.write(VERSION_1);
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(appVersion));
|
||||
writer.write("\n");
|
||||
writer.write(Integer.toString(valueCount));
|
||||
writer.write("\n");
|
||||
writer.write("\n");
|
||||
|
||||
for (final Entry entry : lruEntries.values()) {
|
||||
if (entry.currentEditor != null) {
|
||||
writer.write(DIRTY + ' ' + entry.key + '\n');
|
||||
} else {
|
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
writer.close();
|
||||
journalFileTmp.renameTo(journalFile);
|
||||
journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
private static void deleteIfExists(final File file) throws IOException {
|
||||
// try {
|
||||
// Libcore.os.remove(file.getPath());
|
||||
// } catch (ErrnoException errnoException) {
|
||||
// if (errnoException.errno != OsConstants.ENOENT) {
|
||||
// throw errnoException.rethrowAsIOException();
|
||||
// }
|
||||
// }
|
||||
if (file.exists() && !file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't
|
||||
* exist is not currently readable. If a value is returned, it is moved to
|
||||
* the head of the LRU queue.
|
||||
*/
|
||||
public synchronized Snapshot get(final String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
final Entry entry = lruEntries.get(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open all streams eagerly to guarantee that we see a single published
|
||||
* snapshot. If we opened streams lazily then the streams could come
|
||||
* from different edits.
|
||||
*/
|
||||
final InputStream[] ins = new InputStream[valueCount];
|
||||
try {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
ins[i] = new FileInputStream(entry.getCleanFile(i));
|
||||
}
|
||||
} catch (final FileNotFoundException e) {
|
||||
// a file must have been deleted manually!
|
||||
return null;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(READ + ' ' + key + '\n');
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, ins);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for the entry named {@code key}, or null if another
|
||||
* edit is in progress.
|
||||
*/
|
||||
public Editor edit(final String key) throws IOException {
|
||||
return edit(key, ANY_SEQUENCE_NUMBER);
|
||||
}
|
||||
|
||||
private synchronized Editor edit(final String key, final long expectedSequenceNumber)
|
||||
throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
Entry entry = lruEntries.get(key);
|
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
|
||||
&& (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
|
||||
return null; // snapshot is stale
|
||||
}
|
||||
if (entry == null) {
|
||||
entry = new Entry(key);
|
||||
lruEntries.put(key, entry);
|
||||
} else if (entry.currentEditor != null) {
|
||||
return null; // another edit is in progress
|
||||
}
|
||||
|
||||
final Editor editor = new Editor(entry);
|
||||
entry.currentEditor = editor;
|
||||
|
||||
// flush the journal before creating files to prevent file leaks
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n');
|
||||
journalWriter.flush();
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory where this cache stores its data.
|
||||
*/
|
||||
public File getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of bytes that this cache should use to store
|
||||
* its data.
|
||||
*/
|
||||
public long maxSize() {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes currently being used to store the values in
|
||||
* this cache. This may be greater than the max size if a background
|
||||
* deletion is pending.
|
||||
*/
|
||||
public synchronized long size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
private synchronized void completeEdit(final Editor editor, final boolean success)
|
||||
throws IOException {
|
||||
final Entry entry = editor.entry;
|
||||
if (entry.currentEditor != editor) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// if this edit is creating the entry for the first time, every index
|
||||
// must have a value
|
||||
if (success && !entry.readable) {
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
if (!entry.getDirtyFile(i).exists()) {
|
||||
editor.abort();
|
||||
throw new IllegalStateException("edit didn't create file " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
final File dirty = entry.getDirtyFile(i);
|
||||
if (success) {
|
||||
if (dirty.exists()) {
|
||||
final File clean = entry.getCleanFile(i);
|
||||
dirty.renameTo(clean);
|
||||
final long oldLength = entry.lengths[i];
|
||||
final long newLength = clean.length();
|
||||
entry.lengths[i] = newLength;
|
||||
size = size - oldLength + newLength;
|
||||
}
|
||||
} else {
|
||||
deleteIfExists(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
entry.currentEditor = null;
|
||||
if (entry.readable | success) {
|
||||
entry.readable = true;
|
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
|
||||
if (success) {
|
||||
entry.sequenceNumber = nextSequenceNumber++;
|
||||
}
|
||||
} else {
|
||||
lruEntries.remove(entry.key);
|
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n');
|
||||
}
|
||||
|
||||
if (size > maxSize || journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only rebuild the journal when it will halve the size of the journal
|
||||
* and eliminate at least 2000 ops.
|
||||
*/
|
||||
private boolean journalRebuildRequired() {
|
||||
final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
|
||||
return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
|
||||
&& redundantOpCount >= lruEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries
|
||||
* actively being edited cannot be removed.
|
||||
*
|
||||
* @return true if an entry was removed.
|
||||
*/
|
||||
public synchronized boolean remove(final String key) throws IOException {
|
||||
checkNotClosed();
|
||||
validateKey(key);
|
||||
final Entry entry = lruEntries.get(key);
|
||||
if (entry == null || entry.currentEditor != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < valueCount; i++) {
|
||||
final File file = entry.getCleanFile(i);
|
||||
if (!file.delete()) {
|
||||
throw new IOException("failed to delete " + file);
|
||||
}
|
||||
size -= entry.lengths[i];
|
||||
entry.lengths[i] = 0;
|
||||
}
|
||||
|
||||
redundantOpCount++;
|
||||
journalWriter.append(REMOVE + ' ' + key + '\n');
|
||||
lruEntries.remove(key);
|
||||
|
||||
if (journalRebuildRequired()) {
|
||||
executorService.submit(cleanupCallable);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this cache has been closed.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return journalWriter == null;
|
||||
}
|
||||
|
||||
private void checkNotClosed() {
|
||||
if (journalWriter == null) {
|
||||
throw new IllegalStateException("cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force buffered operations to the filesystem.
|
||||
*/
|
||||
public synchronized void flush() throws IOException {
|
||||
checkNotClosed();
|
||||
trimToSize();
|
||||
journalWriter.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes this cache. Stored values will remain on the filesystem.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
if (journalWriter == null) {
|
||||
return; // already closed
|
||||
}
|
||||
for (final Entry entry : new ArrayList<Entry>(lruEntries.values())) {
|
||||
if (entry.currentEditor != null) {
|
||||
entry.currentEditor.abort();
|
||||
}
|
||||
}
|
||||
trimToSize();
|
||||
journalWriter.close();
|
||||
journalWriter = null;
|
||||
}
|
||||
|
||||
private void trimToSize() throws IOException {
|
||||
while (size > maxSize) {
|
||||
// Map.Entry<String, Entry> toEvict = lruEntries.eldest();
|
||||
final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
|
||||
remove(toEvict.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the cache and deletes all of its stored values. This will delete
|
||||
* all files in the cache directory including files that weren't created by
|
||||
* the cache.
|
||||
*/
|
||||
public void delete() throws IOException {
|
||||
close();
|
||||
deleteContents(directory);
|
||||
}
|
||||
|
||||
private void validateKey(final String key) {
|
||||
if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
|
||||
throw new IllegalArgumentException("keys must not contain spaces or newlines: \"" + key
|
||||
+ "\"");
|
||||
}
|
||||
}
|
||||
|
||||
private static String inputStreamToString(final InputStream in) throws IOException {
|
||||
return readFully(new InputStreamReader(in, UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* A snapshot of the values for an entry.
|
||||
*/
|
||||
public final class Snapshot implements Closeable {
|
||||
private final String key;
|
||||
|
||||
private final long sequenceNumber;
|
||||
|
||||
private final InputStream[] ins;
|
||||
|
||||
private Snapshot(final String key, final long sequenceNumber, final InputStream[] ins) {
|
||||
this.key = key;
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.ins = ins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an editor for this snapshot's entry, or null if either the
|
||||
* entry has changed since this snapshot was created or if another edit
|
||||
* is in progress.
|
||||
*/
|
||||
public Editor edit() throws IOException {
|
||||
return DiskLruCache.this.edit(key, sequenceNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unbuffered stream with the value for {@code index}.
|
||||
*/
|
||||
public InputStream getInputStream(final int index) {
|
||||
return ins[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string value for {@code index}.
|
||||
*/
|
||||
public String getString(final int index) throws IOException {
|
||||
return inputStreamToString(getInputStream(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
for (final InputStream in : ins) {
|
||||
closeQuietly(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits the values for an entry.
|
||||
*/
|
||||
public final class Editor {
|
||||
private final Entry entry;
|
||||
|
||||
private boolean hasErrors;
|
||||
|
||||
private Editor(final Entry entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unbuffered input stream to read the last committed value,
|
||||
* or null if no value has been committed.
|
||||
*/
|
||||
public InputStream newInputStream(final int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!entry.readable) {
|
||||
return null;
|
||||
}
|
||||
return new FileInputStream(entry.getCleanFile(index));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last committed value as a string, or null if no value has
|
||||
* been committed.
|
||||
*/
|
||||
public String getString(final int index) throws IOException {
|
||||
final InputStream in = newInputStream(index);
|
||||
return in != null ? inputStreamToString(in) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unbuffered output stream to write the value at
|
||||
* {@code index}. If the underlying output stream encounters errors when
|
||||
* writing to the filesystem, this edit will be aborted when
|
||||
* {@link #commit} is called. The returned output stream does not throw
|
||||
* IOExceptions.
|
||||
*/
|
||||
public OutputStream newOutputStream(final int index) throws IOException {
|
||||
synchronized (DiskLruCache.this) {
|
||||
if (entry.currentEditor != this) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value at {@code index} to {@code value}.
|
||||
*/
|
||||
public void set(final int index, final String value) throws IOException {
|
||||
Writer writer = null;
|
||||
try {
|
||||
writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
|
||||
writer.write(value);
|
||||
} finally {
|
||||
closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits this edit so it is visible to readers. This releases the edit
|
||||
* lock so another edit may be started on the same key.
|
||||
*/
|
||||
public void commit() throws IOException {
|
||||
if (hasErrors) {
|
||||
completeEdit(this, false);
|
||||
remove(entry.key); // the previous entry is stale
|
||||
} else {
|
||||
completeEdit(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts this edit. This releases the edit lock so another edit may be
|
||||
* started on the same key.
|
||||
*/
|
||||
public void abort() throws IOException {
|
||||
completeEdit(this, false);
|
||||
}
|
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream {
|
||||
private FaultHidingOutputStream(final OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int oneByte) {
|
||||
try {
|
||||
out.write(oneByte);
|
||||
} catch (final IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] buffer, final int offset, final int length) {
|
||||
try {
|
||||
out.write(buffer, offset, length);
|
||||
} catch (final IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
out.close();
|
||||
} catch (final IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (final IOException e) {
|
||||
hasErrors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class Entry {
|
||||
private final String key;
|
||||
|
||||
/** Lengths of this entry's files. */
|
||||
private final long[] lengths;
|
||||
|
||||
/** True if this entry has ever been published */
|
||||
private boolean readable;
|
||||
|
||||
/** The ongoing edit or null if this entry is not being edited. */
|
||||
private Editor currentEditor;
|
||||
|
||||
/**
|
||||
* The sequence number of the most recently committed edit to this
|
||||
* entry.
|
||||
*/
|
||||
private long sequenceNumber;
|
||||
|
||||
private Entry(final String key) {
|
||||
this.key = key;
|
||||
lengths = new long[valueCount];
|
||||
}
|
||||
|
||||
public String getLengths() throws IOException {
|
||||
final StringBuilder result = new StringBuilder();
|
||||
for (final long size : lengths) {
|
||||
result.append(' ').append(size);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set lengths using decimal numbers like "10123".
|
||||
*/
|
||||
private void setLengths(final String[] strings) throws IOException {
|
||||
if (strings.length != valueCount) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
lengths[i] = Long.parseLong(strings[i]);
|
||||
}
|
||||
} catch (final NumberFormatException e) {
|
||||
throw invalidLengths(strings);
|
||||
}
|
||||
}
|
||||
|
||||
private IOException invalidLengths(final String[] strings) throws IOException {
|
||||
throw new IOException("unexpected journal line: " + Arrays.toString(strings));
|
||||
}
|
||||
|
||||
public File getCleanFile(final int i) {
|
||||
return new File(directory, key + "." + i);
|
||||
}
|
||||
|
||||
public File getDirtyFile(final int i) {
|
||||
return new File(directory, key + "." + i + ".tmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,721 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Andrew Neal 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.cache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Fragment;
|
||||
import android.app.FragmentManager;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Looper;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
/**
|
||||
* This class holds the memory and disk bitmap caches.
|
||||
*/
|
||||
public final class ImageCache {
|
||||
|
||||
private static final String TAG = ImageCache.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* Default memory cache size as a percent of device memory class
|
||||
*/
|
||||
private static final float MEM_CACHE_DIVIDER = 0.25f;
|
||||
|
||||
/**
|
||||
* Default disk cache size 10MB
|
||||
*/
|
||||
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10;
|
||||
|
||||
/**
|
||||
* Compression settings when writing images to disk cache
|
||||
*/
|
||||
private static final CompressFormat COMPRESS_FORMAT = CompressFormat.JPEG;
|
||||
|
||||
/**
|
||||
* Disk cache index to read from
|
||||
*/
|
||||
private static final int DISK_CACHE_INDEX = 0;
|
||||
|
||||
/**
|
||||
* Image compression quality
|
||||
*/
|
||||
private static final int COMPRESS_QUALITY = 98;
|
||||
|
||||
/**
|
||||
* LRU cache
|
||||
*/
|
||||
private MemoryCache mLruCache;
|
||||
|
||||
/**
|
||||
* Disk LRU cache
|
||||
*/
|
||||
private DiskLruCache mDiskCache;
|
||||
|
||||
private static ImageCache sInstance;
|
||||
|
||||
/**
|
||||
* Used to temporarily pause the disk cache while scrolling
|
||||
*/
|
||||
public boolean mPauseDiskAccess = false;
|
||||
private final Object mPauseLock = new Object();
|
||||
|
||||
/**
|
||||
* Constructor of <code>ImageCache</code>
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
*/
|
||||
public ImageCache(final Context context) {
|
||||
init(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to create a singleton of {@link ImageCache}
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @return A new instance of this class.
|
||||
*/
|
||||
public final static ImageCache getInstance(final Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new ImageCache(context.getApplicationContext());
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the cache, providing all parameters.
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @param cacheParams The cache parameters to initialize the cache
|
||||
*/
|
||||
private void init(final Context context) {
|
||||
Utils.execute(false, new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Void... unused) {
|
||||
// Initialize the disk cahe in a background thread
|
||||
initDiskCache(context);
|
||||
return null;
|
||||
}
|
||||
}, (Void[])null);
|
||||
// Set up the memory cache
|
||||
initLruCache(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the disk cache. Note that this includes disk access so this
|
||||
* should not be executed on the main/UI thread. By default an ImageCache
|
||||
* does not initialize the disk cache when it is created, instead you should
|
||||
* call initDiskCache() to initialize it on a background thread.
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
*/
|
||||
private synchronized void initDiskCache(final Context context) {
|
||||
// Set up disk cache
|
||||
if (mDiskCache == null || mDiskCache.isClosed()) {
|
||||
File diskCacheDir = getDiskCacheDir(context, TAG);
|
||||
if (diskCacheDir != null) {
|
||||
if (!diskCacheDir.exists()) {
|
||||
diskCacheDir.mkdirs();
|
||||
}
|
||||
if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
|
||||
try {
|
||||
mDiskCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
|
||||
} catch (final IOException e) {
|
||||
diskCacheDir = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the Lru cache
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public void initLruCache(final Context context) {
|
||||
final ActivityManager activityManager = (ActivityManager)context
|
||||
.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
final int lruCacheSize = Math.round(MEM_CACHE_DIVIDER * activityManager.getMemoryClass()
|
||||
* 1024 * 1024);
|
||||
mLruCache = new MemoryCache(lruCacheSize);
|
||||
|
||||
// Release some memory as needed
|
||||
context.registerComponentCallbacks(new ComponentCallbacks2() {
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onTrimMemory(final int level) {
|
||||
if (level >= TRIM_MEMORY_MODERATE) {
|
||||
evictAll();
|
||||
} else if (level >= TRIM_MEMORY_BACKGROUND) {
|
||||
mLruCache.trimToSize(mLruCache.size() / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
// Nothing to do
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return an existing ImageCache stored in a {@link RetainFragment}
|
||||
* , if not found a new one is created using the supplied params and saved
|
||||
* to a {@link RetainFragment}
|
||||
*
|
||||
* @param activity The calling {@link FragmentActivity}
|
||||
* @return An existing retained ImageCache object or a new one if one did
|
||||
* not exist
|
||||
*/
|
||||
public static final ImageCache findOrCreateCache(final Activity activity) {
|
||||
|
||||
// Search for, or create an instance of the non-UI RetainFragment
|
||||
final RetainFragment retainFragment = findOrCreateRetainFragment(
|
||||
activity.getFragmentManager());
|
||||
|
||||
// See if we already have an ImageCache stored in RetainFragment
|
||||
ImageCache cache = (ImageCache)retainFragment.getObject();
|
||||
|
||||
// No existing ImageCache, create one and store it in RetainFragment
|
||||
if (cache == null) {
|
||||
cache = getInstance(activity);
|
||||
retainFragment.setObject(cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate an existing instance of this {@link Fragment} or if not found,
|
||||
* create and add it using {@link FragmentManager}
|
||||
*
|
||||
* @param fm The {@link FragmentManager} to use
|
||||
* @return The existing instance of the {@link Fragment} or the new instance
|
||||
* if just created
|
||||
*/
|
||||
public static final RetainFragment findOrCreateRetainFragment(final FragmentManager fm) {
|
||||
// Check to see if we have retained the worker fragment
|
||||
RetainFragment retainFragment = (RetainFragment)fm.findFragmentByTag(TAG);
|
||||
|
||||
// If not retained, we need to create and add it
|
||||
if (retainFragment == null) {
|
||||
retainFragment = new RetainFragment();
|
||||
fm.beginTransaction().add(retainFragment, TAG).commit();
|
||||
}
|
||||
return retainFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new image to the memory and disk caches
|
||||
*
|
||||
* @param data The key used to store the image
|
||||
* @param bitmap The {@link Bitmap} to cache
|
||||
*/
|
||||
public void addBitmapToCache(final String data, final Bitmap bitmap) {
|
||||
if (data == null || bitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to memory cache
|
||||
addBitmapToMemCache(data, bitmap);
|
||||
|
||||
// Add to disk cache
|
||||
if (mDiskCache != null) {
|
||||
final String key = hashKeyForDisk(data);
|
||||
OutputStream out = null;
|
||||
try {
|
||||
final DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
|
||||
if (snapshot == null) {
|
||||
final DiskLruCache.Editor editor = mDiskCache.edit(key);
|
||||
if (editor != null) {
|
||||
out = editor.newOutputStream(DISK_CACHE_INDEX);
|
||||
bitmap.compress(COMPRESS_FORMAT, COMPRESS_QUALITY, out);
|
||||
editor.commit();
|
||||
out.close();
|
||||
flush();
|
||||
}
|
||||
} else {
|
||||
snapshot.getInputStream(DISK_CACHE_INDEX).close();
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "addBitmapToCache - " + e);
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
out = null;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "addBitmapToCache - " + e);
|
||||
} catch (final IllegalStateException e) {
|
||||
Log.e(TAG, "addBitmapToCache - " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to add a new image to the memory cache
|
||||
*
|
||||
* @param data The key identifier
|
||||
* @param bitmap The {@link Bitmap} to cache
|
||||
*/
|
||||
public void addBitmapToMemCache(final String data, final Bitmap bitmap) {
|
||||
if (data == null || bitmap == null) {
|
||||
return;
|
||||
}
|
||||
// Add to memory cache
|
||||
if (getBitmapFromMemCache(data) == null) {
|
||||
mLruCache.put(data, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a cached image from the memory cache
|
||||
*
|
||||
* @param data Unique identifier for which item to get
|
||||
* @return The {@link Bitmap} if found in cache, null otherwise
|
||||
*/
|
||||
public final Bitmap getBitmapFromMemCache(final String data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (mLruCache != null) {
|
||||
final Bitmap lruBitmap = mLruCache.get(data);
|
||||
if (lruBitmap != null) {
|
||||
return lruBitmap;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a cached image from the disk cache
|
||||
*
|
||||
* @param data Unique identifier for which item to get
|
||||
* @return The {@link Bitmap} if found in cache, null otherwise
|
||||
*/
|
||||
public final Bitmap getBitmapFromDiskCache(final String data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check in the memory cache here to avoid going to the disk cache less
|
||||
// often
|
||||
if (getBitmapFromMemCache(data) != null) {
|
||||
return getBitmapFromMemCache(data);
|
||||
}
|
||||
|
||||
waitUntilUnpaused();
|
||||
final String key = hashKeyForDisk(data);
|
||||
if (mDiskCache != null) {
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
final DiskLruCache.Snapshot snapshot = mDiskCache.get(key);
|
||||
if (snapshot != null) {
|
||||
inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);
|
||||
if (inputStream != null) {
|
||||
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||
if (bitmap != null) {
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "getBitmapFromDiskCache - " + e);
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final int STATE_NONE = 0;
|
||||
public static final int STATE_FROM_MEMORY = 1;
|
||||
public static final int STATE_FROM_DISK = 2;
|
||||
|
||||
|
||||
/**
|
||||
* Tries to return a cached image from memory cache before fetching from the
|
||||
* disk cache
|
||||
*
|
||||
* @param data Unique identifier for which item to get
|
||||
* @return The {@link Bitmap} if found in cache, null otherwise
|
||||
*/
|
||||
public final Bitmap getCachedBitmap(final String data, int[] state) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
Bitmap cachedImage = getBitmapFromMemCache(data);
|
||||
if (cachedImage == null) {
|
||||
cachedImage = getBitmapFromDiskCache(data);
|
||||
if (cachedImage != null) {
|
||||
addBitmapToMemCache(data, cachedImage);
|
||||
if (state != null)
|
||||
state[0] = STATE_FROM_DISK;
|
||||
} else {
|
||||
if (state != null)
|
||||
state[0] = STATE_NONE;
|
||||
}
|
||||
} else {
|
||||
if (state != null)
|
||||
state[0] = STATE_FROM_MEMORY;
|
||||
}
|
||||
return cachedImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* flush() is called to synchronize up other methods that are accessing the
|
||||
* cache first
|
||||
*/
|
||||
public void flush() {
|
||||
Utils.execute(false, new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Void... unused) {
|
||||
if (mDiskCache != null) {
|
||||
try {
|
||||
if (!mDiskCache.isClosed()) {
|
||||
mDiskCache.flush();
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "flush - " + e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, (Void[])null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the disk and memory caches
|
||||
*/
|
||||
public void clearCaches() {
|
||||
Utils.execute(false, new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Void... unused) {
|
||||
// Clear the disk cache
|
||||
try {
|
||||
if (mDiskCache != null) {
|
||||
mDiskCache.delete();
|
||||
mDiskCache = null;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "clearCaches - " + e);
|
||||
}
|
||||
// Clear the memory cache
|
||||
evictAll();
|
||||
return null;
|
||||
}
|
||||
}, (Void[])null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the disk cache associated with this ImageCache object. Note that
|
||||
* this includes disk access so this should not be executed on the main/UI
|
||||
* thread.
|
||||
*/
|
||||
public void close() {
|
||||
Utils.execute(false, new AsyncTask<Void, Void, Void>() {
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(final Void... unused) {
|
||||
if (mDiskCache != null) {
|
||||
try {
|
||||
if (!mDiskCache.isClosed()) {
|
||||
mDiskCache.close();
|
||||
mDiskCache = null;
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "close - " + e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, (Void[])null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all of the items from the memory cache and lets the system know
|
||||
* now would be a good time to garbage collect
|
||||
*/
|
||||
public void evictAll() {
|
||||
if (mLruCache != null) {
|
||||
mLruCache.evictAll();
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key The key used to identify which cache entries to delete.
|
||||
*/
|
||||
public void removeFromCache(final String key) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
// Remove the Lru entry
|
||||
if (mLruCache != null) {
|
||||
mLruCache.remove(key);
|
||||
}
|
||||
|
||||
try {
|
||||
// Remove the disk entry
|
||||
if (mDiskCache != null) {
|
||||
mDiskCache.remove(hashKeyForDisk(key));
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
Log.e(TAG, "remove - " + e);
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to temporarily pause the disk cache while the user is scrolling to
|
||||
* improve scrolling.
|
||||
*
|
||||
* @param pause True to temporarily pause the disk cache, false otherwise.
|
||||
*/
|
||||
public void setPauseDiskCache(final boolean pause) {
|
||||
synchronized (mPauseLock) {
|
||||
if (mPauseDiskAccess != pause) {
|
||||
mPauseDiskAccess = pause;
|
||||
if (!pause) {
|
||||
mPauseLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void waitUntilUnpaused() {
|
||||
synchronized (mPauseLock) {
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
while (mPauseDiskAccess) {
|
||||
try {
|
||||
mPauseLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
// ignored, we'll start waiting again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the user is scrolling, false otherwise.
|
||||
*/
|
||||
public boolean isDiskCachePaused() {
|
||||
return mPauseDiskAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a usable cache directory (external if available, internal otherwise)
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @param uniqueName A unique directory name to append to the cache
|
||||
* directory
|
||||
* @return The cache directory
|
||||
*/
|
||||
public static final File getDiskCacheDir(final Context context, final String uniqueName) {
|
||||
// getExternalCacheDir(context) returns null if external storage is not ready
|
||||
final String cachePath = getExternalCacheDir(context) != null
|
||||
? getExternalCacheDir(context).getPath()
|
||||
: context.getCacheDir().getPath();
|
||||
return new File(cachePath, uniqueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if external storage is built-in or removable
|
||||
*
|
||||
* @return True if external storage is removable (like an SD card), false
|
||||
* otherwise
|
||||
*/
|
||||
public static final boolean isExternalStorageRemovable() {
|
||||
return Environment.isExternalStorageRemovable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the external app cache directory
|
||||
*
|
||||
* @param context The {@link Context} to use
|
||||
* @return The external cache directory
|
||||
*/
|
||||
public static final File getExternalCacheDir(final Context context) {
|
||||
return context.getExternalCacheDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check how much usable space is available at a given path.
|
||||
*
|
||||
* @param path The path to check
|
||||
* @return The space available in bytes
|
||||
*/
|
||||
public static final long getUsableSpace(final File path) {
|
||||
return path.getUsableSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* A hashing method that changes a string (like a URL) into a hash suitable
|
||||
* for using as a disk filename.
|
||||
*
|
||||
* @param key The key used to store the file
|
||||
*/
|
||||
public static final String hashKeyForDisk(final String key) {
|
||||
String cacheKey;
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(key.getBytes());
|
||||
cacheKey = bytesToHexString(digest.digest());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
cacheKey = String.valueOf(key.hashCode());
|
||||
}
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* http://stackoverflow.com/questions/332079
|
||||
*
|
||||
* @param bytes The bytes to convert.
|
||||
* @return A {@link String} converted from the bytes of a hashable key used
|
||||
* to store a filename on the disk, to hex digits.
|
||||
*/
|
||||
private static final String bytesToHexString(final byte[] bytes) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final byte b : bytes) {
|
||||
final String hex = Integer.toHexString(0xFF & b);
|
||||
if (hex.length() == 1) {
|
||||
builder.append('0');
|
||||
}
|
||||
builder.append(hex);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple non-UI Fragment that stores a single Object and is retained over
|
||||
* configuration changes. In this sample it will be used to retain an
|
||||
* {@link ImageCache} object.
|
||||
*/
|
||||
public static final class RetainFragment extends Fragment {
|
||||
|
||||
/**
|
||||
* The object to be stored
|
||||
*/
|
||||
private Object mObject;
|
||||
|
||||
/**
|
||||
* Empty constructor as per the {@link Fragment} documentation
|
||||
*/
|
||||
public RetainFragment() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Make sure this Fragment is retained over a configuration change
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a single object in this {@link Fragment}
|
||||
*
|
||||
* @param object The object to store
|
||||
*/
|
||||
public void setObject(final Object object) {
|
||||
mObject = object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored object
|
||||
*
|
||||
* @return The stored object
|
||||
*/
|
||||
public Object getObject() {
|
||||
return mObject;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cache images via {@link LruCache}.
|
||||
*/
|
||||
public static final class MemoryCache extends LruCache<String, Bitmap> {
|
||||
|
||||
/**
|
||||
* Constructor of <code>MemoryCache</code>
|
||||
*
|
||||
* @param maxSize The allowed size of the {@link LruCache}
|
||||
*/
|
||||
public MemoryCache(final int maxSize) {
|
||||
super(maxSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size in bytes of a bitmap.
|
||||
*/
|
||||
public static final int getBitmapSize(final Bitmap bitmap) {
|
||||
return bitmap.getByteCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected int sizeOf(final String paramString, final Bitmap paramBitmap) {
|
||||
return getBitmapSize(paramBitmap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project 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.cache;
|
||||
|
||||
// NOTE: upstream of this class is android.util.LruCache, changes below
|
||||
// expose trimToSize() to be called externally.
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Static library version of {@link android.util.LruCache}. Used to write apps
|
||||
* that run on API levels prior to 12. When running on API level 12 or above,
|
||||
* this implementation is still used; it does not try to switch to the
|
||||
* framework's implementation. See the framework SDK documentation for a class
|
||||
* overview.
|
||||
*/
|
||||
public class LruCache<K, V> {
|
||||
|
||||
private final LinkedHashMap<K, V> map;
|
||||
|
||||
private final int maxSize;
|
||||
|
||||
/** Size of this cache in units. Not necessarily the number of elements. */
|
||||
private int size;
|
||||
|
||||
private int putCount;
|
||||
|
||||
private int createCount;
|
||||
|
||||
private int evictionCount;
|
||||
|
||||
private int hitCount;
|
||||
|
||||
private int missCount;
|
||||
|
||||
/**
|
||||
* @param maxSize for caches that do not override {@link #sizeOf}, this is
|
||||
* the maximum number of entries in the cache. For all other
|
||||
* caches, this is the maximum sum of the sizes of the entries in
|
||||
* this cache.
|
||||
*/
|
||||
public LruCache(final int maxSize) {
|
||||
if (maxSize <= 0) {
|
||||
throw new IllegalArgumentException("maxSize <= 0");
|
||||
}
|
||||
this.maxSize = maxSize;
|
||||
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for {@code key} if it exists in the cache or can be
|
||||
* created by {@code #create}. If a value was returned, it is moved to the
|
||||
* head of the queue. This returns null if a value is not cached and cannot
|
||||
* be created.
|
||||
*/
|
||||
public final V get(final K key) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key == null");
|
||||
}
|
||||
|
||||
V mapValue;
|
||||
synchronized (this) {
|
||||
mapValue = map.get(key);
|
||||
if (mapValue != null) {
|
||||
this.hitCount++;
|
||||
return mapValue;
|
||||
}
|
||||
this.missCount++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to create a value. This may take a long time, and the map may
|
||||
* be different when create() returns. If a conflicting value was added
|
||||
* to the map while create() was working, we leave that value in the map
|
||||
* and release the created value.
|
||||
*/
|
||||
|
||||
final V createdValue = create(key);
|
||||
if (createdValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
this.createCount++;
|
||||
mapValue = map.put(key, createdValue);
|
||||
|
||||
if (mapValue != null) {
|
||||
/* There was a conflict so undo that last put */
|
||||
this.map.put(key, mapValue);
|
||||
} else {
|
||||
this.size += safeSizeOf(key, createdValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (mapValue != null) {
|
||||
entryRemoved(false, key, createdValue, mapValue);
|
||||
return mapValue;
|
||||
} else {
|
||||
trimToSize(maxSize);
|
||||
return createdValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches {@code value} for {@code key}. The value is moved to the head of
|
||||
* the queue.
|
||||
*
|
||||
* @return the previous value mapped by {@code key}.
|
||||
*/
|
||||
public final V put(final K key, final V value) {
|
||||
if (key == null || value == null) {
|
||||
throw new NullPointerException("key == null || value == null");
|
||||
}
|
||||
|
||||
V previous;
|
||||
synchronized (this) {
|
||||
this.putCount++;
|
||||
this.size += safeSizeOf(key, value);
|
||||
previous = this.map.put(key, value);
|
||||
if (previous != null) {
|
||||
this.size -= safeSizeOf(key, previous);
|
||||
}
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
entryRemoved(false, key, previous, value);
|
||||
}
|
||||
|
||||
trimToSize(maxSize);
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxSize the maximum size of the cache before returning. May be -1
|
||||
* to evict even 0-sized elements.
|
||||
*/
|
||||
public void trimToSize(final int maxSize) {
|
||||
while (true) {
|
||||
K key;
|
||||
V value;
|
||||
synchronized (this) {
|
||||
if (this.size < 0 || this.map.isEmpty() && size != 0) {
|
||||
throw new IllegalStateException(getClass().getName()
|
||||
+ ".sizeOf() is reporting inconsistent results!");
|
||||
}
|
||||
|
||||
if (this.size <= maxSize || this.map.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
final Map.Entry<K, V> toEvict = this.map.entrySet().iterator().next();
|
||||
key = toEvict.getKey();
|
||||
value = toEvict.getValue();
|
||||
this.map.remove(key);
|
||||
this.size -= safeSizeOf(key, value);
|
||||
this.evictionCount++;
|
||||
}
|
||||
|
||||
entryRemoved(true, key, value, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the entry for {@code key} if it exists.
|
||||
*
|
||||
* @return the previous value mapped by {@code key}.
|
||||
*/
|
||||
public final V remove(final K key) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key == null");
|
||||
}
|
||||
|
||||
V previous;
|
||||
synchronized (this) {
|
||||
previous = this.map.remove(key);
|
||||
if (previous != null) {
|
||||
this.size -= safeSizeOf(key, previous);
|
||||
}
|
||||
}
|
||||
|
||||
if (previous != null) {
|
||||
entryRemoved(false, key, previous, null);
|
||||
}
|
||||
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for entries that have been evicted or removed. This method is
|
||||
* invoked when a value is evicted to make space, removed by a call to
|
||||
* {@link #remove}, or replaced by a call to {@link #put}. The default
|
||||
* implementation does nothing.
|
||||
* <p>
|
||||
* The method is called without synchronization: other threads may access
|
||||
* the cache while this method is executing.
|
||||
*
|
||||
* @param evicted true if the entry is being removed to make space, false if
|
||||
* the removal was caused by a {@link #put} or {@link #remove}.
|
||||
* @param newValue the new value for {@code key}, if it exists. If non-null,
|
||||
* this removal was caused by a {@link #put}. Otherwise it was
|
||||
* caused by an eviction or a {@link #remove}.
|
||||
*/
|
||||
protected void entryRemoved(final boolean evicted, final K key, final V oldValue,
|
||||
final V newValue) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a cache miss to compute a value for the corresponding key.
|
||||
* Returns the computed value or null if no value can be computed. The
|
||||
* default implementation returns null.
|
||||
* <p>
|
||||
* The method is called without synchronization: other threads may access
|
||||
* the cache while this method is executing.
|
||||
* <p>
|
||||
* If a value for {@code key} exists in the cache when this method returns,
|
||||
* the created value will be released with {@link #entryRemoved} and
|
||||
* discarded. This can occur when multiple threads request the same key at
|
||||
* the same time (causing multiple values to be created), or when one thread
|
||||
* calls {@link #put} while another is creating a value for the same key.
|
||||
*/
|
||||
protected V create(final K key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private int safeSizeOf(final K key, final V value) {
|
||||
final int result = sizeOf(key, value);
|
||||
if (result < 0) {
|
||||
throw new IllegalStateException("Negative size: " + key + "=" + value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the entry for {@code key} and {@code value} in
|
||||
* user-defined units. The default implementation returns 1 so that size is
|
||||
* the number of entries and max size is the maximum number of entries.
|
||||
* <p>
|
||||
* An entry's size must not change while it is in the cache.
|
||||
*/
|
||||
protected int sizeOf(final K key, final V value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache, calling {@link #entryRemoved} on each removed entry.
|
||||
*/
|
||||
public final void evictAll() {
|
||||
trimToSize(-1); // -1 will evict 0-sized elements
|
||||
}
|
||||
|
||||
/**
|
||||
* For caches that do not override {@link #sizeOf}, this returns the number
|
||||
* of entries in the cache. For all other caches, this returns the sum of
|
||||
* the sizes of the entries in this cache.
|
||||
*/
|
||||
public synchronized final int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* For caches that do not override {@link #sizeOf}, this returns the maximum
|
||||
* number of entries in the cache. For all other caches, this returns the
|
||||
* maximum sum of the sizes of the entries in this cache.
|
||||
*/
|
||||
public synchronized final int maxSize() {
|
||||
return this.maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times {@link #get} returned a value.
|
||||
*/
|
||||
public synchronized final int hitCount() {
|
||||
return this.hitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times {@link #get} returned null or required a new
|
||||
* value to be created.
|
||||
*/
|
||||
public synchronized final int missCount() {
|
||||
return this.missCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times {@link #create(Object)} returned a value.
|
||||
*/
|
||||
public synchronized final int createCount() {
|
||||
return this.createCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times {@link #put} was called.
|
||||
*/
|
||||
public synchronized final int putCount() {
|
||||
return this.putCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of values that have been evicted.
|
||||
*/
|
||||
public synchronized final int evictionCount() {
|
||||
return this.evictionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the current contents of the cache, ordered from least
|
||||
* recently accessed to most recently accessed.
|
||||
*/
|
||||
public synchronized final Map<K, V> snapshot() {
|
||||
return new LinkedHashMap<K, V>(this.map);
|
||||
}
|
||||
|
||||
@SuppressLint("DefaultLocale")
|
||||
@Override
|
||||
public synchronized final String toString() {
|
||||
final int accesses = this.hitCount + this.missCount;
|
||||
final int hitPercent = accesses != 0 ? 100 * this.hitCount / accesses : 0;
|
||||
return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", this.maxSize,
|
||||
this.hitCount, this.missCount, hitPercent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cardview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* @author Hippo
|
||||
*
|
||||
* A salon to make views look like card
|
||||
*/
|
||||
public final class CardViewSalon {
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void reform(View view, int bgColor, int boundColor) {
|
||||
view.setBackgroundDrawable(new RoundRectDrawable(
|
||||
bgColor, boundColor));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void reform(View view, int[][] stateSets, int[] bgColors,
|
||||
int[] boundColors) {
|
||||
view.setBackgroundDrawable(new RoundRectDrawable(
|
||||
stateSets, bgColors, boundColors));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void reformWithShadow(View view, int bgColor, int boundColor,
|
||||
boolean keepPadding) {
|
||||
view.setBackgroundDrawable(new RoundRectDrawableWithShadow(
|
||||
bgColor, boundColor, keepPadding));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void reformWithShadow(View view, int[][] stateSets, int[] bgColors,
|
||||
int[] boundColors, boolean keepPadding) {
|
||||
view.setBackgroundDrawable(new RoundRectDrawableWithShadow(
|
||||
stateSets, bgColors, boundColors, keepPadding));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cardview;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.StateSet;
|
||||
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
|
||||
// Get most code from android.support.v7.widget.CardViewEclairMr1
|
||||
public class RoundRectDrawable extends Drawable {
|
||||
|
||||
protected final float mCornerRadius;
|
||||
private final int[][] mStateSets;
|
||||
private final int[] mBgColors;
|
||||
private final int[] mBoundColors;
|
||||
|
||||
private final Paint mBgPaint;
|
||||
private final Paint mBoundPaint;
|
||||
|
||||
private int mLastIndex = -1;
|
||||
protected final RectF mTempRectF = new RectF();
|
||||
|
||||
public RoundRectDrawable(int bgColor, int boundColor) {
|
||||
mCornerRadius = Ui.dp2pix(2);
|
||||
mStateSets = null;
|
||||
mBgColors = null;
|
||||
mBoundColors = null;
|
||||
|
||||
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mBgPaint.setColor(bgColor);
|
||||
if (boundColor == 0) {
|
||||
mBoundPaint = null;
|
||||
} else {
|
||||
mBoundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mBoundPaint.setColor(boundColor);
|
||||
}
|
||||
}
|
||||
|
||||
public RoundRectDrawable(int[][] stateSets, int[] bgColors, int[] boundColors) {
|
||||
mCornerRadius = Ui.dp2pix(2);
|
||||
mStateSets = stateSets;
|
||||
mBgColors = bgColors;
|
||||
mBoundColors = boundColors;
|
||||
|
||||
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
if (mBoundColors == null) {
|
||||
mBoundPaint = null;
|
||||
} else {
|
||||
mBoundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return mStateSets != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] stateSet) {
|
||||
final int N = mStateSets.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (StateSet.stateSetMatches(mStateSets[i], stateSet)) {
|
||||
if (mLastIndex == i)
|
||||
return false;
|
||||
mLastIndex = i;
|
||||
|
||||
mBgPaint.setColor(mBgColors[i]);
|
||||
if (mBoundColors != null)
|
||||
mBoundPaint.setColor(mBoundColors[i]);
|
||||
invalidateSelf();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
drawBody(canvas, new RectF(getBounds()));
|
||||
}
|
||||
|
||||
protected void drawBody(Canvas canvas, RectF bounds) {
|
||||
if (mBoundPaint == null && Build.VERSION.SDK_INT >= 17) {
|
||||
canvas.drawRoundRect(bounds, mCornerRadius, mCornerRadius, mBgPaint);
|
||||
|
||||
} else {
|
||||
final float twoRadius = mCornerRadius * 2;
|
||||
final float innerWidth = bounds.width() - twoRadius;
|
||||
final float innerHeight = bounds.height() - twoRadius;
|
||||
Paint drawBoundsPaint = mBoundPaint == null ? mBgPaint : mBoundPaint;
|
||||
|
||||
// Draw four corner
|
||||
mTempRectF.set(bounds.left, bounds.top,
|
||||
bounds.left + mCornerRadius * 2, bounds.top + mCornerRadius * 2);
|
||||
canvas.drawArc(mTempRectF, 180, 90, true, drawBoundsPaint);
|
||||
mTempRectF.offset(innerWidth, 0);
|
||||
canvas.drawArc(mTempRectF, 270, 90, true, drawBoundsPaint);
|
||||
mTempRectF.offset(0, innerHeight);
|
||||
canvas.drawArc(mTempRectF, 0, 90, true, drawBoundsPaint);
|
||||
mTempRectF.offset(-innerWidth, 0);
|
||||
canvas.drawArc(mTempRectF, 90, 90, true, drawBoundsPaint);
|
||||
|
||||
//draw top and bottom pieces
|
||||
canvas.drawRect(bounds.left + mCornerRadius, bounds.top,
|
||||
bounds.right - mCornerRadius, bounds.top + mCornerRadius,
|
||||
drawBoundsPaint);
|
||||
canvas.drawRect(bounds.left + mCornerRadius,
|
||||
bounds.bottom - mCornerRadius, bounds.right - mCornerRadius,
|
||||
bounds.bottom, drawBoundsPaint);
|
||||
|
||||
if (mBoundPaint == null) {
|
||||
// No bounds color, just draw center and left and right at same time
|
||||
canvas.drawRect(bounds.left, bounds.top + mCornerRadius,
|
||||
bounds.right, bounds.bottom - mCornerRadius, mBgPaint);
|
||||
} else {
|
||||
// Draw left and right pieces
|
||||
canvas.drawRect(bounds.left, bounds.top + mCornerRadius,
|
||||
bounds.left + mCornerRadius, bounds.bottom - mCornerRadius,
|
||||
drawBoundsPaint);
|
||||
canvas.drawRect(bounds.right - mCornerRadius, bounds.top + mCornerRadius,
|
||||
bounds.right, bounds.bottom - mCornerRadius, drawBoundsPaint);
|
||||
// Draw center
|
||||
canvas.drawRect(bounds.left + mCornerRadius, bounds.top + mCornerRadius,
|
||||
bounds.right - mCornerRadius, bounds.bottom - mCornerRadius, mBgPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.cardview;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.RadialGradient;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
|
||||
// Get most code from android.support.v7.widget.RoundRectDrawableWithShadow
|
||||
public class RoundRectDrawableWithShadow extends RoundRectDrawable {
|
||||
private final static float SHADOW_MULTIPLIER = 1.5f;
|
||||
|
||||
private int mShadowStartColor;
|
||||
private int mShadowEndColor;
|
||||
private float mShadowSize;
|
||||
|
||||
private Paint mCornerShadowPaint;
|
||||
private Paint mEdgeShadowPaint;
|
||||
|
||||
private final RectF mPreShadowBounds = new RectF();
|
||||
private final Path mCornerShadowPath = new Path();
|
||||
|
||||
private boolean mDirty = true;
|
||||
private final boolean mKeepPadding;
|
||||
|
||||
public RoundRectDrawableWithShadow(int bgColor, int boundColor,
|
||||
boolean keepPadding) {
|
||||
super(bgColor, boundColor);
|
||||
mKeepPadding = keepPadding;
|
||||
init();
|
||||
}
|
||||
|
||||
public RoundRectDrawableWithShadow(int[][] stateSets,
|
||||
int[] bgColors, int[] boundColors, boolean keepPadding) {
|
||||
super(stateSets, bgColors, boundColors);
|
||||
mKeepPadding = keepPadding;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mShadowStartColor = 0x37000000;
|
||||
mShadowEndColor = 0x03000000;
|
||||
mShadowSize = Ui.dp2pix(2) * SHADOW_MULTIPLIER;
|
||||
|
||||
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mCornerShadowPaint.setStyle(Paint.Style.FILL);
|
||||
mCornerShadowPaint.setDither(true);
|
||||
mEdgeShadowPaint = new Paint(mCornerShadowPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
mDirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPadding(Rect padding) {
|
||||
if (mKeepPadding) {
|
||||
return false;
|
||||
} else {
|
||||
padding.set(getSuggestionPadding());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Rect getSuggestionPadding() {
|
||||
Rect padding = new Rect();
|
||||
final int topShadow = (int) Math.ceil(mShadowSize * (1 / (SHADOW_MULTIPLIER * 2)));
|
||||
final int sideShadow = (int) Math.ceil(mShadowSize - topShadow);
|
||||
padding.set(sideShadow, topShadow, sideShadow, (int) Math.ceil(mShadowSize));
|
||||
return padding;
|
||||
}
|
||||
|
||||
private void buildShadowCorners() {
|
||||
RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
|
||||
RectF outerBounds = new RectF(innerBounds);
|
||||
outerBounds.inset(-mShadowSize, -mShadowSize);
|
||||
|
||||
mCornerShadowPath.reset();
|
||||
mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
|
||||
mCornerShadowPath.moveTo(-mCornerRadius, 0);
|
||||
mCornerShadowPath.rLineTo(-mShadowSize, 0);
|
||||
// outer arc
|
||||
mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
|
||||
// inner arc
|
||||
mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
|
||||
mCornerShadowPath.close();
|
||||
|
||||
float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
|
||||
mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
|
||||
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
|
||||
new float[]{0f, startRatio, 1f}
|
||||
, Shader.TileMode.CLAMP));
|
||||
|
||||
// we offset the content shadowSize/2 pixels up to make it more realistic.
|
||||
// this is why edge shadow shader has some extra space
|
||||
// When drawing bottom edge shadow, we use that extra space.
|
||||
mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
|
||||
-mCornerRadius - mShadowSize,
|
||||
new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
|
||||
new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
|
||||
}
|
||||
|
||||
private void buildComponents(Rect bounds) {
|
||||
mPreShadowBounds.set(bounds.left + mShadowSize, bounds.top + mShadowSize,
|
||||
bounds.right - mShadowSize, bounds.bottom - mShadowSize);
|
||||
buildShadowCorners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mDirty) {
|
||||
buildComponents(getBounds());
|
||||
mDirty = false;
|
||||
}
|
||||
drawShadow(canvas);
|
||||
final float offset = mShadowSize * (1 - 1 / (SHADOW_MULTIPLIER * 2));
|
||||
final float horizontalOffset = mShadowSize - offset;
|
||||
canvas.translate(0, -offset);
|
||||
mPreShadowBounds.bottom += offset;
|
||||
mPreShadowBounds.left -= horizontalOffset;
|
||||
mPreShadowBounds.right += horizontalOffset;
|
||||
drawBody(canvas, mPreShadowBounds);
|
||||
mPreShadowBounds.bottom -= offset;
|
||||
mPreShadowBounds.left += horizontalOffset;
|
||||
mPreShadowBounds.right -= horizontalOffset;
|
||||
canvas.translate(0, offset);
|
||||
}
|
||||
|
||||
private void drawShadow(Canvas canvas) {
|
||||
int saved = canvas.save();
|
||||
|
||||
float cornerPathSize = 2 * (mCornerRadius + mShadowSize);
|
||||
float edgeShadowTop = -mCornerRadius - mShadowSize;
|
||||
final Rect bounds = getBounds();
|
||||
|
||||
// LT
|
||||
canvas.translate(mPreShadowBounds.left + mCornerRadius,
|
||||
mPreShadowBounds.top + mCornerRadius);
|
||||
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
|
||||
canvas.drawRect(0, edgeShadowTop,
|
||||
bounds.right - cornerPathSize, -mCornerRadius,
|
||||
mEdgeShadowPaint);
|
||||
// RB
|
||||
canvas.rotate(180f);
|
||||
canvas.translate(-bounds.width() + cornerPathSize, -bounds.height() + cornerPathSize);
|
||||
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
|
||||
canvas.drawRect(0, edgeShadowTop,
|
||||
bounds.right - cornerPathSize, -mCornerRadius + mShadowSize,
|
||||
mEdgeShadowPaint);
|
||||
|
||||
// LB
|
||||
canvas.rotate(90f);
|
||||
canvas.translate(0, -bounds.width() + cornerPathSize);
|
||||
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
|
||||
canvas.drawRect(0, edgeShadowTop,
|
||||
bounds.bottom - cornerPathSize, -mCornerRadius,
|
||||
mEdgeShadowPaint);
|
||||
|
||||
// RT
|
||||
canvas.rotate(180f);
|
||||
canvas.translate(-bounds.height() + cornerPathSize, -bounds.width() + cornerPathSize);
|
||||
canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
|
||||
canvas.drawRect(0, edgeShadowTop,
|
||||
bounds.bottom - cornerPathSize, -mCornerRadius,
|
||||
mEdgeShadowPaint);
|
||||
|
||||
canvas.restoreToCount(saved);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ApiGalleryDetail extends ApiGalleryInfo
|
||||
implements LofiDetailImpl {
|
||||
public int previewPageNum;
|
||||
public int previewPerPage;
|
||||
private final List<PreviewList> previewLists = new ArrayList<PreviewList>();
|
||||
|
||||
public ApiGalleryDetail() {}
|
||||
|
||||
public ApiGalleryDetail(ApiGalleryInfo apiGalleryInfo) {
|
||||
gid = apiGalleryInfo.gid;
|
||||
token = apiGalleryInfo.token;
|
||||
title = apiGalleryInfo.title;
|
||||
posted = apiGalleryInfo.posted;
|
||||
category = apiGalleryInfo.category;
|
||||
thumb = apiGalleryInfo.thumb;
|
||||
uploader = apiGalleryInfo.uploader;
|
||||
rating = apiGalleryInfo.rating;
|
||||
simpleLanguage = apiGalleryInfo.simpleLanguage;
|
||||
archiver_key = apiGalleryInfo.archiver_key;
|
||||
title_jpn = apiGalleryInfo.title_jpn;
|
||||
filecount = apiGalleryInfo.filecount;
|
||||
filesize = apiGalleryInfo.filesize;
|
||||
expunged = apiGalleryInfo.expunged;
|
||||
torrentcount = apiGalleryInfo.torrentcount;
|
||||
apiTags = apiGalleryInfo.apiTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPageNum() {
|
||||
return previewPageNum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreviewPageNum(int pageNum) {
|
||||
previewPageNum = pageNum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPerPage() {
|
||||
return previewPerPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreviewList getPreview(int page) {
|
||||
if (page >= 0 && page < previewLists.size())
|
||||
return previewLists.get(page);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreview(int page, PreviewList previewList) {
|
||||
if (page >= 0 && page < previewLists.size())
|
||||
previewLists.set(page, previewList);
|
||||
else if (page == previewLists.size())
|
||||
previewLists.add(previewList);
|
||||
else
|
||||
; // Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTags() {
|
||||
return apiTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryInfo toGalleryInfo() {
|
||||
return new GalleryInfo(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
|
||||
public class ApiGalleryInfo extends GalleryInfo{
|
||||
public String archiver_key;
|
||||
public String title_jpn;
|
||||
public String filecount;
|
||||
public long filesize;
|
||||
public boolean expunged;
|
||||
public String torrentcount;
|
||||
public String[] apiTags;
|
||||
|
||||
public static final Parcelable.Creator<ApiGalleryInfo> CREATOR =
|
||||
new Parcelable.Creator<ApiGalleryInfo>() {
|
||||
@Override
|
||||
public ApiGalleryInfo createFromParcel(Parcel source) {
|
||||
ApiGalleryInfo p = new ApiGalleryInfo();
|
||||
p.gid = source.readInt();
|
||||
p.token = source.readString();
|
||||
p.title = source.readString();
|
||||
p.posted = source.readString();
|
||||
p.category = source.readInt();
|
||||
p.thumb = source.readString();
|
||||
p.uploader = source.readString();
|
||||
p.rating = source.readFloat();
|
||||
p.simpleLanguage = source.readString();
|
||||
p.archiver_key = source.readString();
|
||||
p.title_jpn = source.readString();
|
||||
p.filecount = source.readString();
|
||||
p.filesize = source.readLong();
|
||||
p.expunged = Utils.int2boolean(source.readInt());
|
||||
p.torrentcount = source.readString();
|
||||
int apiTagsLength = source.readInt();
|
||||
p.apiTags = new String[apiTagsLength];
|
||||
for (int i = 0; i < apiTagsLength; i++)
|
||||
p.apiTags[i] = source.readString();
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiGalleryInfo[] newArray(int size) {
|
||||
return new ApiGalleryInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeString(archiver_key);
|
||||
dest.writeString(title_jpn);
|
||||
dest.writeString(filecount);
|
||||
dest.writeLong(filesize);
|
||||
dest.writeInt(Utils.boolean2int(expunged));
|
||||
dest.writeString(torrentcount);
|
||||
dest.writeInt(apiTags.length);
|
||||
for (String str : apiTags)
|
||||
dest.writeString(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
public class Comment {
|
||||
public String time;
|
||||
public String user;
|
||||
public String comment;
|
||||
|
||||
public Comment (String time, String user, String comment) {
|
||||
this.time = time;
|
||||
this.user = user;
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,934 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.hippo.ehviewer.Analytics;
|
||||
import com.hippo.ehviewer.service.DownloadService;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
import com.hippo.ehviewer.util.SqlUtils;
|
||||
|
||||
// TODO
|
||||
// what a mess
|
||||
// refactoring
|
||||
|
||||
/**
|
||||
* @author Hippo
|
||||
*
|
||||
* Ver 2: 移除 read 表,添加 Download 表
|
||||
* Ver 3: Download 表添加下载时间项
|
||||
* Ver 4: 添历史记录
|
||||
*/
|
||||
public class Data {
|
||||
private static final String TAG = "Data";
|
||||
|
||||
private static final int VERSION = 4;
|
||||
private static final String DB_NAME = "data";
|
||||
|
||||
private static final String TABLE_GALLERY = "gallery";
|
||||
private static final String TABLE_LOCAL_FAVOURITE = "local_favourite";
|
||||
private static final String TABLE_TAG = "tag";
|
||||
private static final String TABLE_DOWNLOAD = "download";
|
||||
private static final String TABLE_HISTORY = "history";
|
||||
|
||||
private static final String COLUMN_GID = "gid";
|
||||
private static final String COLUMN_TOKEN = "token";
|
||||
private static final String COLUMN_TITLE = "title";
|
||||
private static final String COLUMN_POSTED = "posted";
|
||||
private static final String COLUMN_CATEGORY = "category";
|
||||
private static final String COLUMN_THUMB = "thumb";
|
||||
private static final String COLUMN_UPLOADER = "uploader";
|
||||
private static final String COLUMN_RATING = "rating";
|
||||
private static final String COLUMN_REFERENCE = "reference";
|
||||
|
||||
private static final String COLUMN_ID = "id";
|
||||
private static final String COLUMN_NAME = "name";
|
||||
private static final String COLUMN_MODE = "mode";
|
||||
private static final String COLUMN_SEARCH = "search";
|
||||
private static final String COLUMN_ADVANCE = "advance";
|
||||
private static final String COLUMN_MIN_RATING = "min_rating";
|
||||
private static final String COLUMN_TAG = "tag";
|
||||
|
||||
private static final String COLUMN_STATE = "state";
|
||||
private static final String COLUMN_LEGACY = "legacy";
|
||||
|
||||
private static final String COLUMN_DATE = "date";
|
||||
|
||||
private final SparseArray<GalleryInfo> mGallerys;
|
||||
|
||||
private long mTagRowNum;
|
||||
private List<Tag> mTags;
|
||||
private List<GalleryInfo> mLocalFavourites;
|
||||
private List<DownloadInfo> mDownloads;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final DBHelper mDBHelper;
|
||||
private final SQLiteDatabase mDatabase;
|
||||
|
||||
private static Data sInstance;
|
||||
|
||||
public static void createInstance(Context context) {
|
||||
sInstance = new Data(context);
|
||||
}
|
||||
|
||||
public static Data getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public Data(Context context) {
|
||||
mContext = context;
|
||||
mGallerys = new SparseArray<GalleryInfo>();
|
||||
|
||||
mDBHelper = new DBHelper(mContext);
|
||||
mDatabase = mDBHelper.getWritableDatabase();
|
||||
|
||||
getTags();
|
||||
getLocalFavourites();
|
||||
getDownloads();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalize() {
|
||||
mDBHelper.close();
|
||||
}
|
||||
|
||||
/****** gallery ******/
|
||||
|
||||
/**
|
||||
* Get galleryInfo in table, if do not exits return null
|
||||
*
|
||||
* @param gid
|
||||
* @return
|
||||
*/
|
||||
public GalleryInfo getGallery(SQLiteDatabase db, int gid) {
|
||||
GalleryInfo galleryInfo = mGallerys.get(gid);
|
||||
if (galleryInfo == null) {
|
||||
Cursor cursor = db.rawQuery("select * from " + TABLE_GALLERY + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst()) {
|
||||
galleryInfo = new GalleryInfo();
|
||||
galleryInfo.gid = gid;
|
||||
galleryInfo.token = cursor.getString(1);
|
||||
galleryInfo.title = cursor.getString(2);
|
||||
galleryInfo.posted = cursor.getString(3);
|
||||
galleryInfo.category = cursor.getInt(4);
|
||||
galleryInfo.thumb = cursor.getString(5);
|
||||
galleryInfo.uploader = cursor.getString(6);
|
||||
galleryInfo.rating = cursor.getFloat(7);
|
||||
galleryInfo.generateSLang();
|
||||
|
||||
// add to map
|
||||
mGallerys.put(gid, galleryInfo);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return galleryInfo;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private synchronized boolean containsGallery(int gid) {
|
||||
boolean re = false;
|
||||
Cursor cursor = mDatabase.rawQuery("select * from "
|
||||
+ TABLE_GALLERY + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst())
|
||||
re = true;
|
||||
cursor.close();
|
||||
return re;
|
||||
}
|
||||
|
||||
/**
|
||||
* If do not contain the gallery return -1;
|
||||
* @param gid
|
||||
* @return
|
||||
*/
|
||||
private synchronized int getGalleryReference(int gid) {
|
||||
int reference = -1;
|
||||
Cursor cursor = mDatabase.rawQuery("select " + COLUMN_REFERENCE + " from "
|
||||
+ TABLE_GALLERY + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst()) {
|
||||
reference = cursor.getInt(0);
|
||||
}
|
||||
cursor.close();
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* If exits same gid, add the reference by 1.
|
||||
* If exits no same gid, add it to table reference is 1
|
||||
*
|
||||
* @param galleryInfo
|
||||
*/
|
||||
private void addGallery(GalleryInfo galleryInfo) {
|
||||
boolean isInsert;
|
||||
int gid = galleryInfo.gid;
|
||||
// add to map
|
||||
mGallerys.put(gid, galleryInfo);
|
||||
|
||||
// add to sql or update and add reference by 1
|
||||
int reference;
|
||||
if ((reference = getGalleryReference(gid)) != -1) {
|
||||
isInsert = false;
|
||||
reference++;
|
||||
}
|
||||
else {
|
||||
isInsert = true;
|
||||
reference = 1;
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, gid);
|
||||
values.put(COLUMN_TOKEN, galleryInfo.token);
|
||||
values.put(COLUMN_TITLE, galleryInfo.title);
|
||||
values.put(COLUMN_POSTED, galleryInfo.posted);
|
||||
values.put(COLUMN_CATEGORY, galleryInfo.category);
|
||||
values.put(COLUMN_THUMB, galleryInfo.thumb);
|
||||
values.put(COLUMN_UPLOADER, galleryInfo.uploader);
|
||||
values.put(COLUMN_RATING, galleryInfo.rating);
|
||||
values.put(COLUMN_REFERENCE, reference);
|
||||
|
||||
if (isInsert) { // Set
|
||||
mDatabase.insert(TABLE_GALLERY, null, values);
|
||||
} else { // Update
|
||||
mDatabase.update(TABLE_GALLERY, values, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if update
|
||||
*
|
||||
* @param gid
|
||||
* @return
|
||||
*/
|
||||
private boolean updateGallery(GalleryInfo galleryInfo) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, galleryInfo.gid);
|
||||
values.put(COLUMN_TOKEN, galleryInfo.token);
|
||||
values.put(COLUMN_TITLE, galleryInfo.title);
|
||||
values.put(COLUMN_POSTED, galleryInfo.posted);
|
||||
values.put(COLUMN_CATEGORY, galleryInfo.category);
|
||||
values.put(COLUMN_THUMB, galleryInfo.thumb);
|
||||
values.put(COLUMN_UPLOADER, galleryInfo.uploader);
|
||||
values.put(COLUMN_RATING, galleryInfo.rating);
|
||||
|
||||
return mDatabase.update(TABLE_GALLERY, values,
|
||||
COLUMN_GID + "=?", new String[]{String.valueOf(galleryInfo.gid)}) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrease reference by 1, only delete when reference is 0
|
||||
* @param gid
|
||||
*/
|
||||
private boolean deleteGallery(int gid) {
|
||||
int reference = getGalleryReference(gid);
|
||||
if (reference == -1) {
|
||||
Log.w(TAG, "Can't get reference when deleteGallery, gid is " + gid);
|
||||
return false;
|
||||
}
|
||||
reference--;
|
||||
if (reference == 0) { // delete gallery
|
||||
// delete in sql
|
||||
mDatabase.delete(TABLE_GALLERY, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
// delete in list
|
||||
mGallerys.remove(gid);
|
||||
} else { // update reference
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_REFERENCE, reference);
|
||||
mDatabase.update(TABLE_GALLERY, values, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/****** download ******/
|
||||
private synchronized void getDownloads() {
|
||||
boolean startService = false;
|
||||
boolean keepDownloadService = Config.getKeepDownloadService();
|
||||
mDownloads = new ArrayList<DownloadInfo>();
|
||||
|
||||
Cursor cursor = mDatabase.rawQuery("select * from " + TABLE_DOWNLOAD
|
||||
+ " order by " + COLUMN_DATE + " asc", null);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
|
||||
int gid = cursor.getInt(0);
|
||||
GalleryInfo galleryInfo = getGallery(mDatabase, gid);
|
||||
if (galleryInfo == null) {
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
int mode = cursor.getInt(1);
|
||||
int state = cursor.getInt(2);
|
||||
int legacy = cursor.getInt(3);
|
||||
if (state == DownloadInfo.STATE_WAIT || state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
if (keepDownloadService) {
|
||||
state = DownloadInfo.STATE_WAIT;
|
||||
startService = true;
|
||||
} else {
|
||||
state = DownloadInfo.STATE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
DownloadInfo downloadInfo = new DownloadInfo(
|
||||
galleryInfo, mode, state, legacy);
|
||||
mDownloads.add(downloadInfo);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
if (startService) {
|
||||
Intent it = new Intent(mContext, DownloadService.class);
|
||||
mContext.startService(it);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null if not found
|
||||
* @return
|
||||
*/
|
||||
public synchronized DownloadInfo getFirstWaitDownloadInfo() {
|
||||
for (DownloadInfo di : mDownloads) {
|
||||
if (di.state == DownloadInfo.STATE_WAIT)
|
||||
return di;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if contain this item
|
||||
*
|
||||
* @param gid
|
||||
* @return
|
||||
*/
|
||||
public synchronized boolean containsDownload(int gid) {
|
||||
boolean re = false;
|
||||
Cursor cursor = mDatabase.rawQuery("select * from "
|
||||
+ TABLE_DOWNLOAD + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst())
|
||||
re = true;
|
||||
cursor.close();
|
||||
return re;
|
||||
}
|
||||
|
||||
/**
|
||||
* If exits, just update
|
||||
*
|
||||
* @param downloadInfo
|
||||
* @return
|
||||
*/
|
||||
public synchronized boolean addDownload(DownloadInfo downloadInfo) {
|
||||
GalleryInfo galleryInfo = downloadInfo.galleryInfo;
|
||||
int gid = galleryInfo.gid;
|
||||
|
||||
// Add to download
|
||||
boolean update;
|
||||
if (containsDownload(gid)) {
|
||||
update = true;
|
||||
} else {
|
||||
update = false;
|
||||
addGallery(galleryInfo);
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, gid);
|
||||
values.put(COLUMN_MODE, downloadInfo.mode);
|
||||
values.put(COLUMN_STATE, downloadInfo.state);
|
||||
values.put(COLUMN_LEGACY, downloadInfo.legacy);
|
||||
|
||||
if (update) {
|
||||
mDatabase.update(TABLE_DOWNLOAD, values,
|
||||
COLUMN_GID + "=?", new String[]{String.valueOf(galleryInfo.gid)});
|
||||
return false;
|
||||
} else {
|
||||
// If not update, set date
|
||||
values.put(COLUMN_DATE, System.currentTimeMillis());
|
||||
|
||||
if (mDatabase.insert(TABLE_DOWNLOAD, null,
|
||||
values) != -1) { // If add ok
|
||||
mDownloads.add(downloadInfo);
|
||||
return true;
|
||||
} else { // If add fail
|
||||
deleteGallery(gid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean deleteDownload(int gid) {
|
||||
int deleteNum = mDatabase.delete(TABLE_DOWNLOAD, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
if (deleteNum > 1)
|
||||
Log.w(TAG, "WTF? more than one gid is " + gid);
|
||||
|
||||
if (deleteNum == 0)
|
||||
return false;
|
||||
|
||||
// delete from list
|
||||
for (DownloadInfo di : mDownloads) {
|
||||
if (gid == di.galleryInfo.gid) {
|
||||
mDownloads.remove(di);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// sub reference
|
||||
deleteGallery(gid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized List<DownloadInfo> getAllDownloads() {
|
||||
return mDownloads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null if not found
|
||||
*
|
||||
* @param gid
|
||||
* @return
|
||||
*/
|
||||
public synchronized DownloadInfo getDownload(int gid) {
|
||||
for (DownloadInfo di : mDownloads) {
|
||||
if (gid == di.galleryInfo.gid)
|
||||
return di;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized void setDownloadState(int gid, int state) {
|
||||
setDownloadState(getDownload(gid), state);
|
||||
}
|
||||
|
||||
public synchronized void setDownloadState(DownloadInfo di, int state) {
|
||||
if (di == null || di.state == state)
|
||||
return;
|
||||
|
||||
di.state = state;
|
||||
// Update in db
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_STATE, state);
|
||||
mDatabase.update(TABLE_DOWNLOAD, values,
|
||||
COLUMN_GID + "=?", new String[]{String.valueOf(di.galleryInfo.gid)});
|
||||
}
|
||||
|
||||
/**
|
||||
* If state is STATE_NONE or state is STATE_FINISH and legacy is not 0,
|
||||
* make state STATE_WAIT
|
||||
*/
|
||||
public synchronized void startAllDownload() {
|
||||
for (DownloadInfo di : mDownloads) {
|
||||
if (di.state == DownloadInfo.STATE_NONE ||
|
||||
(di.state == DownloadInfo.STATE_FINISH && di.legacy != 0)) {
|
||||
setDownloadState(di, DownloadInfo.STATE_WAIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If state is STATE_WAIT or STATE_DOWNLOAD,
|
||||
* make state STATE_NONE
|
||||
*/
|
||||
public synchronized void stopAllDownload() {
|
||||
for (DownloadInfo di : mDownloads) {
|
||||
if (di.state == DownloadInfo.STATE_WAIT ||
|
||||
di.state == DownloadInfo.STATE_DOWNLOAD) {
|
||||
setDownloadState(di, DownloadInfo.STATE_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****** local favourite ******/
|
||||
private synchronized void getLocalFavourites() {
|
||||
mLocalFavourites = new ArrayList<GalleryInfo>();
|
||||
Cursor cursor = mDatabase.rawQuery("select * from "
|
||||
+ TABLE_LOCAL_FAVOURITE, null);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
int gid = cursor.getInt(0);
|
||||
GalleryInfo galleryInfo = getGallery(mDatabase, gid);
|
||||
if (galleryInfo != null)
|
||||
mLocalFavourites.add(galleryInfo);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public synchronized boolean containsLocalFavourite(int gid) {
|
||||
boolean re = false;
|
||||
Cursor cursor = mDatabase.rawQuery("select * from "
|
||||
+ TABLE_LOCAL_FAVOURITE + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst())
|
||||
re = true;
|
||||
cursor.close();
|
||||
return re;
|
||||
}
|
||||
|
||||
/**
|
||||
* galleryInfo can't be null
|
||||
* @param galleryInfo
|
||||
*/
|
||||
public synchronized void addLocalFavourite(GalleryInfo galleryInfo) {
|
||||
int gid = galleryInfo.gid;
|
||||
|
||||
if (containsLocalFavourite(gid)) {
|
||||
updateGallery(galleryInfo);
|
||||
} else {
|
||||
addGallery(galleryInfo);
|
||||
// add to sql
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, galleryInfo.gid);
|
||||
mDatabase.insert(TABLE_LOCAL_FAVOURITE, null, values);
|
||||
// add to list
|
||||
mLocalFavourites.add(galleryInfo);
|
||||
|
||||
// Analytics
|
||||
Analytics.addToFavoriteGallery(mContext, gid, galleryInfo.token);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deleteLocalFavourite(int gid) {
|
||||
// delete from list
|
||||
for (GalleryInfo galleryInfo : mLocalFavourites) {
|
||||
if (galleryInfo.gid == gid) {
|
||||
mLocalFavourites.remove(galleryInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// delete from sql
|
||||
mDatabase.delete(TABLE_LOCAL_FAVOURITE, COLUMN_GID + "=?", new String[]{String.valueOf(gid)});
|
||||
deleteGallery(gid);
|
||||
}
|
||||
|
||||
public synchronized List<GalleryInfo> getAllLocalFavourites() {
|
||||
return mLocalFavourites;
|
||||
}
|
||||
|
||||
/****** tag ******/
|
||||
private synchronized void getTags() {
|
||||
mTags = new ArrayList<Tag>();
|
||||
mTagRowNum = 0;
|
||||
Cursor cursor = mDatabase.rawQuery("select * from " + TABLE_TAG
|
||||
+ " order by " + COLUMN_ID + " asc", null);
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
|
||||
String name = cursor.getString(1);
|
||||
int mode = cursor.getInt(2);
|
||||
int category = cursor.getInt(3);
|
||||
String search = cursor.getString(4);
|
||||
int advance = cursor.getInt(5);
|
||||
int min_rating = cursor.getInt(6);
|
||||
String tagStr = cursor.getString(7);
|
||||
|
||||
Tag tag = new Tag(name, category, search);
|
||||
tag.setAdvance(advance, min_rating);
|
||||
tag.setTag(tagStr);
|
||||
tag.setMode(mode);
|
||||
|
||||
mTags.add(tag);
|
||||
|
||||
cursor.moveToNext();
|
||||
mTagRowNum++;
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
public synchronized boolean addTag(Tag tag) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ID, mTagRowNum);
|
||||
values.put(COLUMN_NAME, tag.getName());
|
||||
values.put(COLUMN_MODE, tag.getMode());
|
||||
values.put(COLUMN_CATEGORY, tag.getCategory());
|
||||
values.put(COLUMN_SEARCH, tag.getSearch());
|
||||
values.put(COLUMN_ADVANCE, tag.getAdvanceType());
|
||||
values.put(COLUMN_MIN_RATING, tag.getMinRating());
|
||||
values.put(COLUMN_TAG, tag.getTag());
|
||||
|
||||
boolean ok = mDatabase.insert(
|
||||
TABLE_TAG, null, values) != -1;
|
||||
if (ok) {
|
||||
mTagRowNum++;
|
||||
mTags.add(tag);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
public synchronized boolean deleteTag(int id) {
|
||||
int deleteNum = mDatabase.delete(TABLE_TAG, COLUMN_ID + "=?", new String[]{String.valueOf(id)});
|
||||
if (deleteNum > 1)
|
||||
Log.e(TAG, "WTF? more than one id is " + id);
|
||||
|
||||
if (deleteNum == 0)
|
||||
return false;
|
||||
if (id < 0 || id >= mTags.size()) {
|
||||
Log.e(TAG, "id is out of bounds, id is " + id + ", mTags.size() is " + mTags.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update sql
|
||||
ContentValues values = new ContentValues();
|
||||
for (int i = id + 1; i < mTagRowNum; i++) {
|
||||
values.put(COLUMN_ID, i - 1);
|
||||
mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(i)});
|
||||
}
|
||||
mTagRowNum -= deleteNum;
|
||||
|
||||
// Update list
|
||||
mTags.remove(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized List<Tag> getAllTags() {
|
||||
return mTags;
|
||||
}
|
||||
|
||||
public synchronized Tag getTag(int location) {
|
||||
return mTags.get(location);
|
||||
}
|
||||
|
||||
public synchronized List<String> getAllTagNames() {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (Tag tag : mTags)
|
||||
names.add(tag.getName());
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* location >= 0 && location <= tagRowNum please
|
||||
* @param location
|
||||
* @param tag
|
||||
*/
|
||||
public synchronized void setTag(int location, Tag tag) {
|
||||
if (location == mTagRowNum) {
|
||||
addTag(tag);
|
||||
} else {
|
||||
// Update list
|
||||
mTags.set(location, tag);
|
||||
|
||||
// Update sql
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_NAME, tag.getName());
|
||||
values.put(COLUMN_CATEGORY, tag.getCategory());
|
||||
values.put(COLUMN_SEARCH, tag.getSearch());
|
||||
values.put(COLUMN_ADVANCE, tag.getAdvanceType());
|
||||
values.put(COLUMN_MIN_RATING, tag.getMinRating());
|
||||
mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(location)});
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean swapTag(int indexOne, int indexTwo) {
|
||||
if (indexOne < 0 || indexOne > mTagRowNum
|
||||
|| indexTwo < 0 || indexTwo > mTagRowNum)
|
||||
return false;
|
||||
|
||||
if (indexOne == indexTwo)
|
||||
return true;
|
||||
|
||||
// Update list
|
||||
Tag temp = mTags.get(indexOne);
|
||||
mTags.set(indexOne, mTags.get(indexTwo));
|
||||
mTags.set(indexTwo, temp);
|
||||
// Update sql
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ID, -1);
|
||||
mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(indexOne)});
|
||||
values.put(COLUMN_ID, indexOne);
|
||||
mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(indexTwo)});
|
||||
values.put(COLUMN_ID, indexTwo);
|
||||
mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(-1)});
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final int NONE = 0x0;
|
||||
public static final int BROWSE = 0x1;
|
||||
public static final int READ = 0x2;
|
||||
|
||||
public synchronized List<HistoryItem> getHistory(int mode, boolean desc) {
|
||||
List<HistoryItem> history = new ArrayList<HistoryItem>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("select * from ").append(TABLE_HISTORY);
|
||||
boolean first = true;
|
||||
if (mode != 0) {
|
||||
sb.append(" where " + COLUMN_MODE + " in (");
|
||||
for (int b = 1; b <= READ; b <<= 1) {
|
||||
if ((mode & b) != 0) {
|
||||
sb.append(first ? "" : ", ").append(b);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
sb.append(")");
|
||||
}
|
||||
sb.append(" order by ").append(COLUMN_DATE).append(desc ? " desc" : " asc");
|
||||
|
||||
Log.d(TAG, sb.toString());
|
||||
|
||||
Cursor cursor = mDatabase.rawQuery(sb.toString(), null);
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
int gid = cursor.getInt(0);
|
||||
GalleryInfo gi = getGallery(mDatabase, gid);
|
||||
if (gi != null) {
|
||||
history.add(new HistoryItem(gi, cursor.getInt(1), cursor.getLong(2)));
|
||||
}
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
public synchronized int containsHistory(int gid) {
|
||||
int re;
|
||||
Cursor cursor = mDatabase.rawQuery("select * from "
|
||||
+ TABLE_HISTORY + " where " + COLUMN_GID + "=?",
|
||||
new String[]{String.valueOf(gid)});
|
||||
if (cursor.moveToFirst())
|
||||
re = cursor.getInt(1);
|
||||
else
|
||||
re = NONE;
|
||||
cursor.close();
|
||||
return re;
|
||||
}
|
||||
|
||||
private int checkMode(int mode) {
|
||||
return MathUtils.clamp(mode, NONE, READ);
|
||||
}
|
||||
|
||||
public synchronized void addHistory(GalleryInfo gi, int mode) {
|
||||
mode = checkMode(mode);
|
||||
|
||||
long curDate = System.currentTimeMillis();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, gi.gid);
|
||||
values.put(COLUMN_DATE, curDate);
|
||||
|
||||
int curMode = checkMode(containsHistory(gi.gid));
|
||||
if (curMode == NONE) {
|
||||
// Add new record
|
||||
values.put(COLUMN_MODE, mode);
|
||||
mDatabase.insert(TABLE_HISTORY, null, values);
|
||||
addGallery(gi);
|
||||
|
||||
truncateHistory(Config.getMaxHistoryCount());
|
||||
} else {
|
||||
// Update record
|
||||
int newMode = Math.max(curMode, mode);
|
||||
values.put(COLUMN_MODE, newMode);
|
||||
mDatabase.update(TABLE_HISTORY, values,
|
||||
COLUMN_GID + "=?", new String[]{String.valueOf(gi.gid)});
|
||||
updateGallery(gi);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearHistory() {
|
||||
truncateHistory(0);
|
||||
}
|
||||
|
||||
protected synchronized void truncateHistory(int maxEntries) {
|
||||
if (maxEntries < 0) {
|
||||
return;
|
||||
} else if (maxEntries == 0) {
|
||||
mDatabase.delete(TABLE_HISTORY, null, null);
|
||||
} else {
|
||||
try {
|
||||
mDatabase.execSQL("delete from " + TABLE_HISTORY + " where " + COLUMN_GID
|
||||
+ " in " + "(select " + COLUMN_GID + " from " + TABLE_HISTORY
|
||||
+ " order by " + COLUMN_DATE + " desc limit " + maxEntries
|
||||
+ ", -1);");
|
||||
} catch(Throwable e){}
|
||||
}
|
||||
}
|
||||
|
||||
public class DBHelper extends SQLiteOpenHelper {
|
||||
|
||||
public DBHelper(Context context) {
|
||||
super(context, DB_NAME, null, VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
// gallery
|
||||
String CreateGallery = "create table " + TABLE_GALLERY + "("
|
||||
+ COLUMN_GID + " integer primary key,"
|
||||
+ COLUMN_TOKEN + " text,"
|
||||
+ COLUMN_TITLE + " text,"
|
||||
+ COLUMN_POSTED + " text,"
|
||||
+ COLUMN_CATEGORY + " integer,"
|
||||
+ COLUMN_THUMB + " text,"
|
||||
+ COLUMN_UPLOADER + " text,"
|
||||
+ COLUMN_RATING + " float,"
|
||||
+ COLUMN_REFERENCE + " integer);";
|
||||
db.execSQL(CreateGallery);
|
||||
|
||||
// local favourite
|
||||
String CreateLocalFavourite = "create table "
|
||||
+ TABLE_LOCAL_FAVOURITE + "("
|
||||
+ COLUMN_GID + " integer primary key,"
|
||||
+ "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));";
|
||||
db.execSQL(CreateLocalFavourite);
|
||||
|
||||
// tag
|
||||
String CreateTag = "create table "
|
||||
+ TABLE_TAG + "("
|
||||
+ COLUMN_ID + " integer primary key,"
|
||||
+ COLUMN_NAME + " text,"
|
||||
+ COLUMN_MODE + " integer,"
|
||||
+ COLUMN_CATEGORY + " integer,"
|
||||
+ COLUMN_SEARCH + " text,"
|
||||
+ COLUMN_ADVANCE + " integer,"
|
||||
+ COLUMN_MIN_RATING + " integer,"
|
||||
+ COLUMN_TAG + " text);";
|
||||
db.execSQL(CreateTag);
|
||||
|
||||
// download
|
||||
createDownloadTableVer3(db);
|
||||
|
||||
// History
|
||||
createHistoryTable(db);
|
||||
}
|
||||
|
||||
private void createDownloadTableVer2(SQLiteDatabase db) {
|
||||
String createDownload = "create table "
|
||||
+ TABLE_DOWNLOAD + "("
|
||||
+ COLUMN_GID + " integer primary key,"
|
||||
+ COLUMN_MODE + " integer,"
|
||||
+ COLUMN_STATE + " integer,"
|
||||
+ COLUMN_LEGACY + " integer,"
|
||||
+ "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));";
|
||||
|
||||
SqlUtils.exeSQLSafely(db, createDownload);
|
||||
}
|
||||
|
||||
private synchronized List<DownloadInfo> getDownloadsVer2(SQLiteDatabase db) {
|
||||
List<DownloadInfo> list = new ArrayList<DownloadInfo>();
|
||||
|
||||
Cursor cursor = db.rawQuery("select * from " + TABLE_DOWNLOAD,
|
||||
null);
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
while (!cursor.isAfterLast()) {
|
||||
|
||||
int gid = cursor.getInt(0);
|
||||
GalleryInfo galleryInfo = getGallery(db, gid);
|
||||
if (galleryInfo == null) {
|
||||
cursor.moveToNext();
|
||||
continue;
|
||||
}
|
||||
int mode = cursor.getInt(1);
|
||||
int state = cursor.getInt(2);
|
||||
int legacy = cursor.getInt(3);
|
||||
if (state == DownloadInfo.STATE_WAIT || state == DownloadInfo.STATE_DOWNLOAD)
|
||||
state = DownloadInfo.STATE_NONE;
|
||||
|
||||
DownloadInfo downloadInfo = new DownloadInfo(
|
||||
galleryInfo, mode, state, legacy);
|
||||
list.add(downloadInfo);
|
||||
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void createDownloadTableVer3(SQLiteDatabase db) {
|
||||
String createDownload = "create table "
|
||||
+ TABLE_DOWNLOAD + "("
|
||||
+ COLUMN_GID + " integer primary key,"
|
||||
+ COLUMN_MODE + " integer,"
|
||||
+ COLUMN_STATE + " integer,"
|
||||
+ COLUMN_LEGACY + " integer,"
|
||||
+ COLUMN_DATE + " long,"
|
||||
+ "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));";
|
||||
|
||||
SqlUtils.exeSQLSafely(db, createDownload);
|
||||
}
|
||||
|
||||
public void addDownloadInfoVer3(SQLiteDatabase db, List<DownloadInfo> l) {
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
DownloadInfo di = l.get(i);
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_GID, di.galleryInfo.gid);
|
||||
values.put(COLUMN_MODE, di.mode);
|
||||
values.put(COLUMN_STATE, di.state);
|
||||
values.put(COLUMN_LEGACY, di.legacy);
|
||||
values.put(COLUMN_DATE, (long) i);
|
||||
db.insert(TABLE_DOWNLOAD, null, values);
|
||||
}
|
||||
}
|
||||
|
||||
private void createHistoryTable(SQLiteDatabase db) {
|
||||
String createDownload = "create table "
|
||||
+ TABLE_HISTORY + "("
|
||||
+ COLUMN_GID + " integer primary key,"
|
||||
+ COLUMN_MODE + " integer,"
|
||||
+ COLUMN_DATE + " long,"
|
||||
+ "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));";
|
||||
|
||||
SqlUtils.exeSQLSafely(db, createDownload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
switch (oldVersion) {
|
||||
case 1:
|
||||
SqlUtils.dropTable(db, TABLE_DOWNLOAD);
|
||||
SqlUtils.dropTable(db, "read");
|
||||
createDownloadTableVer2(db);
|
||||
case 2:
|
||||
List<DownloadInfo> l = getDownloadsVer2(db);
|
||||
SqlUtils.dropTable(db, TABLE_DOWNLOAD);
|
||||
createDownloadTableVer3(db);
|
||||
addDownloadInfoVer3(db, l);
|
||||
case 3:
|
||||
createHistoryTable(db);
|
||||
case VERSION:
|
||||
break;
|
||||
default:
|
||||
SqlUtils.dropAllTable(db);
|
||||
onCreate(db);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import com.hippo.ehviewer.widget.ExpandableListItem;
|
||||
|
||||
|
||||
public class DownloadInfo implements ExpandableListItem {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DownloadInfo.class.getSimpleName();
|
||||
|
||||
public static final int STATE_NONE = 0x0;
|
||||
public static final int STATE_WAIT = 0x1;
|
||||
public static final int STATE_DOWNLOAD = 0x2;
|
||||
public static final int STATE_FINISH = 0x3;
|
||||
|
||||
public final GalleryInfo galleryInfo;
|
||||
public final int mode;
|
||||
public volatile int state = STATE_NONE;
|
||||
/** byte/second **/
|
||||
public volatile int speed;
|
||||
public volatile int download = -1;
|
||||
public volatile int total = -1;
|
||||
public volatile int legacy;
|
||||
|
||||
private boolean mExpanded = false;
|
||||
|
||||
public DownloadInfo(GalleryInfo gi, int mode) {
|
||||
this.galleryInfo = gi;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public DownloadInfo(GalleryInfo gi, int mode, int state, int legacy) {
|
||||
this.galleryInfo = gi;
|
||||
this.mode = mode;
|
||||
this.state = state;
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExpanded(boolean expanded) {
|
||||
mExpanded = expanded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getExpanded() {
|
||||
return mExpanded;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class GalleryDetail extends GalleryInfo
|
||||
implements PreviewImpl {
|
||||
|
||||
public String title_jpn;
|
||||
public int pages;
|
||||
public String size;
|
||||
public String resized;
|
||||
public String parent;
|
||||
public String visible;
|
||||
public String language;
|
||||
public int people;
|
||||
public String firstPage;
|
||||
/**
|
||||
* Can't be null, just can be empty
|
||||
*/
|
||||
public LinkedHashMap<String, LinkedList<String>> tags;
|
||||
|
||||
// For Preview
|
||||
public int previewSum;
|
||||
public int previewPerPage;
|
||||
public PreviewList[] previewLists;
|
||||
public List<Comment> comments;
|
||||
|
||||
public GalleryDetail() {}
|
||||
|
||||
public GalleryDetail(GalleryInfo galleryInfo) {
|
||||
gid = galleryInfo.gid;
|
||||
token = galleryInfo.token;
|
||||
title = galleryInfo.title;
|
||||
posted = galleryInfo.posted;
|
||||
category = galleryInfo.category;
|
||||
thumb = galleryInfo.thumb;
|
||||
uploader = galleryInfo.uploader;
|
||||
rating = galleryInfo.rating;
|
||||
simpleLanguage = galleryInfo.simpleLanguage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPageNum() {
|
||||
return previewSum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPerPage() {
|
||||
return previewPerPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreviewList getPreview(int page) {
|
||||
if (page >= 0 && previewLists != null && page < previewLists.length)
|
||||
return previewLists[page];
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreview(int page, PreviewList previewList) {
|
||||
if (previewLists == null && previewSum > 0)
|
||||
previewLists = new PreviewList[previewSum];
|
||||
|
||||
if (page >= 0 && previewLists != null && page < previewLists.length)
|
||||
previewLists[page] = previewList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryInfo toGalleryInfo() {
|
||||
return new GalleryInfo(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class GalleryInfo implements Parcelable {
|
||||
|
||||
/**
|
||||
* ISO 639-1
|
||||
*/
|
||||
public static final String S_LANG_JA = "JA";
|
||||
public static final String S_LANG_EN = "EN";
|
||||
public static final String S_LANG_ZH = "ZH";
|
||||
public static final String S_LANG_NL = "NL";
|
||||
public static final String S_LANG_FR = "FR";
|
||||
public static final String S_LANG_DE = "DE";
|
||||
public static final String S_LANG_HU = "HU";
|
||||
public static final String S_LANG_IT = "IT";
|
||||
public static final String S_LANG_KO = "KO";
|
||||
public static final String S_LANG_PL = "PL";
|
||||
public static final String S_LANG_PT = "PT";
|
||||
public static final String S_LANG_RU = "RU";
|
||||
public static final String S_LANG_ES = "ES";
|
||||
public static final String S_LANG_TH = "TH";
|
||||
public static final String S_LANG_VI = "VI";
|
||||
|
||||
public static final String[] S_LANGS = {
|
||||
S_LANG_EN,
|
||||
S_LANG_ZH,
|
||||
S_LANG_ES,
|
||||
S_LANG_KO,
|
||||
S_LANG_RU,
|
||||
S_LANG_FR,
|
||||
S_LANG_PT,
|
||||
S_LANG_TH,
|
||||
S_LANG_DE,
|
||||
S_LANG_IT,
|
||||
S_LANG_VI,
|
||||
S_LANG_PL,
|
||||
S_LANG_HU,
|
||||
S_LANG_NL,
|
||||
};
|
||||
|
||||
public static final String[] S_LANG_PATTERNS = {
|
||||
"[(\\[]eng(?:lish)?[)\\]]",
|
||||
"[((\\[]chinese[))\\]]|[汉漢]化|中[国國][语語]|中文|[(\\[]CN[)\\]]",
|
||||
"[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]",
|
||||
"[(\\[]korean?[)\\]]",
|
||||
"[(\\[]rus(?:sian)?[)\\]]",
|
||||
"[(\\[]fr(?:ench)?[)\\]]",
|
||||
"[(\\[]portuguese",
|
||||
"[(\\[]thai(?: ภาษาไทย)?[)\\]]",
|
||||
"[(\\[]german[)\\]]",
|
||||
"[(\\[]italiano?[)\\]]",
|
||||
"[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]",
|
||||
"[(\\[]polish[)\\]]",
|
||||
"[(\\[]hun(?:garian)?[)\\]]",
|
||||
"[(\\[]dutch[)\\]]",
|
||||
};
|
||||
|
||||
public int gid;
|
||||
public String token;
|
||||
public String title;
|
||||
public String posted;
|
||||
public int category;
|
||||
public String thumb;
|
||||
public String uploader;
|
||||
public float rating;
|
||||
/**
|
||||
* Will not be recond
|
||||
*/
|
||||
public int thumbWidth;
|
||||
/**
|
||||
* Will not be recond
|
||||
*/
|
||||
public int thumbHeight;
|
||||
/**
|
||||
* language get from title
|
||||
*/
|
||||
public String simpleLanguage;
|
||||
|
||||
public static final Parcelable.Creator<GalleryInfo> CREATOR =
|
||||
new Parcelable.Creator<GalleryInfo>() {
|
||||
@Override
|
||||
public GalleryInfo createFromParcel(Parcel source) {
|
||||
GalleryInfo p = new GalleryInfo();
|
||||
p.gid = source.readInt();
|
||||
p.token = source.readString();
|
||||
p.title = source.readString();
|
||||
p.posted = source.readString();
|
||||
p.category = source.readInt();
|
||||
p.thumb = source.readString();
|
||||
p.uploader = source.readString();
|
||||
p.rating = source.readFloat();
|
||||
p.simpleLanguage = source.readString();
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryInfo[] newArray(int size) {
|
||||
return new GalleryInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(gid);
|
||||
dest.writeString(token);
|
||||
dest.writeString(title);
|
||||
dest.writeString(posted);
|
||||
dest.writeInt(category);
|
||||
dest.writeString(thumb);
|
||||
dest.writeString(uploader);
|
||||
dest.writeFloat(rating);
|
||||
dest.writeString(simpleLanguage);
|
||||
}
|
||||
|
||||
public GalleryInfo() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
public GalleryInfo(GalleryInfo gi) {
|
||||
gid = gi.gid;
|
||||
token = gi.token;
|
||||
title = gi.title;
|
||||
posted = gi.posted;
|
||||
category = gi.category;
|
||||
thumb = gi.thumb;
|
||||
uploader = gi.uploader;
|
||||
rating = gi.rating;
|
||||
simpleLanguage = gi.simpleLanguage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof GalleryInfo && ((GalleryInfo) o).gid == gid)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public final void generateSLang() {
|
||||
for (int i = 0; i < S_LANGS.length; i++) {
|
||||
if (Pattern.compile(S_LANG_PATTERNS[i], Pattern.CASE_INSENSITIVE).matcher(title).find()) {
|
||||
simpleLanguage = S_LANGS[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
simpleLanguage = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class GalleryPopular extends GalleryInfo {
|
||||
public long count;
|
||||
|
||||
public static final Parcelable.Creator<GalleryPopular> CREATOR =
|
||||
new Parcelable.Creator<GalleryPopular>() {
|
||||
@Override
|
||||
public GalleryPopular createFromParcel(Parcel source) {
|
||||
GalleryPopular p = new GalleryPopular();
|
||||
p.gid = source.readInt();
|
||||
p.token = source.readString();
|
||||
p.title = source.readString();
|
||||
p.posted = source.readString();
|
||||
p.category = source.readInt();
|
||||
p.thumb = source.readString();
|
||||
p.uploader = source.readString();
|
||||
p.rating = source.readFloat();
|
||||
p.simpleLanguage = source.readString();
|
||||
p.count = source.readLong();
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryPopular[] newArray(int size) {
|
||||
return new GalleryPopular[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeLong(count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class HistoryItem extends GalleryInfo {
|
||||
public int mode;
|
||||
public long date;
|
||||
|
||||
public static final Parcelable.Creator<HistoryItem> CREATOR =
|
||||
new Parcelable.Creator<HistoryItem>() {
|
||||
@Override
|
||||
public HistoryItem createFromParcel(Parcel source) {
|
||||
HistoryItem p = new HistoryItem();
|
||||
p.gid = source.readInt();
|
||||
p.token = source.readString();
|
||||
p.title = source.readString();
|
||||
p.posted = source.readString();
|
||||
p.category = source.readInt();
|
||||
p.thumb = source.readString();
|
||||
p.uploader = source.readString();
|
||||
p.rating = source.readFloat();
|
||||
p.simpleLanguage = source.readString();
|
||||
p.mode = source.readInt();
|
||||
p.date = source.readLong();
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HistoryItem[] newArray(int size) {
|
||||
return new HistoryItem[size];
|
||||
}
|
||||
};
|
||||
|
||||
public HistoryItem() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
public HistoryItem(GalleryInfo gi, int m, long d) {
|
||||
super(gi);
|
||||
mode = m;
|
||||
date = d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
dest.writeInt(mode);
|
||||
dest.writeLong(date);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.ImageLoader;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleGridLayout;
|
||||
|
||||
public class LargePreviewList extends PreviewList {
|
||||
|
||||
private class Item {
|
||||
public String mImageUrl;
|
||||
public String mPageUrl;
|
||||
|
||||
public Item(String imageUrl, String pageUrl) {
|
||||
mImageUrl = imageUrl;
|
||||
mPageUrl = pageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Item> mItemList = new ArrayList<Item>();
|
||||
|
||||
public void addItem(String imageUrl, String pageUrl) {
|
||||
mItemList.add(new Item(imageUrl, pageUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageUrl(int index) {
|
||||
if (index >= 0 && index < mItemList.size())
|
||||
return mItemList.get(index).mPageUrl;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return mItemList.size();
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@Override
|
||||
public void addPreview(SimpleGridLayout viewGroup) {
|
||||
if (mTargetPage != mHolder.getCurPreviewPage())
|
||||
return;
|
||||
|
||||
viewGroup.removeAllViews();
|
||||
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||
int startIndex = mTargetPage * mPi.getPreviewPerPage();
|
||||
int index = startIndex;
|
||||
for (Item item : mItemList) {
|
||||
|
||||
View view = inflater.inflate(R.layout.preview_item, null);
|
||||
((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1));
|
||||
final int _index = index;
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Add to read in Data
|
||||
Intent intent = new Intent(mActivity,
|
||||
GalleryActivity.class);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mPi.toGalleryInfo());
|
||||
intent.putExtra(GalleryActivity.KEY_START_INDEX, _index);
|
||||
mActivity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
viewGroup.addView(view);
|
||||
// TODO I need a better key
|
||||
ImageLoader.getInstance(mActivity).add(item.mImageUrl, mPi.getGid() +
|
||||
"-preview-" + index,
|
||||
new PreviewImageGetListener(viewGroup, index - startIndex));
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewImageGetListener implements ImageLoader.OnGetImageListener {
|
||||
private final SimpleGridLayout mViewGroup;
|
||||
private final int mIndex;
|
||||
|
||||
/**
|
||||
* index is view index in viewgroup
|
||||
* @param viewGroup
|
||||
* @param index
|
||||
*/
|
||||
public PreviewImageGetListener(SimpleGridLayout viewGroup, int index) {
|
||||
mViewGroup = viewGroup;
|
||||
mIndex = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetImage(String key, Bitmap bmp, int state) {
|
||||
if (mTargetPage != mHolder.getCurPreviewPage())
|
||||
return;
|
||||
|
||||
if (bmp == null)
|
||||
mHolder.onGetPreviewImageFailure();
|
||||
else
|
||||
((ImageView)mViewGroup.getChildAt(mIndex).findViewById(R.id.image)).setImageBitmap(bmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import com.hippo.ehviewer.ehclient.EhClient;
|
||||
import com.hippo.ehviewer.network.UrlBuilder;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class ListUrls {
|
||||
|
||||
public static final boolean NUM_CATEGORY = false;
|
||||
|
||||
// Mode
|
||||
public static final int MODE_NORMAL = 0x0;
|
||||
public static final int MODE_UPLOADER = 0x1;
|
||||
public static final int MODE_TAG = 0x2;
|
||||
public static final int MODE_POPULAR = 0x3;
|
||||
public static final int MODE_IMAGE_SEARCH = 0x4;
|
||||
|
||||
// Category
|
||||
public static final int NONE = -1; // Use it for homepage
|
||||
public static final int MISC = 0x1;
|
||||
public static final int DOUJINSHI = 0x2;
|
||||
public static final int MANGA = 0x4;
|
||||
public static final int ARTIST_CG = 0x8;
|
||||
public static final int GAME_CG = 0x10;
|
||||
public static final int IMAGE_SET = 0x20;
|
||||
public static final int COSPLAY = 0x40;
|
||||
public static final int ASIAN_PORN = 0x80;
|
||||
public static final int NON_H = 0x100;
|
||||
public static final int WESTERN = 0x200;
|
||||
public static final int UNKNOWN = 0x400;
|
||||
|
||||
public static final int ALL_CATEGORT = UNKNOWN - 1;
|
||||
//DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC;
|
||||
|
||||
// advance search
|
||||
public static final int SNAME = 0x1;
|
||||
public static final int STAGS = 0x2;
|
||||
public static final int SDESC = 0x4;
|
||||
public static final int STORR = 0x8;
|
||||
public static final int STO = 0x10;
|
||||
public static final int STD1 = 0x20;
|
||||
public static final int STD2 = 0x40;
|
||||
public static final int SH = 0x80;
|
||||
|
||||
public static final int DEFAULT_ADVANCE = SNAME | STAGS;
|
||||
public static final int DEFAULT_MIN_RATING = 2;
|
||||
|
||||
private int mMode = MODE_NORMAL;
|
||||
|
||||
private int page = 0;
|
||||
private int category = NONE;
|
||||
|
||||
private int advsearchType = -1;
|
||||
private int minRating = -1;
|
||||
|
||||
private String search = null;
|
||||
private String mTag = null;
|
||||
|
||||
private int mImageSearchMode;
|
||||
private String mImageKey;
|
||||
private String mImageUrl;
|
||||
private File mSearchFile;
|
||||
private String mResultUrl = null;
|
||||
|
||||
public ListUrls() {}
|
||||
|
||||
public ListUrls(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public ListUrls(int category, int page) {
|
||||
this.category = category;
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public ListUrls(int category, String search) {
|
||||
this.category = category;
|
||||
this.search = search;
|
||||
}
|
||||
|
||||
public ListUrls(int category, String search, int page) {
|
||||
this.category = category;
|
||||
this.search = search;
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public void setCategory(int category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public int getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getSearch() {
|
||||
return search;
|
||||
}
|
||||
|
||||
public void setAdvance(boolean enabled) {
|
||||
if (enabled && advsearchType == -1)
|
||||
advsearchType = DEFAULT_ADVANCE;
|
||||
else if (!enabled)
|
||||
advsearchType = -1;
|
||||
}
|
||||
|
||||
public void setAdvance(int advanceType) {
|
||||
advsearchType = advanceType;
|
||||
}
|
||||
|
||||
public void setAdvance(int advanceType, int minRating) {
|
||||
advsearchType = advanceType;
|
||||
this.minRating = minRating;
|
||||
}
|
||||
|
||||
public boolean isAdvance() {
|
||||
return advsearchType != -1;
|
||||
}
|
||||
|
||||
public int getAdvanceType() {
|
||||
return advsearchType;
|
||||
}
|
||||
|
||||
public boolean isMinRating() {
|
||||
return minRating != -1;
|
||||
}
|
||||
|
||||
public int getMinRating() {
|
||||
return minRating;
|
||||
}
|
||||
|
||||
public void setPage(int p) {
|
||||
page = p;
|
||||
}
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
mMode = mode;
|
||||
}
|
||||
|
||||
// For tag mode
|
||||
public void setTag(String tag) {
|
||||
mMode = MODE_TAG;
|
||||
mTag = tag;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
// For image search mode
|
||||
public void setSearchImage(String key, String url, int imageSearchMode) {
|
||||
mMode = MODE_IMAGE_SEARCH;
|
||||
mImageKey = key;
|
||||
mImageUrl = url;
|
||||
mImageSearchMode = imageSearchMode;
|
||||
}
|
||||
|
||||
public void setSearchFile(File searchFile, int imageSearchMode) {
|
||||
mMode = MODE_IMAGE_SEARCH;
|
||||
mSearchFile = searchFile;
|
||||
mImageSearchMode = imageSearchMode;
|
||||
}
|
||||
|
||||
public void setSearchResult(String resultUrl) {
|
||||
mResultUrl = resultUrl;
|
||||
}
|
||||
|
||||
public File getSearchFile() {
|
||||
return mSearchFile;
|
||||
}
|
||||
|
||||
public String getSearchImageKey() {
|
||||
return mImageKey;
|
||||
}
|
||||
|
||||
public String getSearchImageUrl() {
|
||||
return mImageUrl;
|
||||
}
|
||||
|
||||
public int getImageSearchMode() {
|
||||
return mImageSearchMode;
|
||||
}
|
||||
|
||||
|
||||
public String getUrl() {
|
||||
return getUrl(Config.getMode());
|
||||
}
|
||||
|
||||
public String getUrl(int mode) {
|
||||
|
||||
switch (mMode) {
|
||||
case MODE_POPULAR:
|
||||
return null;
|
||||
|
||||
case MODE_IMAGE_SEARCH:
|
||||
if (mResultUrl == null)
|
||||
return null;
|
||||
else
|
||||
return mResultUrl + "&page=" + page;
|
||||
|
||||
case MODE_TAG:
|
||||
// Add tag
|
||||
StringBuilder sb = new StringBuilder(EhClient.getUrlHeader(mode));
|
||||
sb.append("tag/");
|
||||
if (mTag != null) {
|
||||
String[] tag = mTag.split("\\s+");
|
||||
try {
|
||||
for (int i = 0; i < tag.length; i++)
|
||||
tag[i] = URLEncoder.encode(tag[i], "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {}
|
||||
sb.append(Utils.join(tag, '+'));
|
||||
|
||||
// Add page
|
||||
sb.append("/").append(page);
|
||||
} else {
|
||||
sb.append(page);
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
case MODE_NORMAL:
|
||||
case MODE_UPLOADER:
|
||||
default:
|
||||
String header = EhClient.getUrlHeader(mode);
|
||||
boolean isNeedFooter = false;
|
||||
UrlBuilder ub;
|
||||
// Add category
|
||||
if (category != NONE) {
|
||||
isNeedFooter = true;
|
||||
if (NUM_CATEGORY) {
|
||||
ub = new UrlBuilder(header + category);
|
||||
} else {
|
||||
ub = new UrlBuilder(header);
|
||||
ub.addQuery("f_doujinshi", ((category & DOUJINSHI) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_manga", ((category & MANGA) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_artistcg", ((category & ARTIST_CG) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_gamecg", ((category & GAME_CG) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_western", ((category & WESTERN) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_non-h", ((category & NON_H) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_imageset", ((category & IMAGE_SET) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_cosplay", ((category & COSPLAY) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_asianporn", ((category & ASIAN_PORN) == 0) ? "0" : "1");
|
||||
ub.addQuery("f_misc", ((category & MISC) == 0) ? "0" : "1");
|
||||
}
|
||||
} else {
|
||||
ub = new UrlBuilder(header);
|
||||
}
|
||||
|
||||
// Add search item
|
||||
if (search != null) {
|
||||
isNeedFooter = true;
|
||||
|
||||
String[] tag = search.split("\\s+");
|
||||
try {
|
||||
for (int i = 0; i < tag.length; i++)
|
||||
tag[i] = URLEncoder.encode(tag[i], "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {}
|
||||
|
||||
ub.addQuery("f_search", Utils.join(tag, '+'));
|
||||
}
|
||||
|
||||
// Add page
|
||||
if (page != 0) {
|
||||
ub.addQuery("page", page);
|
||||
}
|
||||
|
||||
// Add foot
|
||||
if (isNeedFooter) ub.addQuery("f_apply", "Apply+Filter");
|
||||
|
||||
// Add advance search
|
||||
if (isAdvance()) {
|
||||
ub.addQuery("advsearch", "1");
|
||||
if((advsearchType & SNAME) != 0) ub.addQuery("f_sname", "on");
|
||||
if((advsearchType & STAGS) != 0) ub.addQuery("f_stags", "on");
|
||||
if((advsearchType & SDESC) != 0) ub.addQuery("f_sdesc", "on");
|
||||
if((advsearchType & STORR) != 0) ub.addQuery("f_storr", "on");
|
||||
if((advsearchType & STO) != 0) ub.addQuery("f_sto", "on");
|
||||
if((advsearchType & STD1) != 0) ub.addQuery("f_sdt1", "on");
|
||||
if((advsearchType & STD2) != 0) ub.addQuery("f_sdt2", "on");
|
||||
if((advsearchType & SH) != 0) ub.addQuery("f_sh", "on");
|
||||
|
||||
// Set min star
|
||||
if (isMinRating()) {
|
||||
ub.addQuery("f_sr", "on");
|
||||
ub.addQuery("f_srdd", minRating);
|
||||
}
|
||||
}
|
||||
return ub.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (mMode) {
|
||||
case MODE_NORMAL:
|
||||
case MODE_UPLOADER:
|
||||
return search;
|
||||
case MODE_TAG:
|
||||
return mTag;
|
||||
case MODE_POPULAR:
|
||||
case MODE_IMAGE_SEARCH:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
public interface LofiDetailImpl extends PreviewImpl {
|
||||
|
||||
public void setPreviewPageNum(int pageNum);
|
||||
|
||||
/**
|
||||
* Check null and empty before use
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getTags();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class LofiGalleryDetail extends LofiGalleryInfo
|
||||
implements LofiDetailImpl {
|
||||
public int previewPageNum;
|
||||
public int previewPerPage;
|
||||
private final List<PreviewList> previewLists = new ArrayList<PreviewList>();
|
||||
|
||||
public LofiGalleryDetail(GalleryInfo galleryInfo) {
|
||||
this(new LofiGalleryInfo(galleryInfo));
|
||||
}
|
||||
|
||||
public LofiGalleryDetail(LofiGalleryInfo lofiGalleryInfo) {
|
||||
gid = lofiGalleryInfo.gid;
|
||||
token = lofiGalleryInfo.token;
|
||||
title = lofiGalleryInfo.title;
|
||||
posted = lofiGalleryInfo.posted;
|
||||
category = lofiGalleryInfo.category;
|
||||
thumb = lofiGalleryInfo.thumb;
|
||||
uploader = lofiGalleryInfo.uploader;
|
||||
rating = lofiGalleryInfo.rating;
|
||||
simpleLanguage = lofiGalleryInfo.simpleLanguage;
|
||||
lofiTags = lofiGalleryInfo.lofiTags;
|
||||
|
||||
previewPageNum = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGid() {
|
||||
return gid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPageNum() {
|
||||
return previewPageNum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreviewPageNum(int pageNum) {
|
||||
previewPageNum = pageNum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPreviewPerPage() {
|
||||
return previewPerPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreviewList getPreview(int page) {
|
||||
if (page >= 0 && page < previewLists.size())
|
||||
return previewLists.get(page);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreview(int page, PreviewList previewList) {
|
||||
if (page >= 0 && page < previewLists.size())
|
||||
previewLists.set(page, previewList);
|
||||
else if (page == previewLists.size())
|
||||
previewLists.add(previewList);
|
||||
else
|
||||
; // Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getTags() {
|
||||
return lofiTags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GalleryInfo toGalleryInfo() {
|
||||
return new GalleryInfo(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class LofiGalleryInfo extends GalleryInfo {
|
||||
|
||||
/**
|
||||
* It could be null, please check before use.
|
||||
*/
|
||||
public String[] lofiTags;
|
||||
|
||||
public static final Parcelable.Creator<LofiGalleryInfo> CREATOR =
|
||||
new Parcelable.Creator<LofiGalleryInfo>() {
|
||||
@Override
|
||||
public LofiGalleryInfo createFromParcel(Parcel source) {
|
||||
int length;
|
||||
|
||||
LofiGalleryInfo p = new LofiGalleryInfo();
|
||||
p.gid = source.readInt();
|
||||
p.token = source.readString();
|
||||
p.title = source.readString();
|
||||
p.posted = source.readString();
|
||||
p.category = source.readInt();
|
||||
p.thumb = source.readString();
|
||||
p.uploader = source.readString();
|
||||
p.rating = source.readFloat();
|
||||
p.simpleLanguage = source.readString();
|
||||
|
||||
length = source.readInt();
|
||||
if (length > 0) {
|
||||
p.lofiTags = new String[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
p.lofiTags[i] = source.readString();
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LofiGalleryInfo[] newArray(int size) {
|
||||
return new LofiGalleryInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
public LofiGalleryInfo() {}
|
||||
|
||||
public LofiGalleryInfo(GalleryInfo galleryInfo) {
|
||||
gid = galleryInfo.gid;
|
||||
token = galleryInfo.token;
|
||||
title = galleryInfo.title;
|
||||
posted = galleryInfo.posted;
|
||||
category = galleryInfo.category;
|
||||
thumb = galleryInfo.thumb;
|
||||
uploader = galleryInfo.uploader;
|
||||
rating = galleryInfo.rating;
|
||||
simpleLanguage = galleryInfo.simpleLanguage;
|
||||
lofiTags = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
super.writeToParcel(dest, flags);
|
||||
|
||||
int length = lofiTags == null ? 0 : lofiTags.length;
|
||||
dest.writeInt(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
dest.writeString(lofiTags[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.ImageLoader;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.ui.GalleryActivity;
|
||||
import com.hippo.ehviewer.widget.SimpleGridLayout;
|
||||
|
||||
public class NormalPreviewList extends PreviewList{
|
||||
|
||||
public class Item {
|
||||
public int xOffset;
|
||||
public int yOffset;
|
||||
public int width;
|
||||
public int height;
|
||||
public String url;
|
||||
|
||||
public Item(int xOffset, int yOffset, int width, int height, String url) {
|
||||
this.xOffset = xOffset;
|
||||
this.yOffset = yOffset;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public class Row {
|
||||
public String imageUrl;
|
||||
public ArrayList<Item> itemArray = new ArrayList<Item>();
|
||||
public int startIndex;
|
||||
public Row(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public void addItem(int xOffset, int yOffset, int width, int height,
|
||||
String url) {
|
||||
itemArray.add(new Item(xOffset, yOffset, width, height, url));
|
||||
}
|
||||
}
|
||||
|
||||
private Row curRow;
|
||||
public ArrayList<Row> rowArray = new ArrayList<Row>();
|
||||
|
||||
public void addItem(String imageUrl, String xOffset, String yOffset, String width, String height,
|
||||
String url) {
|
||||
if (curRow == null) {
|
||||
curRow = new Row(imageUrl);
|
||||
curRow.startIndex = 0;
|
||||
rowArray.add(curRow);
|
||||
} else if (!curRow.imageUrl.equals(imageUrl)) {
|
||||
Row lastRow = curRow;
|
||||
curRow = new Row(imageUrl);
|
||||
curRow.startIndex = lastRow.startIndex + lastRow.itemArray.size();
|
||||
rowArray.add(curRow);
|
||||
}
|
||||
|
||||
curRow.addItem(Integer.parseInt(xOffset),
|
||||
Integer.parseInt(yOffset), Integer.parseInt(width),
|
||||
Integer.parseInt(height), url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
int sum = 0;
|
||||
for (Row row : rowArray)
|
||||
sum += row.itemArray.size();
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPageUrl(int index) {
|
||||
for (Row row : rowArray) {
|
||||
if (index < row.startIndex + row.itemArray.size() && index >= row.startIndex)
|
||||
return row.itemArray.get(index - row.startIndex).url;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO reuse the TextView in AutoWrapLayout
|
||||
@SuppressLint("InflateParams")
|
||||
@Override
|
||||
public void addPreview(SimpleGridLayout viewGroup) {
|
||||
if (mTargetPage != mHolder.getCurPreviewPage())
|
||||
return;
|
||||
|
||||
viewGroup.removeAllViews();
|
||||
LayoutInflater inflater = LayoutInflater.from(mActivity);
|
||||
int index = mTargetPage * mPi.getPreviewPerPage(); // it is display index
|
||||
int rowIndex = 0;
|
||||
for (NormalPreviewList.Row row : rowArray) {
|
||||
for (int i = 0; i < row.itemArray.size(); i++) {
|
||||
View view = inflater.inflate(R.layout.preview_item, null);
|
||||
|
||||
((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1));
|
||||
|
||||
final int _index = index;
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Add to read in Data
|
||||
Intent intent = new Intent(mActivity,
|
||||
GalleryActivity.class);
|
||||
intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mPi.toGalleryInfo());
|
||||
intent.putExtra(GalleryActivity.KEY_START_INDEX, _index);
|
||||
mActivity.startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
viewGroup.addView(view);
|
||||
index++;
|
||||
}
|
||||
// TODO I need a better key
|
||||
ImageLoader.getInstance(mActivity).add(row.imageUrl, mPi.getGid() +
|
||||
"-preview-" + mTargetPage + "-" + rowIndex,
|
||||
new PreviewImageGetListener(viewGroup, rowIndex));
|
||||
rowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewImageGetListener implements ImageLoader.OnGetImageListener {
|
||||
private final SimpleGridLayout mViewGroup;
|
||||
private final int mRowIndex;
|
||||
|
||||
public PreviewImageGetListener(SimpleGridLayout viewGroup, int rowIndex) {
|
||||
mViewGroup = viewGroup;
|
||||
mRowIndex = rowIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetImage(String key, Bitmap bmp, int state) {
|
||||
if (mTargetPage != mHolder.getCurPreviewPage())
|
||||
return;
|
||||
|
||||
if (bmp == null) {
|
||||
mHolder.onGetPreviewImageFailure();
|
||||
} else {
|
||||
int maxWidth = bmp.getWidth();
|
||||
int maxHeight = bmp.getHeight();
|
||||
|
||||
NormalPreviewList.Row row = rowArray.get(mRowIndex);
|
||||
int i = row.startIndex;
|
||||
for(Item item : row.itemArray) {
|
||||
|
||||
if (item.xOffset + item.width > maxWidth)
|
||||
item.width = maxWidth - item.xOffset;
|
||||
if (item.yOffset + item.height > maxHeight)
|
||||
item.height = maxHeight - item.yOffset;
|
||||
Bitmap bitmap = Bitmap.createBitmap(bmp, item.xOffset, item.yOffset, item.width, item.height);
|
||||
|
||||
((ImageView)mViewGroup.getChildAt(i).findViewById(R.id.image)).setImageBitmap(bitmap);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
public interface PreviewImpl {
|
||||
|
||||
public int getGid();
|
||||
|
||||
public String getToken();
|
||||
|
||||
public String getTitle();
|
||||
|
||||
/**
|
||||
* If it is unkonwn, return Integer.MAX_VALUE
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getPreviewPageNum();
|
||||
|
||||
public int getPreviewPerPage();
|
||||
|
||||
/**
|
||||
* If page is error, return null
|
||||
*
|
||||
* @param page
|
||||
* @return
|
||||
*/
|
||||
public PreviewList getPreview(int page);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param previewList
|
||||
*/
|
||||
public void setPreview(int page, PreviewList previewList);
|
||||
|
||||
public GalleryInfo toGalleryInfo();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hippo.ehviewer.widget.SimpleGridLayout;
|
||||
|
||||
/**
|
||||
* This interface describe the previews in one page
|
||||
*
|
||||
* @author Hippo
|
||||
*
|
||||
*/
|
||||
public abstract class PreviewList {
|
||||
|
||||
protected PreviewHolder mHolder;
|
||||
protected Activity mActivity;
|
||||
protected PreviewImpl mPi;
|
||||
protected int mTargetPage;
|
||||
|
||||
/**
|
||||
* Add preview to view group
|
||||
* @param viewGroup
|
||||
* @return
|
||||
*/
|
||||
public abstract void addPreview(SimpleGridLayout viewGroup);
|
||||
|
||||
public abstract int size();
|
||||
|
||||
public void setData(PreviewHolder holder, Activity activity, PreviewImpl pi, int targetPage) {
|
||||
mHolder = holder;
|
||||
mActivity = activity;
|
||||
mPi = pi;
|
||||
mTargetPage = targetPage;
|
||||
}
|
||||
|
||||
public void setTargetPage(int targetPage) {
|
||||
mTargetPage = targetPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return page url at target index, null for invalid index
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public abstract String getPageUrl(int index);
|
||||
|
||||
protected class TextViewWithUrl extends TextView {
|
||||
public String url;
|
||||
|
||||
public TextViewWithUrl(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
|
||||
public interface PreviewHolder {
|
||||
public int getCurPreviewPage();
|
||||
public void onGetPreviewImageFailure();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.data;
|
||||
|
||||
|
||||
public class Tag extends ListUrls {
|
||||
private String mName;
|
||||
|
||||
public Tag(String name, int category, String search) {
|
||||
super(category, search);
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public Tag(String name, ListUrls lus) {
|
||||
this(name, lus.getCategory(), lus.getSearch());
|
||||
setAdvance(lus.getAdvanceType(), lus.getMinRating());
|
||||
setTag(lus.getTag());
|
||||
setMode(lus.getMode());
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
public class BatteryDrawable extends Drawable {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = BatteryDrawable.class.getSimpleName();
|
||||
|
||||
public static final int WARN_LIMIT = 15;
|
||||
|
||||
private int mColor = Color.WHITE;
|
||||
private int mWarnColor = Color.RED;
|
||||
private int mElect = -1;
|
||||
private final Paint mPaint;
|
||||
|
||||
private final Rect mTopRect;
|
||||
private final Rect mBottomRect;
|
||||
private final Rect mRightRect;
|
||||
private final Rect mHeadRect;
|
||||
private final Rect mElectRect;
|
||||
private int mStart;
|
||||
private int mStop;
|
||||
|
||||
public BatteryDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
|
||||
mTopRect = new Rect();
|
||||
mBottomRect = new Rect();
|
||||
mRightRect = new Rect();
|
||||
mHeadRect = new Rect();
|
||||
mElectRect = new Rect();
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
int width = bounds.width();
|
||||
int height = bounds.height();
|
||||
int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f);
|
||||
int turn1 = width * 6 / 7;
|
||||
int turn2 = height / 3;
|
||||
int secBottom = height - strokeWidth;
|
||||
mStart = strokeWidth;
|
||||
mStop = turn1 - strokeWidth;
|
||||
|
||||
mTopRect.set(0, 0, turn1, strokeWidth);
|
||||
mBottomRect.set(0, secBottom, turn1, height);
|
||||
mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom);
|
||||
mHeadRect.set(turn1, turn2, width, height - turn2);
|
||||
mElectRect.set(0, strokeWidth, mStop, secBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* How to draw:<br>
|
||||
* |------------------------------|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |/////////////////| |//|\\\|<br>
|
||||
* |------------------------------|---|<br>
|
||||
* |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|<br>
|
||||
* |------------------------------|
|
||||
*/
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
if (mElect == -1)
|
||||
return;
|
||||
|
||||
mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f);
|
||||
|
||||
canvas.drawRect(mTopRect, mPaint);
|
||||
canvas.drawRect(mBottomRect, mPaint);
|
||||
canvas.drawRect(mRightRect, mPaint);
|
||||
canvas.drawRect(mHeadRect, mPaint);
|
||||
canvas.drawRect(mElectRect, mPaint);
|
||||
}
|
||||
|
||||
private boolean isWarn() {
|
||||
return mElect <= WARN_LIMIT;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor == color)
|
||||
return;
|
||||
|
||||
mColor = color;
|
||||
if (!isWarn()) {
|
||||
mPaint.setColor(mColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setWarnColor(int color) {
|
||||
if (mWarnColor == color)
|
||||
return;
|
||||
|
||||
mWarnColor = color;
|
||||
if (isWarn()) {
|
||||
mPaint.setColor(mWarnColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void setElect(int elect) {
|
||||
if (mElect == elect)
|
||||
return;
|
||||
|
||||
mElect = elect;
|
||||
updatePaint();
|
||||
}
|
||||
|
||||
public void setElect(int elect, boolean warn) {
|
||||
if (mElect == elect)
|
||||
return;
|
||||
|
||||
mElect = elect;
|
||||
updatePaint(warn);
|
||||
}
|
||||
|
||||
private void updatePaint() {
|
||||
updatePaint(isWarn());
|
||||
}
|
||||
|
||||
private void updatePaint(boolean warn) {
|
||||
if (warn)
|
||||
mPaint.setColor(mWarnColor);
|
||||
else
|
||||
mPaint.setColor(mColor);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.drawable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
import com.hippo.ehviewer.util.Ui;
|
||||
|
||||
public class FreeMaterialDrawable extends Drawable {
|
||||
|
||||
private static final String TAG = FreeMaterialDrawable.class.getSimpleName();
|
||||
|
||||
private static final int MAX_SPRITE_NUM = 6;
|
||||
|
||||
private static final int SPRITE_SHAPE_RECTANGLE = 0;
|
||||
private static final int SPRITE_SHAPE_ROUND = 1;
|
||||
private static final int SPRITE_SHAPE_MAX = 2;
|
||||
|
||||
private boolean mKeepTop;
|
||||
private int mBgIndex;
|
||||
private int mBgColor;
|
||||
private final Paint mPaint;
|
||||
private int mSpriteNum;
|
||||
private final Sprite[] mSpriteArray = new Sprite[MAX_SPRITE_NUM];
|
||||
|
||||
private static final int[] sColorArray = new int[] {
|
||||
0xff9c27b0, 0xff673ab7,
|
||||
0xff3f51b5, 0xff2196f3, 0xff03a9f4, 0xff00bcd4,
|
||||
0xff4caf50
|
||||
};
|
||||
|
||||
public FreeMaterialDrawable () {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
mPaint.setShadowLayer(Ui.dp2pix(3), 0, 0, 0x8a000000);
|
||||
}
|
||||
|
||||
public FreeMaterialDrawable setBgIndex(int bgIndex) {
|
||||
mBgIndex = bgIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FreeMaterialDrawable setKeepTop(boolean keepTop) {
|
||||
mKeepTop = keepTop;
|
||||
return this;
|
||||
}
|
||||
|
||||
private int getColor(int[] copyOfColorArray) {
|
||||
int index = MathUtils.random(0, copyOfColorArray.length);
|
||||
int color = copyOfColorArray[index];
|
||||
while (color == -1)
|
||||
color = copyOfColorArray[++index < copyOfColorArray.length ? index : (index = 0)];
|
||||
copyOfColorArray[index] = -1;
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
int w = bounds.width();
|
||||
int h = bounds.height();
|
||||
Sprite s;
|
||||
int[] copyOfColorArray = Arrays.copyOf(sColorArray, sColorArray.length);
|
||||
if (mBgIndex >= 0) {
|
||||
mBgColor = copyOfColorArray[mBgIndex];
|
||||
copyOfColorArray[mBgIndex] = -1;
|
||||
} else {
|
||||
mBgColor = getColor(copyOfColorArray);
|
||||
}
|
||||
mSpriteNum = MathUtils.random(4, MAX_SPRITE_NUM + 1);
|
||||
for (int i = 0; i < mSpriteNum; i++) {
|
||||
switch (SPRITE_SHAPE_ROUND) {//MathUtils.random(0, SPRITE_SHAPE_MAX)) {
|
||||
case SPRITE_SHAPE_RECTANGLE:
|
||||
s = new Rectangle(w, h, getColor(copyOfColorArray));
|
||||
break;
|
||||
default:
|
||||
case SPRITE_SHAPE_ROUND:
|
||||
s = new Round(w, h, getColor(copyOfColorArray));
|
||||
break;
|
||||
}
|
||||
mSpriteArray[i] = s;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawColor(mBgColor);
|
||||
|
||||
for (int i = 0; i < mSpriteNum; i++)
|
||||
mSpriteArray[i].draw(canvas, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
// Empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
private abstract class Sprite {
|
||||
|
||||
private final int mColor;
|
||||
|
||||
public Sprite(int w, int h, int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
public void draw(Canvas canvas, Paint paint) {
|
||||
paint.setColor(mColor);
|
||||
doDraw(canvas, paint);
|
||||
}
|
||||
|
||||
public abstract void doDraw(Canvas canvas, Paint paint);
|
||||
}
|
||||
|
||||
private class Rectangle extends Sprite {
|
||||
|
||||
private final int left;
|
||||
private final int top;
|
||||
private final int right;
|
||||
private final int bottom;
|
||||
|
||||
public Rectangle(int w, int h, int color) {
|
||||
super(w, h, color);
|
||||
|
||||
int width = MathUtils.random(w / 4, w / 2);
|
||||
int height = MathUtils.random(h / 4, h / 2);
|
||||
left = MathUtils.random(w - width / 2);
|
||||
top = MathUtils.random(h - height / 2);
|
||||
right = left + width;
|
||||
bottom = top + height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDraw(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(left, top, right, bottom, paint);
|
||||
}
|
||||
}
|
||||
|
||||
private class Round extends Sprite {
|
||||
|
||||
int mX, mY, mRadius;
|
||||
|
||||
public Round(int w, int h, int color) {
|
||||
super(w, h, color);
|
||||
|
||||
int t = (w + h) / 2;
|
||||
mRadius = MathUtils.random(t / 8, t / 4);
|
||||
mX = MathUtils.random(w);
|
||||
mY = MathUtils.random(mKeepTop ? mRadius + 10 : 0, h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDraw(Canvas canvas, Paint paint) {
|
||||
canvas.drawCircle(mX, mY, mRadius, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,695 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Balys Valentukevicius
|
||||
*
|
||||
* 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.drawable;
|
||||
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.Property;
|
||||
import android.util.TypedValue;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
|
||||
public class MaterialIndicatorDrawable extends Drawable implements Animatable {
|
||||
|
||||
private static final String STATE_KEY = "material_menu_icon_state";
|
||||
|
||||
public enum IconState {
|
||||
BURGER, ARROW
|
||||
}
|
||||
|
||||
public enum AnimationState {
|
||||
BURGER_ARROW;
|
||||
|
||||
public IconState getFirstState() {
|
||||
switch (this) {
|
||||
case BURGER_ARROW:
|
||||
return IconState.BURGER;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IconState getSecondState() {
|
||||
switch (this) {
|
||||
case BURGER_ARROW:
|
||||
return IconState.ARROW;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Stroke {
|
||||
/**
|
||||
* 3 dip
|
||||
*/
|
||||
REGULAR(3),
|
||||
/**
|
||||
* 2 dip
|
||||
*/
|
||||
THIN(2),
|
||||
/**
|
||||
* 1 dip
|
||||
*/
|
||||
EXTRA_THIN(1);
|
||||
|
||||
private final int strokeWidth;
|
||||
|
||||
Stroke(int strokeWidth) {
|
||||
this.strokeWidth = strokeWidth;
|
||||
}
|
||||
|
||||
protected static Stroke valueOf(int strokeWidth) {
|
||||
switch (strokeWidth) {
|
||||
case 3:
|
||||
return REGULAR;
|
||||
case 2:
|
||||
return THIN;
|
||||
case 1:
|
||||
return EXTRA_THIN;
|
||||
default:
|
||||
return THIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final int DEFAULT_COLOR = Color.WHITE;
|
||||
public static final int DEFAULT_SCALE = 1;
|
||||
public static final int DEFAULT_TRANSFORM_DURATION = 800;
|
||||
public static final int DEFAULT_PRESSED_DURATION = 400;
|
||||
|
||||
private static final int BASE_DRAWABLE_WIDTH = 40;
|
||||
private static final int BASE_DRAWABLE_HEIGHT = 40;
|
||||
private static final int BASE_ICON_WIDTH = 20;
|
||||
private static final int BASE_CIRCLE_RADIUS = 18;
|
||||
|
||||
private static final float ARROW_MID_LINE_ANGLE = 180;
|
||||
private static final float ARROW_TOP_LINE_ANGLE = 135;
|
||||
private static final float ARROW_BOT_LINE_ANGLE = 225;
|
||||
|
||||
private static final float TRANSFORMATION_START = 0;
|
||||
private static final float TRANSFORMATION_MID = 1.0f;
|
||||
private static final float TRANSFORMATION_END = 2.0f;
|
||||
|
||||
private static final int DEFAULT_CIRCLE_ALPHA = 200;
|
||||
|
||||
private final float diph;
|
||||
private final float dip1;
|
||||
private final float dip2;
|
||||
private final float dip3;
|
||||
private final float dip4;
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final float strokeWidth;
|
||||
private final float iconWidth;
|
||||
private final float topPadding;
|
||||
private final float sidePadding;
|
||||
private final float circleRadius;
|
||||
|
||||
private final Stroke stroke;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private final Paint gridPaint = new Paint();
|
||||
private final Paint iconPaint = new Paint();
|
||||
private final Paint circlePaint = new Paint();
|
||||
|
||||
private float transformationValue = 0f;
|
||||
private float pressedProgressValue = 0f;
|
||||
private boolean transformationRunning = false;
|
||||
|
||||
private IconState currentIconState = IconState.BURGER;
|
||||
private AnimationState animationState = AnimationState.BURGER_ARROW;
|
||||
|
||||
private IconState animatingIconState;
|
||||
private boolean drawTouchCircle;
|
||||
private boolean neverDrawTouch;
|
||||
private boolean rtlEnabled;
|
||||
|
||||
private ObjectAnimator transformation;
|
||||
private ObjectAnimator pressedCircle;
|
||||
|
||||
private MaterialMenuState materialMenuState;
|
||||
|
||||
public MaterialIndicatorDrawable(Context context, int color, Stroke stroke) {
|
||||
this(context, color, stroke, DEFAULT_SCALE, DEFAULT_TRANSFORM_DURATION, DEFAULT_PRESSED_DURATION);
|
||||
}
|
||||
|
||||
public MaterialIndicatorDrawable(Context context, int color, Stroke stroke, int transformDuration, int pressedDuration) {
|
||||
this(context, color, stroke, DEFAULT_SCALE, transformDuration, pressedDuration);
|
||||
}
|
||||
|
||||
public MaterialIndicatorDrawable(Context context, int color, Stroke stroke, int scale, int transformDuration, int pressedDuration) {
|
||||
Resources resources = context.getResources();
|
||||
// convert each separately due to various densities
|
||||
this.dip1 = dpToPx(resources, 1) * scale;
|
||||
this.dip2 = dpToPx(resources, 2) * scale;
|
||||
this.dip3 = dpToPx(resources, 3) * scale;
|
||||
this.dip4 = dpToPx(resources, 4) * scale;
|
||||
this.diph = dip1 / 2;
|
||||
|
||||
this.stroke = stroke;
|
||||
this.width = (int) (dpToPx(resources, BASE_DRAWABLE_WIDTH) * scale);
|
||||
this.height = (int) (dpToPx(resources, BASE_DRAWABLE_HEIGHT) * scale);
|
||||
this.iconWidth = dpToPx(resources, BASE_ICON_WIDTH) * scale;
|
||||
this.circleRadius = dpToPx(resources, BASE_CIRCLE_RADIUS) * scale;
|
||||
this.strokeWidth = dpToPx(resources, stroke.strokeWidth) * scale;
|
||||
|
||||
this.sidePadding = (width - iconWidth) / 2;
|
||||
this.topPadding = (height - 5 * dip3) / 2;
|
||||
|
||||
initPaint(color);
|
||||
initAnimations(transformDuration, pressedDuration);
|
||||
|
||||
materialMenuState = new MaterialMenuState();
|
||||
}
|
||||
|
||||
private MaterialIndicatorDrawable(int color, Stroke stroke, long transformDuration, long pressedDuration,
|
||||
int width, int height, float iconWidth, float circleRadius, float strokeWidth, float dip1
|
||||
) {
|
||||
this.dip1 = dip1;
|
||||
this.dip2 = dip1 * 2;
|
||||
this.dip3 = dip1 * 3;
|
||||
this.dip4 = dip1 * 4;
|
||||
this.diph = dip1 / 2;
|
||||
this.stroke = stroke;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.iconWidth = iconWidth;
|
||||
this.circleRadius = circleRadius;
|
||||
this.strokeWidth = strokeWidth;
|
||||
this.sidePadding = (width - iconWidth) / 2;
|
||||
this.topPadding = (height - 5 * dip3) / 2;
|
||||
|
||||
initPaint(color);
|
||||
initAnimations((int) transformDuration, (int) pressedDuration);
|
||||
|
||||
materialMenuState = new MaterialMenuState();
|
||||
}
|
||||
|
||||
private void initPaint(int color) {
|
||||
gridPaint.setAntiAlias(false);
|
||||
gridPaint.setColor(Color.GREEN);
|
||||
gridPaint.setStrokeWidth(1);
|
||||
|
||||
iconPaint.setAntiAlias(true);
|
||||
iconPaint.setStyle(Style.STROKE);
|
||||
iconPaint.setStrokeWidth(strokeWidth);
|
||||
iconPaint.setColor(color);
|
||||
|
||||
circlePaint.setAntiAlias(true);
|
||||
circlePaint.setStyle(Style.FILL);
|
||||
circlePaint.setColor(color);
|
||||
circlePaint.setAlpha(DEFAULT_CIRCLE_ALPHA);
|
||||
|
||||
setBounds(0, 0, width, height);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drawing
|
||||
*/
|
||||
|
||||
@Override public void draw(Canvas canvas) {
|
||||
final float ratio = transformationValue <= 1 ? transformationValue : 2 - transformationValue;
|
||||
|
||||
if (rtlEnabled) {
|
||||
canvas.save();
|
||||
canvas.scale(-1, 1, 0, 0);
|
||||
canvas.translate(-getIntrinsicWidth(), 0);
|
||||
}
|
||||
|
||||
drawTopLine(canvas, ratio);
|
||||
drawMiddleLine(canvas, ratio);
|
||||
drawBottomLine(canvas, ratio);
|
||||
|
||||
if (rtlEnabled) {
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
if (drawTouchCircle) drawTouchCircle(canvas);
|
||||
}
|
||||
|
||||
private void drawTouchCircle(Canvas canvas) {
|
||||
canvas.restore();
|
||||
canvas.drawCircle(width / 2, height / 2, pressedProgressValue, circlePaint);
|
||||
}
|
||||
|
||||
private void drawMiddleLine(Canvas canvas, float ratio) {
|
||||
canvas.restore();
|
||||
canvas.save();
|
||||
|
||||
float rotation = 0;
|
||||
float pivotX = width / 2;
|
||||
float pivotY = width / 2;
|
||||
float startX = sidePadding;
|
||||
float startY = topPadding + dip3 / 2 * 5;
|
||||
float stopX = width - sidePadding;
|
||||
float stopY = topPadding + dip3 / 2 * 5;
|
||||
int alpha = 255;
|
||||
|
||||
switch (animationState) {
|
||||
case BURGER_ARROW:
|
||||
// rotate by 180
|
||||
if (isMorphingForward()) {
|
||||
rotation = ratio * ARROW_MID_LINE_ANGLE;
|
||||
} else {
|
||||
rotation = ARROW_MID_LINE_ANGLE + (1 - ratio) * ARROW_MID_LINE_ANGLE;
|
||||
}
|
||||
// shorten one end
|
||||
stopX -= ratio * resolveStrokeModifier(ratio) / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
iconPaint.setAlpha(alpha);
|
||||
canvas.rotate(rotation, pivotX, pivotY);
|
||||
canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
|
||||
iconPaint.setAlpha(255);
|
||||
}
|
||||
|
||||
private void drawTopLine(Canvas canvas, float ratio) {
|
||||
canvas.save();
|
||||
|
||||
float rotation = 0, pivotX = 0, pivotY = 0;
|
||||
float rotation2 = 0;
|
||||
// pivot at center of line
|
||||
float pivotX2 = width / 2 + dip3 / 2;
|
||||
float pivotY2 = topPadding + dip2;
|
||||
|
||||
float startX = sidePadding;
|
||||
float startY = topPadding + dip2;
|
||||
float stopX = width - sidePadding;
|
||||
float stopY = topPadding + dip2;
|
||||
int alpha = 255;
|
||||
|
||||
switch (animationState) {
|
||||
case BURGER_ARROW:
|
||||
if (isMorphingForward()) {
|
||||
// rotate until required angle
|
||||
rotation = ratio * ARROW_BOT_LINE_ANGLE;
|
||||
} else {
|
||||
// rotate back to start doing a 360
|
||||
rotation = ARROW_BOT_LINE_ANGLE + (1 - ratio) * ARROW_TOP_LINE_ANGLE;
|
||||
}
|
||||
// rotate by middle
|
||||
pivotX = width / 2;
|
||||
pivotY = height / 2;
|
||||
|
||||
// shorten both ends
|
||||
stopX -= resolveStrokeModifier(ratio);
|
||||
startX += dip3 * ratio;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
iconPaint.setAlpha(alpha);
|
||||
canvas.rotate(rotation, pivotX, pivotY);
|
||||
canvas.rotate(rotation2, pivotX2, pivotY2);
|
||||
canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
|
||||
iconPaint.setAlpha(255);
|
||||
}
|
||||
|
||||
private void drawBottomLine(Canvas canvas, float ratio) {
|
||||
canvas.restore();
|
||||
canvas.save();
|
||||
|
||||
float rotation = 0, pivotX = 0, pivotY = 0;
|
||||
float rotation2 = 0;
|
||||
// pivot at center of line
|
||||
float pivotX2 = width / 2 + dip3 / 2;
|
||||
float pivotY2 = height - topPadding - dip2;
|
||||
|
||||
float startX = sidePadding;
|
||||
float startY = height - topPadding - dip2;
|
||||
float stopX = width - sidePadding;
|
||||
float stopY = height - topPadding - dip2;
|
||||
|
||||
switch (animationState) {
|
||||
case BURGER_ARROW:
|
||||
if (isMorphingForward()) {
|
||||
// rotate to required angle
|
||||
rotation = ARROW_TOP_LINE_ANGLE * ratio;
|
||||
} else {
|
||||
// rotate back to start doing a 360
|
||||
rotation = ARROW_TOP_LINE_ANGLE + (1 - ratio) * ARROW_BOT_LINE_ANGLE;
|
||||
}
|
||||
// pivot center of canvas
|
||||
pivotX = width / 2;
|
||||
pivotY = height / 2;
|
||||
|
||||
// shorten both ends
|
||||
stopX = width - sidePadding - resolveStrokeModifier(ratio);
|
||||
startX = sidePadding + dip3 * ratio;
|
||||
break;
|
||||
}
|
||||
|
||||
canvas.rotate(rotation, pivotX, pivotY);
|
||||
canvas.rotate(rotation2, pivotX2, pivotY2);
|
||||
canvas.drawLine(startX, startY, stopX, stopY, iconPaint);
|
||||
}
|
||||
|
||||
private boolean isMorphingForward() {
|
||||
return transformationValue <= TRANSFORMATION_MID;
|
||||
}
|
||||
|
||||
private float resolveStrokeModifier(float ratio) {
|
||||
switch (stroke) {
|
||||
case REGULAR:
|
||||
return ratio * dip3;
|
||||
case THIN:
|
||||
return ratio * (dip3 + diph);
|
||||
case EXTRA_THIN:
|
||||
return ratio * dip4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override public void setAlpha(int alpha) {
|
||||
iconPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override public void setColorFilter(ColorFilter cf) {
|
||||
iconPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
@Override public int getOpacity() {
|
||||
return PixelFormat.TRANSPARENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessor methods
|
||||
*/
|
||||
|
||||
public void setColor(int color) {
|
||||
iconPaint.setColor(color);
|
||||
circlePaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void setTransformationDuration(int duration) {
|
||||
transformation.setDuration(duration);
|
||||
}
|
||||
|
||||
public void setPressedDuration(int duration) {
|
||||
pressedCircle.setDuration(duration);
|
||||
}
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
transformation.setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
public void setNeverDrawTouch(boolean neverDrawTouch) {
|
||||
this.neverDrawTouch = neverDrawTouch;
|
||||
}
|
||||
|
||||
public void setIconState(IconState iconState) {
|
||||
synchronized (lock) {
|
||||
if (transformationRunning) {
|
||||
transformation.cancel();
|
||||
transformationRunning = false;
|
||||
}
|
||||
|
||||
if (currentIconState == iconState) return;
|
||||
|
||||
switch (iconState) {
|
||||
case BURGER:
|
||||
animationState = AnimationState.BURGER_ARROW;
|
||||
transformationValue = TRANSFORMATION_START;
|
||||
break;
|
||||
case ARROW:
|
||||
animationState = AnimationState.BURGER_ARROW;
|
||||
transformationValue = TRANSFORMATION_MID;
|
||||
break;
|
||||
}
|
||||
currentIconState = iconState;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public void animateIconState(IconState state, boolean drawTouch) {
|
||||
synchronized (lock) {
|
||||
if (transformationRunning) {
|
||||
transformation.end();
|
||||
pressedCircle.end();
|
||||
}
|
||||
drawTouchCircle = drawTouch;
|
||||
animatingIconState = state;
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
public IconState setTransformationOffset(AnimationState animationState, float offset) {
|
||||
if (offset < TRANSFORMATION_START || offset > TRANSFORMATION_END) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Value must be between %s and %s", TRANSFORMATION_START, TRANSFORMATION_END)
|
||||
);
|
||||
}
|
||||
|
||||
this.animationState = animationState;
|
||||
|
||||
final boolean isFirstIcon = offset < TRANSFORMATION_MID || offset == TRANSFORMATION_END;
|
||||
|
||||
currentIconState = isFirstIcon ? animationState.getFirstState() : animationState.getSecondState();
|
||||
animatingIconState = isFirstIcon ? animationState.getSecondState() : animationState.getFirstState();
|
||||
|
||||
setTransformationValue(offset);
|
||||
|
||||
return currentIconState;
|
||||
}
|
||||
|
||||
public void setRTLEnabled(boolean rtlEnabled) {
|
||||
this.rtlEnabled = rtlEnabled;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public IconState getIconState() {
|
||||
return currentIconState;
|
||||
}
|
||||
|
||||
/*
|
||||
* Animations
|
||||
*/
|
||||
private final Property<MaterialIndicatorDrawable, Float> transformationProperty
|
||||
= new Property<MaterialIndicatorDrawable, Float>(Float.class, "transformation") {
|
||||
@Override
|
||||
public Float get(MaterialIndicatorDrawable object) {
|
||||
return object.getTransformationValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(MaterialIndicatorDrawable object, Float value) {
|
||||
object.setTransformationValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
private final Property<MaterialIndicatorDrawable, Float> pressedProgressProperty
|
||||
= new Property<MaterialIndicatorDrawable, Float>(Float.class, "pressedProgress") {
|
||||
@Override
|
||||
public Float get(MaterialIndicatorDrawable object) {
|
||||
return object.getPressedProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(MaterialIndicatorDrawable object, Float value) {
|
||||
object.setPressedProgress(value);
|
||||
}
|
||||
};
|
||||
|
||||
public Float getTransformationValue() {
|
||||
return transformationValue;
|
||||
}
|
||||
|
||||
public void setTransformationValue(Float value) {
|
||||
this.transformationValue = value;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public Float getPressedProgress() {
|
||||
return pressedProgressValue;
|
||||
}
|
||||
|
||||
public void setPressedProgress(Float value) {
|
||||
this.pressedProgressValue = value;
|
||||
circlePaint.setAlpha((int) (DEFAULT_CIRCLE_ALPHA * (1 - value / (circleRadius * 1.22f))));
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private void initAnimations(int transformDuration, int pressedDuration) {
|
||||
transformation = ObjectAnimator.ofFloat(this, transformationProperty, 0);
|
||||
transformation.setInterpolator(new DecelerateInterpolator(3));
|
||||
transformation.setDuration(transformDuration);
|
||||
transformation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override public void onAnimationEnd(Animator animation) {
|
||||
transformationRunning = false;
|
||||
setIconState(animatingIconState);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
pressedCircle = ObjectAnimator.ofFloat(this, pressedProgressProperty, 0, 0);
|
||||
pressedCircle.setDuration(pressedDuration);
|
||||
pressedCircle.setInterpolator(new DecelerateInterpolator());
|
||||
pressedCircle.addListener(new AnimatorListenerAdapter() {
|
||||
@Override public void onAnimationEnd(Animator animation) {
|
||||
pressedProgressValue = 0;
|
||||
}
|
||||
|
||||
@Override public void onAnimationCancel(Animator animation) {
|
||||
pressedProgressValue = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean resolveTransformation() {
|
||||
boolean isCurrentBurger = currentIconState == IconState.BURGER;
|
||||
boolean isCurrentArrow = currentIconState == IconState.ARROW;
|
||||
boolean isAnimatingBurger = animatingIconState == IconState.BURGER;
|
||||
boolean isAnimatingArrow = animatingIconState == IconState.ARROW;
|
||||
|
||||
if ((isCurrentBurger && isAnimatingArrow) || (isCurrentArrow && isAnimatingBurger)) {
|
||||
animationState = AnimationState.BURGER_ARROW;
|
||||
return isCurrentBurger;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
String.format("Animating from %s to %s is not supported", currentIconState, animatingIconState)
|
||||
);
|
||||
}
|
||||
|
||||
@Override public void start() {
|
||||
if (transformationRunning || animatingIconState == null || animatingIconState == currentIconState) return;
|
||||
transformationRunning = true;
|
||||
|
||||
final boolean direction = resolveTransformation();
|
||||
transformation.setFloatValues(
|
||||
direction ? TRANSFORMATION_START : TRANSFORMATION_MID,
|
||||
direction ? TRANSFORMATION_MID : TRANSFORMATION_END
|
||||
);
|
||||
transformation.start();
|
||||
|
||||
if (pressedCircle.isRunning()) {
|
||||
pressedCircle.cancel();
|
||||
}
|
||||
if (drawTouchCircle && !neverDrawTouch) {
|
||||
pressedCircle.setFloatValues(0, circleRadius * 1.22f);
|
||||
pressedCircle.start();
|
||||
}
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override public void stop() {
|
||||
if (isRunning() && transformation.isRunning()) {
|
||||
transformation.end();
|
||||
} else {
|
||||
transformationRunning = false;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean isRunning() {
|
||||
return transformationRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ConstantState getConstantState() {
|
||||
materialMenuState.changingConfigurations = getChangingConfigurations();
|
||||
return materialMenuState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable mutate() {
|
||||
materialMenuState = new MaterialMenuState();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call from {@link android.app.Activity#onSaveInstanceState(android.os.Bundle)} to store current icon state
|
||||
*
|
||||
* @param outState outState
|
||||
*/
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putString(STATE_KEY, currentIconState.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Call from {@link android.app.Activity#onPostCreate(android.os.Bundle)} to restore icon state
|
||||
*
|
||||
* @param state state
|
||||
*/
|
||||
public void syncState(Bundle state) {
|
||||
if (state != null) {
|
||||
String iconStateName = state.getString(STATE_KEY);
|
||||
if (iconStateName == null) {
|
||||
iconStateName = MaterialIndicatorDrawable.IconState.BURGER.name();
|
||||
}
|
||||
setIconState(MaterialIndicatorDrawable.IconState.valueOf(iconStateName));
|
||||
}
|
||||
}
|
||||
|
||||
private final class MaterialMenuState extends ConstantState {
|
||||
private int changingConfigurations;
|
||||
|
||||
private MaterialMenuState() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
MaterialIndicatorDrawable drawable = new MaterialIndicatorDrawable(
|
||||
circlePaint.getColor(), stroke, transformation.getDuration(),
|
||||
pressedCircle.getDuration(), width, height, iconWidth, circleRadius, strokeWidth, dip1
|
||||
);
|
||||
drawable.setIconState(animatingIconState != null ? animatingIconState : currentIconState);
|
||||
drawable.setRTLEnabled(rtlEnabled);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return changingConfigurations;
|
||||
}
|
||||
}
|
||||
|
||||
static float dpToPx(Resources resources, float dp) {
|
||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
package com.hippo.ehviewer.drawable;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// Get from android.graphics.drawable.MaterialProgressDrawable
|
||||
public class MaterialProgressDrawable extends Drawable implements Animatable {
|
||||
private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator();
|
||||
private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator();
|
||||
|
||||
private static final long ANIMATION_DURATION = 1333;
|
||||
|
||||
private static final int NUM_POINTS = 5;
|
||||
|
||||
private final ArrayList<Animator> mAnimators = new ArrayList<Animator>();
|
||||
|
||||
private final Ring mRing;
|
||||
|
||||
private MaterialProgressState mState;
|
||||
|
||||
private float mRotation;
|
||||
|
||||
private boolean mMutated;
|
||||
|
||||
public MaterialProgressDrawable() {
|
||||
this(new MaterialProgressState(null));
|
||||
}
|
||||
|
||||
public MaterialProgressDrawable(int width, int height, int color, float strokeWidth, float innerRadius) {
|
||||
this(new MaterialProgressState(width, height, color, strokeWidth, innerRadius));
|
||||
}
|
||||
|
||||
private MaterialProgressDrawable(MaterialProgressState state) {
|
||||
mState = state;
|
||||
mRing = new Ring(mCallback);
|
||||
mMutated = false;
|
||||
|
||||
initializeFromState();
|
||||
setupAnimators();
|
||||
}
|
||||
|
||||
private void initializeFromState() {
|
||||
MaterialProgressState state = mState;
|
||||
|
||||
Ring ring = mRing;
|
||||
ring.setStrokeWidth(state.mStrokeWidth);
|
||||
|
||||
int color = state.mColor.getColorForState(getState(), 0);
|
||||
ring.setColor(color);
|
||||
|
||||
float minEdge = Math.min(state.mWidth, state.mHeight);
|
||||
if ((state.mInnerRadius <= 0.0F) || (minEdge < 0.0F)) {
|
||||
ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0F));
|
||||
} else {
|
||||
float insets = minEdge / 2.0F - state.mInnerRadius;
|
||||
ring.setInsets(insets);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable mutate() {
|
||||
if ((!mMutated) && (super.mutate() == this)) {
|
||||
mState = new MaterialProgressState(mState);
|
||||
mMutated = true;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] state) {
|
||||
boolean changed = super.onStateChange(state);
|
||||
|
||||
int color = mState.mColor.getColorForState(state, 0);
|
||||
if (color != mRing.getColor()) {
|
||||
mRing.setColor(color);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return (super.isStateful()) || (mState.mColor.isStateful());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mState.mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mState.mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas c) {
|
||||
Rect bounds = getBounds();
|
||||
int saveCount = c.save();
|
||||
c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
|
||||
mRing.draw(c, bounds);
|
||||
c.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mState.mColor = ColorStateList.valueOf(color);
|
||||
mRing.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mRing.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mRing.getAlpha();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mRing.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
private void setRotation(float rotation) {
|
||||
mRotation = rotation;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private float getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return -3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
boolean changed = super.setVisible(visible, restart);
|
||||
if (visible) {
|
||||
if ((changed) || (restart)) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
ArrayList<Animator> animators = mAnimators;
|
||||
int N = animators.size();
|
||||
for (int i = 0; i < N; i++) {
|
||||
Animator animator = animators.get(i);
|
||||
if (animator.isRunning()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public void start() {
|
||||
ArrayList<Animator> animators = mAnimators;
|
||||
int N = animators.size();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
for (int i = 0; i < N; i++) {
|
||||
Animator animator = animators.get(i);
|
||||
if (animator.isPaused())
|
||||
animator.resume();
|
||||
else if (!animator.isRunning())
|
||||
animator.start();
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < N; i++) {
|
||||
Animator animator = animators.get(i);
|
||||
if (!animator.isRunning())
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public void stop() {
|
||||
ArrayList<Animator> animators = mAnimators;
|
||||
int N = animators.size();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
for (int i = 0; i < N; i++) {
|
||||
animators.get(i).pause();
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < N; i++) {
|
||||
animators.get(i).cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAnimators() {
|
||||
Ring ring = mRing;
|
||||
|
||||
ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim",
|
||||
new float[] { 0.0F, 0.75F });
|
||||
endTrim.setDuration(ANIMATION_DURATION);
|
||||
endTrim.setInterpolator(START_CURVE_INTERPOLATOR);
|
||||
endTrim.setRepeatCount(-1);
|
||||
endTrim.setRepeatMode(1);
|
||||
|
||||
ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim",
|
||||
new float[] { 0.0F, 0.75F });
|
||||
startTrim.setDuration(ANIMATION_DURATION);
|
||||
startTrim.setInterpolator(END_CURVE_INTERPOLATOR);
|
||||
startTrim.setRepeatCount(-1);
|
||||
startTrim.setRepeatMode(1);
|
||||
|
||||
ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation",
|
||||
new float[] { 0.0F, 0.25F });
|
||||
rotation.setDuration(ANIMATION_DURATION);
|
||||
rotation.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
rotation.setRepeatCount(-1);
|
||||
rotation.setRepeatMode(1);
|
||||
|
||||
ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation",
|
||||
new float[] { 0.0F, 360.0F });
|
||||
groupRotation.setDuration(6665L);
|
||||
groupRotation.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
groupRotation.setRepeatCount(-1);
|
||||
groupRotation.setRepeatMode(1);
|
||||
|
||||
mAnimators.add(endTrim);
|
||||
mAnimators.add(startTrim);
|
||||
mAnimators.add(rotation);
|
||||
mAnimators.add(groupRotation);
|
||||
}
|
||||
|
||||
private final Drawable.Callback mCallback = new Drawable.Callback() {
|
||||
@Override
|
||||
public void invalidateDrawable(Drawable d) {
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleDrawable(Drawable d, Runnable what, long when) {
|
||||
scheduleSelf(what, when);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unscheduleDrawable(Drawable d, Runnable what) {
|
||||
unscheduleSelf(what);
|
||||
}
|
||||
};
|
||||
|
||||
private static class MaterialProgressState extends Drawable.ConstantState {
|
||||
private float mStrokeWidth = 5.0F;
|
||||
private float mInnerRadius = -1.0F;
|
||||
private int mWidth = -1;
|
||||
private int mHeight = -1;
|
||||
private ColorStateList mColor = ColorStateList.valueOf(0);
|
||||
|
||||
public MaterialProgressState(int width, int height, int color, float strokeWidth, float innerRadius) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mColor = ColorStateList.valueOf(color);
|
||||
mStrokeWidth = strokeWidth;
|
||||
mInnerRadius = innerRadius;
|
||||
}
|
||||
|
||||
public MaterialProgressState(MaterialProgressState orig) {
|
||||
if (orig != null) {
|
||||
mStrokeWidth = orig.mStrokeWidth;
|
||||
mInnerRadius = orig.mInnerRadius;
|
||||
mWidth = orig.mWidth;
|
||||
mHeight = orig.mHeight;
|
||||
mColor = orig.mColor;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChangingConfigurations() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Drawable newDrawable() {
|
||||
return new MaterialProgressDrawable(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Ring {
|
||||
private final RectF mTempBounds = new RectF();
|
||||
private final Paint mPaint = new Paint();
|
||||
|
||||
private final Drawable.Callback mCallback;
|
||||
|
||||
private float mStartTrim = 0.0F;
|
||||
private float mEndTrim = 0.0F;
|
||||
private float mRotation = 0.0F;
|
||||
private float mStrokeWidth = 5.0F;
|
||||
private float mStrokeInset = 2.5F;
|
||||
|
||||
private int mAlpha = 255;
|
||||
private int mColor = -16777216;
|
||||
|
||||
public Ring(Drawable.Callback callback) {
|
||||
mCallback = callback;
|
||||
|
||||
mPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
|
||||
public void draw(Canvas c, Rect bounds) {
|
||||
RectF arcBounds = mTempBounds;
|
||||
arcBounds.set(bounds);
|
||||
arcBounds.inset(mStrokeInset, mStrokeInset);
|
||||
|
||||
float startAngle = (mStartTrim + mRotation) * 360.0F;
|
||||
float endAngle = (mEndTrim + mRotation) * 360.0F;
|
||||
float sweepAngle = endAngle - startAngle;
|
||||
|
||||
float diameter = Math.min(arcBounds.width(), arcBounds.height());
|
||||
float minAngle = (float) (360.0D / (diameter * 3.141592653589793D));
|
||||
if ((sweepAngle < minAngle) && (sweepAngle > -minAngle)) {
|
||||
sweepAngle = Math.signum(sweepAngle) * minAngle;
|
||||
}
|
||||
|
||||
c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
|
||||
}
|
||||
|
||||
public void setColorFilter(ColorFilter filter) {
|
||||
mPaint.setColorFilter(filter);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public ColorFilter getColorFilter() {
|
||||
return mPaint.getColorFilter();
|
||||
}
|
||||
|
||||
public void setAlpha(int alpha) {
|
||||
mAlpha = alpha;
|
||||
mPaint.setColor(mColor & 0xFFFFFF | alpha << 24);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
mPaint.setColor(color & 0xFFFFFF | mAlpha << 24);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setStrokeWidth(float strokeWidth) {
|
||||
mStrokeWidth = strokeWidth;
|
||||
mPaint.setStrokeWidth(strokeWidth);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getStrokeWidth() {
|
||||
return mStrokeWidth;
|
||||
}
|
||||
|
||||
public void setStartTrim(float startTrim) {
|
||||
mStartTrim = startTrim;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getStartTrim() {
|
||||
return mStartTrim;
|
||||
}
|
||||
|
||||
public void setEndTrim(float endTrim) {
|
||||
mEndTrim = endTrim;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getEndTrim() {
|
||||
return mEndTrim;
|
||||
}
|
||||
|
||||
public void setRotation(float rotation) {
|
||||
mRotation = rotation;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public void setInsets(float insets) {
|
||||
mStrokeInset = insets;
|
||||
}
|
||||
|
||||
public float getInsets() {
|
||||
return mStrokeInset;
|
||||
}
|
||||
|
||||
private void invalidateSelf() {
|
||||
mCallback.invalidateDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndCurveInterpolator extends
|
||||
AccelerateDecelerateInterpolator {
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
return super
|
||||
.getInterpolation(Math.max(0.0F, (input - 0.5F) * 2.0F));
|
||||
}
|
||||
}
|
||||
|
||||
private static class StartCurveInterpolator extends
|
||||
AccelerateDecelerateInterpolator {
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
return super.getInterpolation(Math.min(1.0F, input * 2.0F));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.drawable;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class OvalDrawable extends Drawable {
|
||||
|
||||
private final Paint mPaint;
|
||||
private final RectF mRectF;
|
||||
|
||||
public OvalDrawable(int color) {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
|
||||
mPaint.setColor(color);
|
||||
mRectF = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
mRectF.set(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.drawOval(mRectF, mPaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setColor(mPaint.getColor() & 0x00ffffff | (alpha << 24));
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
// Not support
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mPaint.setColor(color);
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.effect;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class DrawableTransition {
|
||||
|
||||
public static void transit(final Drawable drawable, boolean fromDark, long duration) {
|
||||
final AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator(fromDark);
|
||||
evaluator.setFraction(0.0f);
|
||||
drawable.setColorFilter(new ColorMatrixColorFilter(evaluator.getColorMatrix()));
|
||||
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
|
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
float f = (Float) animation.getAnimatedValue();
|
||||
evaluator.setFraction(f);
|
||||
drawable.setColorFilter(new ColorMatrixColorFilter(evaluator.getColorMatrix()));
|
||||
}
|
||||
});
|
||||
animator.setDuration(duration);
|
||||
animator.start();
|
||||
}
|
||||
|
||||
// From rnr.tests.imageloadingpattern.AlphaSatColorMatrixEvaluator
|
||||
// with modification
|
||||
private static class AlphaSatColorMatrixEvaluator {
|
||||
private final ColorMatrix colorMatrix;
|
||||
private final float[] elements = new float[20];
|
||||
private final boolean mFromDark;
|
||||
|
||||
public AlphaSatColorMatrixEvaluator(boolean fromDark) {
|
||||
colorMatrix = new ColorMatrix ();
|
||||
mFromDark = fromDark;
|
||||
}
|
||||
|
||||
public ColorMatrix getColorMatrix() {
|
||||
return colorMatrix;
|
||||
}
|
||||
|
||||
public void setFraction(float fraction) {
|
||||
// There are 4 phases so we multiply fraction by that amount
|
||||
float phase = fraction * 4;
|
||||
|
||||
// Compute the alpha change over period [0, 2]
|
||||
float alpha = Math.min(phase, 2.0f) / 2.0f;
|
||||
elements [19] = Math.round(alpha * 255);
|
||||
|
||||
// We substract to make the picture look darker, it will automatically clamp
|
||||
// This is spread over period [0, 3]
|
||||
final int MaxBlacker = 50;
|
||||
float blackening = Math.round((1 - Math.min(phase, 3.0f) / 3.0f) * MaxBlacker);
|
||||
elements [4] = elements [9] = elements [14] = mFromDark ? -blackening : blackening;
|
||||
|
||||
// Finally we desaturate over [0, 4], taken from ColorMatrix.SetSaturation
|
||||
float invSat = 1 - Math.max(0.2f, fraction);
|
||||
float R = 0.213f * invSat;
|
||||
float G = 0.715f * invSat;
|
||||
float B = 0.072f * invSat;
|
||||
|
||||
elements[0] = R + fraction; elements[1] = G; elements[2] = B;
|
||||
elements[5] = R; elements[6] = G + fraction; elements[7] = B;
|
||||
elements[10] = R; elements[11] = G; elements[12] = B + fraction;
|
||||
|
||||
colorMatrix.set(elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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.effect.ripple;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
/**
|
||||
* Draws a Material ripple.
|
||||
*/
|
||||
class Ripple {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = Ripple.class.getSimpleName();
|
||||
|
||||
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
private static final Interpolator DECEL_INTERPOLATOR = new LogInterpolator();
|
||||
|
||||
private static final float GLOBAL_SPEED = 1.0f;
|
||||
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
|
||||
|
||||
private static final long RIPPLE_ENTER_DELAY = 80;
|
||||
|
||||
private final RippleOwner mOwner;
|
||||
|
||||
/** Bounds used for computing max radius. */
|
||||
private final Rect mBounds;
|
||||
|
||||
/** Full-opacity color for drawing this ripple. */
|
||||
private int mColorOpaque;
|
||||
|
||||
/** Maximum ripple radius. */
|
||||
private float mOuterRadius;
|
||||
|
||||
/** Screen density used to adjust pixel-based velocities. */
|
||||
private float mDensity;
|
||||
|
||||
private float mStartingX;
|
||||
private float mStartingY;
|
||||
private float mClampedStartingX;
|
||||
private float mClampedStartingY;
|
||||
|
||||
// Software animators.
|
||||
private ObjectAnimator mAnimRadius;
|
||||
private ObjectAnimator mAnimOpacity;
|
||||
private ObjectAnimator mAnimX;
|
||||
private ObjectAnimator mAnimY;
|
||||
|
||||
// Software rendering properties.
|
||||
private float mOpacity = 1;
|
||||
private float mOuterX;
|
||||
private float mOuterY;
|
||||
|
||||
// Values used to tween between the start and end positions.
|
||||
private float mTweenRadius = 0;
|
||||
private float mTweenX = 0;
|
||||
private float mTweenY = 0;
|
||||
|
||||
/** Whether we have an explicit maximum radius. */
|
||||
private boolean mHasMaxRadius;
|
||||
|
||||
/** Whether we were canceled externally and should avoid self-removal. */
|
||||
private boolean mCanceled;
|
||||
|
||||
/**
|
||||
* Creates a new ripple.
|
||||
*/
|
||||
public Ripple(RippleOwner owner, Rect bounds, float startingX, float startingY) {
|
||||
mOwner = owner;
|
||||
mBounds = bounds;
|
||||
|
||||
mStartingX = startingX;
|
||||
mStartingY = startingY;
|
||||
}
|
||||
|
||||
public void setup(int maxRadius, int color, float density) {
|
||||
mColorOpaque = color | 0xFF000000;
|
||||
mOpacity = Color.alpha(color) / 2 / 255.0f;
|
||||
|
||||
if (maxRadius != RippleOwner.RADIUS_AUTO) {
|
||||
mHasMaxRadius = true;
|
||||
mOuterRadius = maxRadius;
|
||||
} else {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
}
|
||||
|
||||
mOuterX = 0;
|
||||
mOuterY = 0;
|
||||
mDensity = density;
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
|
||||
private void clampStartingPosition() {
|
||||
final float cX = mBounds.exactCenterX();
|
||||
final float cY = mBounds.exactCenterY();
|
||||
final float dX = mStartingX - cX;
|
||||
final float dY = mStartingY - cY;
|
||||
final float r = mOuterRadius;
|
||||
if (dX * dX + dY * dY > r * r) {
|
||||
// Point is outside the circle, clamp to the circumference.
|
||||
final double angle = Math.atan2(dY, dX);
|
||||
mClampedStartingX = cX + (float) (Math.cos(angle) * r);
|
||||
mClampedStartingY = cY + (float) (Math.sin(angle) * r);
|
||||
} else {
|
||||
mClampedStartingX = mStartingX;
|
||||
mClampedStartingY = mStartingY;
|
||||
}
|
||||
}
|
||||
|
||||
public void onHotspotBoundsChanged() {
|
||||
if (!mHasMaxRadius) {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
}
|
||||
|
||||
public void setOpacity(float a) {
|
||||
mOpacity = a;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getOpacity() {
|
||||
return mOpacity;
|
||||
}
|
||||
|
||||
public void setRadiusGravity(float r) {
|
||||
mTweenRadius = r;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getRadiusGravity() {
|
||||
return mTweenRadius;
|
||||
}
|
||||
|
||||
public void setXGravity(float x) {
|
||||
mTweenX = x;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getXGravity() {
|
||||
return mTweenX;
|
||||
}
|
||||
|
||||
public void setYGravity(float y) {
|
||||
mTweenY = y;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getYGravity() {
|
||||
return mTweenY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the ripple centered at (0,0) using the specified paint.
|
||||
*/
|
||||
public boolean draw(Canvas c, Paint p) {
|
||||
return drawSoftware(c, p);
|
||||
}
|
||||
|
||||
private boolean drawSoftware(Canvas c, Paint p) {
|
||||
boolean hasContent = false;
|
||||
|
||||
p.setColor(mColorOpaque);
|
||||
final int alpha = MathUtils.clamp((int) (255 * mOpacity + 0.5f), 0, 255);
|
||||
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
|
||||
if (alpha > 0 && radius > 0) {
|
||||
final float x = MathUtils.lerp(
|
||||
mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
|
||||
final float y = MathUtils.lerp(
|
||||
mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
|
||||
p.setAlpha(alpha);
|
||||
p.setStyle(Style.FILL);
|
||||
c.drawCircle(x, y, radius, p);
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
return hasContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum bounds of the ripple relative to the ripple center.
|
||||
*/
|
||||
public void getBounds(Rect bounds) {
|
||||
final int outerX = (int) mOuterX;
|
||||
final int outerY = (int) mOuterY;
|
||||
final int r = (int) mOuterRadius + 1;
|
||||
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the starting position relative to the drawable bounds. No-op if
|
||||
* the ripple has already entered.
|
||||
*/
|
||||
public void move(float x, float y) {
|
||||
mStartingX = x;
|
||||
mStartingY = y;
|
||||
|
||||
clampStartingPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the enter animation.
|
||||
*/
|
||||
public void enter() {
|
||||
cancel();
|
||||
|
||||
final int radiusDuration = (int)
|
||||
(1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
|
||||
|
||||
final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
|
||||
radius.setDuration(radiusDuration);
|
||||
radius.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
radius.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
|
||||
cX.setDuration(radiusDuration);
|
||||
cX.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
cX.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
|
||||
cY.setDuration(radiusDuration);
|
||||
cY.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
cY.setStartDelay(RIPPLE_ENTER_DELAY);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
radius.setAutoCancel(true);
|
||||
cX.setAutoCancel(true);
|
||||
cY.setAutoCancel(true);
|
||||
}
|
||||
|
||||
mAnimRadius = radius;
|
||||
mAnimX = cX;
|
||||
mAnimY = cY;
|
||||
|
||||
// Enter animations always run on the UI thread, since it's unlikely
|
||||
// that anything interesting is happening until the user lifts their
|
||||
// finger.
|
||||
radius.start();
|
||||
cX.start();
|
||||
cY.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the exit animation.
|
||||
*/
|
||||
public void exit() {
|
||||
cancel();
|
||||
|
||||
final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
|
||||
final float remaining;
|
||||
if (mAnimRadius != null && mAnimRadius.isRunning()) {
|
||||
remaining = mOuterRadius - radius;
|
||||
} else {
|
||||
remaining = mOuterRadius;
|
||||
}
|
||||
|
||||
final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
|
||||
+ WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
|
||||
final int opacityDuration = (int) (1000 * 1 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
|
||||
|
||||
exitSoftware(radiusDuration, opacityDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump all animations to their end state. The caller is responsible for
|
||||
* removing the ripple from the list of animating ripples.
|
||||
*/
|
||||
public void jump() {
|
||||
mCanceled = true;
|
||||
endSoftwareAnimations();
|
||||
mCanceled = false;
|
||||
}
|
||||
|
||||
private void endSoftwareAnimations() {
|
||||
if (mAnimRadius != null) {
|
||||
mAnimRadius.end();
|
||||
mAnimRadius = null;
|
||||
}
|
||||
|
||||
if (mAnimOpacity != null) {
|
||||
mAnimOpacity.end();
|
||||
mAnimOpacity = null;
|
||||
}
|
||||
|
||||
if (mAnimX != null) {
|
||||
mAnimX.end();
|
||||
mAnimX = null;
|
||||
}
|
||||
|
||||
if (mAnimY != null) {
|
||||
mAnimY.end();
|
||||
mAnimY = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void exitSoftware(int radiusDuration, int opacityDuration) {
|
||||
final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
|
||||
radiusAnim.setDuration(radiusDuration);
|
||||
radiusAnim.setInterpolator(DECEL_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
|
||||
xAnim.setDuration(radiusDuration);
|
||||
xAnim.setInterpolator(DECEL_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
|
||||
yAnim.setDuration(radiusDuration);
|
||||
yAnim.setInterpolator(DECEL_INTERPOLATOR);
|
||||
|
||||
final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
|
||||
opacityAnim.setDuration(opacityDuration);
|
||||
opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
opacityAnim.addListener(mAnimationListener);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
radiusAnim.setAutoCancel(true);
|
||||
xAnim.setAutoCancel(true);
|
||||
yAnim.setAutoCancel(true);
|
||||
opacityAnim.setAutoCancel(true);
|
||||
}
|
||||
|
||||
mAnimRadius = radiusAnim;
|
||||
mAnimOpacity = opacityAnim;
|
||||
mAnimX = xAnim;
|
||||
mAnimY = yAnim;
|
||||
|
||||
radiusAnim.start();
|
||||
opacityAnim.start();
|
||||
xAnim.start();
|
||||
yAnim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all animations. The caller is responsible for removing
|
||||
* the ripple from the list of animating ripples.
|
||||
*/
|
||||
public void cancel() {
|
||||
mCanceled = true;
|
||||
cancelSoftwareAnimations();
|
||||
mCanceled = false;
|
||||
}
|
||||
|
||||
private void cancelSoftwareAnimations() {
|
||||
if (mAnimRadius != null) {
|
||||
mAnimRadius.cancel();
|
||||
mAnimRadius = null;
|
||||
}
|
||||
|
||||
if (mAnimOpacity != null) {
|
||||
mAnimOpacity.cancel();
|
||||
mAnimOpacity = null;
|
||||
}
|
||||
|
||||
if (mAnimX != null) {
|
||||
mAnimX.cancel();
|
||||
mAnimX = null;
|
||||
}
|
||||
|
||||
if (mAnimY != null) {
|
||||
mAnimY.cancel();
|
||||
mAnimY = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeSelf() {
|
||||
// The owner will invalidate itself.
|
||||
if (!mCanceled) {
|
||||
mOwner.removeRipple(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateSelf() {
|
||||
mOwner.invalidateSelf();
|
||||
}
|
||||
|
||||
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
removeSelf();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Interpolator with a smooth log deceleration
|
||||
*/
|
||||
private static final class LogInterpolator implements Interpolator {
|
||||
@Override
|
||||
public float getInterpolation(float input) {
|
||||
return 1 - (float) Math.pow(400, -input * 1.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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
|
||||
* 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.effect.ripple;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Paint.Style;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
/**
|
||||
* Draws a Material ripple.
|
||||
*/
|
||||
class RippleBackground {
|
||||
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
|
||||
|
||||
private static final float GLOBAL_SPEED = 1.0f;
|
||||
private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_OPACITY_ENTER_VELOCITY = 10.0f * GLOBAL_SPEED;
|
||||
private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f;
|
||||
private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f;
|
||||
|
||||
private final RippleOwner mOwner;
|
||||
|
||||
/** Bounds used for computing max radius. */
|
||||
private final Rect mBounds;
|
||||
|
||||
/** Full-opacity color for drawing this ripple. */
|
||||
private int mColorOpaque;
|
||||
|
||||
/** Maximum alpha value for drawing this ripple. */
|
||||
private int mColorAlpha;
|
||||
|
||||
/** Maximum ripple radius. */
|
||||
private float mOuterRadius;
|
||||
|
||||
/** Screen density used to adjust pixel-based velocities. */
|
||||
private float mDensity;
|
||||
|
||||
// Software animators.
|
||||
private ObjectAnimator mAnimOuterOpacity;
|
||||
|
||||
// Software rendering properties.
|
||||
private float mOuterOpacity = 0;
|
||||
private float mOuterX;
|
||||
private float mOuterY;
|
||||
|
||||
/** Whether we have an explicit maximum radius. */
|
||||
private boolean mHasMaxRadius;
|
||||
|
||||
/**
|
||||
* Creates a new ripple.
|
||||
*/
|
||||
public RippleBackground(RippleOwner owner, Rect bounds) {
|
||||
mOwner = owner;
|
||||
mBounds = bounds;
|
||||
}
|
||||
|
||||
public void setup(int maxRadius, int color, float density) {
|
||||
mColorOpaque = color | 0xFF000000;
|
||||
mColorAlpha = Color.alpha(color) / 2;
|
||||
|
||||
if (maxRadius != RippleOwner.RADIUS_AUTO) {
|
||||
mHasMaxRadius = true;
|
||||
mOuterRadius = maxRadius;
|
||||
} else {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
}
|
||||
|
||||
mOuterX = 0;
|
||||
mOuterY = 0;
|
||||
mDensity = density;
|
||||
}
|
||||
|
||||
public void onHotspotBoundsChanged() {
|
||||
if (!mHasMaxRadius) {
|
||||
final float halfWidth = mBounds.width() / 2.0f;
|
||||
final float halfHeight = mBounds.height() / 2.0f;
|
||||
mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOuterOpacity(float a) {
|
||||
mOuterOpacity = a;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public float getOuterOpacity() {
|
||||
return mOuterOpacity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the ripple centered at (0,0) using the specified paint.
|
||||
*/
|
||||
public boolean draw(Canvas c, Paint p) {
|
||||
return drawSoftware(c, p);
|
||||
}
|
||||
|
||||
public boolean shouldDraw() {
|
||||
final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
|
||||
return outerAlpha > 0 && mOuterRadius > 0;
|
||||
}
|
||||
|
||||
private boolean drawSoftware(Canvas c, Paint p) {
|
||||
boolean hasContent = false;
|
||||
|
||||
p.setColor(mColorOpaque);
|
||||
final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
|
||||
if (outerAlpha > 0 && mOuterRadius > 0) {
|
||||
p.setAlpha(outerAlpha);
|
||||
p.setStyle(Style.FILL);
|
||||
c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
|
||||
hasContent = true;
|
||||
}
|
||||
|
||||
return hasContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum bounds of the ripple relative to the ripple center.
|
||||
*/
|
||||
public void getBounds(Rect bounds) {
|
||||
final int outerX = (int) mOuterX;
|
||||
final int outerY = (int) mOuterY;
|
||||
final int r = (int) mOuterRadius + 1;
|
||||
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the enter animation.
|
||||
*/
|
||||
public void enter() {
|
||||
cancel();
|
||||
|
||||
final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY);
|
||||
final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
|
||||
outer.setDuration(outerDuration);
|
||||
outer.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
outer.setAutoCancel(true);
|
||||
}
|
||||
|
||||
mAnimOuterOpacity = outer;
|
||||
|
||||
// Enter animations always run on the UI thread, since it's unlikely
|
||||
// that anything interesting is happening until the user lifts their
|
||||
// finger.
|
||||
outer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the exit animation.
|
||||
*/
|
||||
public void exit() {
|
||||
cancel();
|
||||
|
||||
// Scale the outer max opacity and opacity velocity based
|
||||
// on the size of the outer radius.
|
||||
final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
|
||||
final float outerSizeInfluence = MathUtils.clamp(
|
||||
(mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity)
|
||||
/ (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1);
|
||||
final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN,
|
||||
WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX, outerSizeInfluence);
|
||||
|
||||
// Determine at what time the inner and outer opacity intersect.
|
||||
// inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000
|
||||
// outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
|
||||
final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
|
||||
/ (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
|
||||
final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
|
||||
+ inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);
|
||||
|
||||
exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump all animations to their end state. The caller is responsible for
|
||||
* removing the ripple from the list of animating ripples.
|
||||
*/
|
||||
public void jump() {
|
||||
endSoftwareAnimations();
|
||||
}
|
||||
|
||||
private void endSoftwareAnimations() {
|
||||
if (mAnimOuterOpacity != null) {
|
||||
mAnimOuterOpacity.end();
|
||||
mAnimOuterOpacity = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) {
|
||||
final ObjectAnimator outerOpacityAnim;
|
||||
if (inflectionDuration > 0) {
|
||||
// Outer opacity continues to increase for a bit.
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this,
|
||||
"outerOpacity", inflectionOpacity / 255.0f);
|
||||
outerOpacityAnim.setDuration(inflectionDuration);
|
||||
outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
}
|
||||
|
||||
// Chain the outer opacity exit animation.
|
||||
final int outerDuration = opacityDuration - inflectionDuration;
|
||||
if (outerDuration > 0) {
|
||||
outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(
|
||||
RippleBackground.this, "outerOpacity", 0);
|
||||
outerFadeOutAnim.setDuration(outerDuration);
|
||||
outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
outerFadeOutAnim.setAutoCancel(true);
|
||||
}
|
||||
|
||||
mAnimOuterOpacity = outerFadeOutAnim;
|
||||
|
||||
outerFadeOutAnim.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
animation.removeListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
|
||||
outerOpacityAnim.setDuration(opacityDuration);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
outerOpacityAnim.setAutoCancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
mAnimOuterOpacity = outerOpacityAnim;
|
||||
|
||||
outerOpacityAnim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all animations. The caller is responsible for removing
|
||||
* the ripple from the list of animating ripples.
|
||||
*/
|
||||
public void cancel() {
|
||||
cancelSoftwareAnimations();
|
||||
}
|
||||
|
||||
private void cancelSoftwareAnimations() {
|
||||
if (mAnimOuterOpacity != null) {
|
||||
mAnimOuterOpacity.cancel();
|
||||
mAnimOuterOpacity = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateSelf() {
|
||||
mOwner.invalidateSelf();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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
|
||||
* 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.effect.ripple;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.widget.Hotspotable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Drawable that shows a ripple effect in response to state changes. The
|
||||
* anchoring position of the ripple for a given state may be specified by
|
||||
* calling {@link #setHotspot(float, float)} with the corresponding state
|
||||
* attribute identifier.
|
||||
* <p>
|
||||
* A touch feedback drawable may contain multiple child layers, including a
|
||||
* special mask layer that is not drawn to the screen. A single layer may be set
|
||||
* as the mask by specifying its android:id value as {@link android.R.id#mask}.
|
||||
* <pre>
|
||||
* <code><!-- A red ripple masked against an opaque rectangle. --/>
|
||||
* <ripple android:color="#ffff0000">
|
||||
* <item android:id="@android:id/mask"
|
||||
* android:drawable="@android:color/white" />
|
||||
* <ripple /></code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* If a mask layer is set, the ripple effect will be masked against that layer
|
||||
* before it is drawn over the composite of the remaining child layers.
|
||||
* <p>
|
||||
* If no mask layer is set, the ripple effect is masked against the composite
|
||||
* of the child layers.
|
||||
* <pre>
|
||||
* <code><!-- A blue ripple drawn atop a black rectangle. --/>
|
||||
* <ripple android:color="#ff00ff00">
|
||||
* <item android:drawable="@android:color/black" />
|
||||
* <ripple />
|
||||
*
|
||||
* <!-- A red ripple drawn atop a drawable resource. --/>
|
||||
* <ripple android:color="#ff00ff00">
|
||||
* <item android:drawable="@drawable/my_drawable" />
|
||||
* <ripple /></code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* If no child layers or mask is specified and the ripple is set as a View
|
||||
* background, the ripple will be drawn atop the first available parent
|
||||
* background within the View's hierarchy. In this case, the drawing region
|
||||
* may extend outside of the Drawable bounds.
|
||||
* <pre>
|
||||
* <code><!-- An unbounded green ripple. --/>
|
||||
* <ripple android:color="#ff0000ff" /></code>
|
||||
* </pre>
|
||||
*
|
||||
* @attr ref android.R.styleable#RippleDrawable_color
|
||||
*/
|
||||
public class RippleDrawable extends Drawable implements RippleOwner, Hotspotable {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = RippleDrawable.class.getSimpleName();
|
||||
|
||||
/** The maximum number of ripples supported. */
|
||||
private static final int MAX_RIPPLES = 10;
|
||||
|
||||
/** Current ripple effect bounds, used to constrain ripple effects. */
|
||||
private final Rect mHotspotBounds = new Rect();
|
||||
|
||||
/** The current background. May be actively animating or pending entry. */
|
||||
private RippleBackground mBackground;
|
||||
|
||||
/** Whether we expect to draw a background when visible. */
|
||||
private boolean mBackgroundActive;
|
||||
|
||||
/** The current ripple. May be actively animating or pending entry. */
|
||||
private Ripple mRipple;
|
||||
|
||||
/** Whether we expect to draw a ripple when visible. */
|
||||
private boolean mRippleActive;
|
||||
|
||||
// Hotspot coordinates that are awaiting activation.
|
||||
private float mPendingX;
|
||||
private float mPendingY;
|
||||
private boolean mHasPending;
|
||||
|
||||
/**
|
||||
* Lazily-created array of actively animating ripples. Inactive ripples are
|
||||
* pruned during draw(). The locations of these will not change.
|
||||
*/
|
||||
private Ripple[] mExitingRipples;
|
||||
private int mExitingRipplesCount = 0;
|
||||
|
||||
/** Paint used to control appearance of ripples. */
|
||||
private Paint mRipplePaint;
|
||||
|
||||
/** Target density of the display into which ripples are drawn. */
|
||||
private final float mDensity = 1.0f;
|
||||
|
||||
/** Whether bounds are being overridden. */
|
||||
private boolean mOverrideBounds;
|
||||
|
||||
/**
|
||||
* Whether the next draw MUST draw something to canvas. Used to work around
|
||||
* a bug in hardware invalidation following a render thread-accelerated
|
||||
* animation.
|
||||
*/
|
||||
private boolean mNeedsDraw;
|
||||
|
||||
private ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA);
|
||||
|
||||
private int mMaxRadius = RADIUS_AUTO;
|
||||
|
||||
private final Drawable mContent;
|
||||
|
||||
RippleDrawable(ColorStateList color, Drawable content) {
|
||||
setColor(color);
|
||||
|
||||
mContent = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public void jumpToCurrentState() {
|
||||
if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
|
||||
super.jumpToCurrentState();
|
||||
}
|
||||
|
||||
boolean needsDraw = false;
|
||||
|
||||
if (mRipple != null) {
|
||||
mRipple.jump();
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.jump();
|
||||
}
|
||||
|
||||
needsDraw |= cancelExitingRipples();
|
||||
|
||||
mNeedsDraw = needsDraw;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private boolean cancelExitingRipples() {
|
||||
final int count = mExitingRipplesCount;
|
||||
final Ripple[] ripples = mExitingRipples;
|
||||
for (int i = 0; i < count; i++) {
|
||||
ripples[i].cancel();
|
||||
}
|
||||
|
||||
if (ripples != null) {
|
||||
Arrays.fill(ripples, 0, count, null);
|
||||
}
|
||||
mExitingRipplesCount = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mColor.withAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return Color.alpha(mColor.getDefaultColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
//TODO Paint.setColorFilter(filter) ?
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
// Worst-case scenario.
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onStateChange(int[] stateSet) {
|
||||
boolean changed = false;
|
||||
if (mContent != null)
|
||||
changed |= mContent.setState(stateSet);
|
||||
|
||||
boolean enabled = false;
|
||||
boolean pressed = false;
|
||||
boolean focused = false;
|
||||
|
||||
for (int state : stateSet) {
|
||||
if (state == android.R.attr.state_enabled) {
|
||||
enabled = true;
|
||||
}
|
||||
if (state == android.R.attr.state_focused) {
|
||||
focused = true;
|
||||
}
|
||||
if (state == android.R.attr.state_pressed) {
|
||||
pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
setRippleActive(enabled && pressed);
|
||||
setBackgroundActive(focused || (enabled && pressed));
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void setRippleActive(boolean active) {
|
||||
if (mRippleActive != active) {
|
||||
mRippleActive = active;
|
||||
if (active) {
|
||||
tryRippleEnter();
|
||||
} else {
|
||||
tryRippleExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setBackgroundActive(boolean active) {
|
||||
if (mBackgroundActive != active) {
|
||||
mBackgroundActive = active;
|
||||
if (active) {
|
||||
tryBackgroundEnter();
|
||||
} else {
|
||||
tryBackgroundExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
|
||||
if (!mOverrideBounds) {
|
||||
mHotspotBounds.set(bounds);
|
||||
onHotspotBoundsChanged();
|
||||
}
|
||||
|
||||
if (mContent != null)
|
||||
mContent.setBounds(bounds);
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setVisible(boolean visible, boolean restart) {
|
||||
final boolean changed = super.setVisible(visible, restart);
|
||||
|
||||
if (!visible) {
|
||||
clearHotspots();
|
||||
} else if (changed) {
|
||||
// If we just became visible, ensure the background and ripple
|
||||
// visibilities are consistent with their internal states.
|
||||
if (mRippleActive) {
|
||||
tryRippleEnter();
|
||||
}
|
||||
|
||||
if (mBackgroundActive) {
|
||||
tryBackgroundEnter();
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setColor(ColorStateList color) {
|
||||
int alpha = Color.alpha(color.getDefaultColor());
|
||||
mColor = color.withAlpha((int) (alpha * (1 - 0.001f * alpha)));
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHotspot(float x, float y) {
|
||||
if (mRipple == null || mBackground == null) {
|
||||
mPendingX = x;
|
||||
mPendingY = y;
|
||||
mHasPending = true;
|
||||
}
|
||||
|
||||
if (mRipple != null) {
|
||||
mRipple.move(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an active hotspot at the specified location.
|
||||
*/
|
||||
private void tryBackgroundEnter() {
|
||||
if (mBackground == null) {
|
||||
mBackground = new RippleBackground(this, mHotspotBounds);
|
||||
}
|
||||
|
||||
final int color = mColor.getColorForState(getState(), Color.TRANSPARENT);
|
||||
mBackground.setup(mMaxRadius, color, mDensity);
|
||||
mBackground.enter();
|
||||
}
|
||||
|
||||
private void tryBackgroundExit() {
|
||||
if (mBackground != null) {
|
||||
// Don't null out the background, we need it to draw!
|
||||
mBackground.exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start an enter animation for the active hotspot. Fails if
|
||||
* there are too many animating ripples.
|
||||
*/
|
||||
private void tryRippleEnter() {
|
||||
if (mExitingRipplesCount >= MAX_RIPPLES) {
|
||||
// This should never happen unless the user is tapping like a maniac
|
||||
// or there is a bug that's preventing ripples from being removed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRipple == null) {
|
||||
final float x;
|
||||
final float y;
|
||||
if (mHasPending) {
|
||||
mHasPending = false;
|
||||
x = mPendingX;
|
||||
y = mPendingY;
|
||||
} else {
|
||||
x = mHotspotBounds.exactCenterX();
|
||||
y = mHotspotBounds.exactCenterY();
|
||||
}
|
||||
mRipple = new Ripple(this, mHotspotBounds, x, y);
|
||||
}
|
||||
|
||||
final int color = mColor.getColorForState(getState(), Color.TRANSPARENT);
|
||||
mRipple.setup(mMaxRadius, color, mDensity);
|
||||
mRipple.enter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start an exit animation for the active hotspot. Fails if
|
||||
* there is no active hotspot.
|
||||
*/
|
||||
private void tryRippleExit() {
|
||||
if (mRipple != null) {
|
||||
if (mExitingRipples == null) {
|
||||
mExitingRipples = new Ripple[MAX_RIPPLES];
|
||||
}
|
||||
mExitingRipples[mExitingRipplesCount++] = mRipple;
|
||||
mRipple.exit();
|
||||
mRipple = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels and removes the active ripple, all exiting ripples, and the
|
||||
* background. Nothing will be drawn after this method is called.
|
||||
*/
|
||||
private void clearHotspots() {
|
||||
boolean needsDraw = false;
|
||||
|
||||
if (mRipple != null) {
|
||||
mRipple.cancel();
|
||||
mRipple = null;
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.cancel();
|
||||
mBackground = null;
|
||||
}
|
||||
|
||||
needsDraw |= cancelExitingRipples();
|
||||
|
||||
mNeedsDraw = needsDraw;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHotspotBounds(int left, int top, int right, int bottom) {
|
||||
mOverrideBounds = true;
|
||||
mHotspotBounds.set(left, top, right, bottom);
|
||||
|
||||
onHotspotBoundsChanged();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void getHotspotBounds(Rect outRect) {
|
||||
outRect.set(mHotspotBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all the animating ripples that the hotspot bounds have changed.
|
||||
*/
|
||||
private void onHotspotBoundsChanged() {
|
||||
final int count = mExitingRipplesCount;
|
||||
final Ripple[] ripples = mExitingRipples;
|
||||
for (int i = 0; i < count; i++) {
|
||||
ripples[i].onHotspotBoundsChanged();
|
||||
}
|
||||
|
||||
if (mRipple != null) {
|
||||
mRipple.onHotspotBoundsChanged();
|
||||
}
|
||||
|
||||
if (mBackground != null) {
|
||||
mBackground.onHotspotBoundsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
final boolean drawNonMaskContent = mContent != null;
|
||||
final Rect bounds = getDirtyBounds();
|
||||
final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
|
||||
canvas.clipRect(bounds);
|
||||
|
||||
// If we have content, draw it into a layer first.
|
||||
if (drawNonMaskContent) {
|
||||
drawContentLayer(canvas, bounds);
|
||||
}
|
||||
|
||||
// If we have a background and a non-opaque mask, draw the masking layer.
|
||||
drawBackgroundLayer(canvas, bounds);
|
||||
|
||||
// If we have ripples and a non-opaque mask, draw the masking layer.
|
||||
drawRippleLayer(canvas, bounds);
|
||||
|
||||
// If we failed to draw anything and we just canceled animations, at
|
||||
// least draw a color so that hardware invalidation works correctly.
|
||||
if (mNeedsDraw) {
|
||||
canvas.drawColor(Color.TRANSPARENT);
|
||||
|
||||
// Request another draw so we can avoid adding a transparent layer
|
||||
// during the next display list refresh.
|
||||
invalidateSelf();
|
||||
}
|
||||
mNeedsDraw = false;
|
||||
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a ripple from the exiting ripple list.
|
||||
*
|
||||
* @param ripple the ripple to remove
|
||||
*/
|
||||
@Override
|
||||
public void removeRipple(Ripple ripple) {
|
||||
// Ripple ripple ripple ripple. Ripple ripple.
|
||||
final Ripple[] ripples = mExitingRipples;
|
||||
final int count = mExitingRipplesCount;
|
||||
final int index = getRippleIndex(ripple);
|
||||
if (index >= 0) {
|
||||
System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1));
|
||||
ripples[count - 1] = null;
|
||||
mExitingRipplesCount--;
|
||||
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private int getRippleIndex(Ripple ripple) {
|
||||
final Ripple[] ripples = mExitingRipples;
|
||||
final int count = mExitingRipplesCount;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ripples[i] == ripple) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void drawContentLayer(Canvas canvas, Rect bounds) {
|
||||
mContent.draw(canvas);
|
||||
}
|
||||
|
||||
private void drawBackgroundLayer(
|
||||
Canvas canvas, Rect bounds) {
|
||||
if (mBackground != null && mBackground.shouldDraw()) {
|
||||
final float x = mHotspotBounds.exactCenterX();
|
||||
final float y = mHotspotBounds.exactCenterY();
|
||||
canvas.translate(x, y);
|
||||
mBackground.draw(canvas, getRipplePaint());
|
||||
canvas.translate(-x, -y);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawRippleLayer(Canvas canvas, Rect bounds) {
|
||||
int restoreTranslate = -1;
|
||||
|
||||
// Translate the canvas to the current hotspot bounds.
|
||||
restoreTranslate = canvas.save();
|
||||
canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
|
||||
|
||||
// Draw ripples and update the animating ripples array.
|
||||
final int count = mExitingRipplesCount;
|
||||
final Ripple[] ripples = mExitingRipples;
|
||||
for (int i = 0; i <= count; i++) {
|
||||
final Ripple ripple;
|
||||
if (i < count) {
|
||||
ripple = ripples[i];
|
||||
} else if (mRipple != null) {
|
||||
ripple = mRipple;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
ripple.draw(canvas, getRipplePaint());
|
||||
}
|
||||
|
||||
// Always restore the translation.
|
||||
if (restoreTranslate >= 0) {
|
||||
canvas.restoreToCount(restoreTranslate);
|
||||
}
|
||||
}
|
||||
|
||||
private Paint getRipplePaint() {
|
||||
if (mRipplePaint == null) {
|
||||
mRipplePaint = new Paint();
|
||||
mRipplePaint.setAntiAlias(true);
|
||||
}
|
||||
return mRipplePaint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rect getDirtyBounds() {
|
||||
return getBounds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum ripple radius in pixels. The default value of
|
||||
* {@link #RADIUS_AUTO} defines the radius as the distance from the center
|
||||
* of the drawable bounds (or hotspot bounds, if specified) to a corner.
|
||||
*
|
||||
* @param maxRadius the maximum ripple radius in pixels or
|
||||
* {@link #RADIUS_AUTO} to automatically determine the maximum
|
||||
* radius based on the bounds
|
||||
* @see #getMaxRadius()
|
||||
* @see #setHotspotBounds(int, int, int, int)
|
||||
* @hide
|
||||
*/
|
||||
public void setMaxRadius(int maxRadius) {
|
||||
if (maxRadius != RADIUS_AUTO && maxRadius < 0) {
|
||||
throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0");
|
||||
}
|
||||
|
||||
mMaxRadius = maxRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if
|
||||
* the radius is determined automatically
|
||||
* @see #setMaxRadius(int)
|
||||
* @hide
|
||||
*/
|
||||
public int getMaxRadius() {
|
||||
return mMaxRadius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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
|
||||
* 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.effect.ripple;
|
||||
|
||||
|
||||
public interface RippleOwner {
|
||||
/**
|
||||
* Constant for automatically determining the maximum ripple radius.
|
||||
*/
|
||||
public static final int RADIUS_AUTO = -1;
|
||||
|
||||
public void removeRipple(Ripple ripple);
|
||||
|
||||
public void invalidateSelf();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.effect.ripple;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import com.hippo.ehviewer.widget.HotspotTouchHelper;
|
||||
|
||||
|
||||
public final class RippleSalon {
|
||||
|
||||
private static final boolean USE_OLD_RIPPLE =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
|
||||
|
||||
private static final Drawable MASK = new ColorDrawable(Color.BLACK);
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void addRipple(View v, ColorStateList color) {
|
||||
addRipple(v, color, v.getBackground());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void addRipple(View v, ColorStateList color, Drawable content) {
|
||||
if (USE_OLD_RIPPLE) {
|
||||
RippleDrawable rippleDrawable = new RippleDrawable(color, content);
|
||||
v.setOnTouchListener(new HotspotTouchHelper(rippleDrawable));
|
||||
v.setBackgroundDrawable(rippleDrawable);
|
||||
} else {
|
||||
android.graphics.drawable.RippleDrawable rippleDrawable = new android.graphics.drawable.RippleDrawable(color, content, MASK);
|
||||
v.setBackgroundDrawable(rippleDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
public class AddToFavoriteParser {
|
||||
|
||||
public boolean parser(String body) {
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
if (body.contains("Close Window"))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hippo.ehviewer.data.Comment;
|
||||
import com.hippo.ehviewer.data.LargePreviewList;
|
||||
import com.hippo.ehviewer.data.NormalPreviewList;
|
||||
import com.hippo.ehviewer.data.PreviewList;
|
||||
import com.hippo.ehviewer.util.EhUtils;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class DetailParser {
|
||||
|
||||
private static final String OFFENSIVE_STRING =
|
||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
||||
private static final String PINING_STRING =
|
||||
"<p>This gallery is pining for the fjords.</p>";
|
||||
|
||||
public static final int DETAIL = 0x1;
|
||||
public static final int TAG = 0x2;
|
||||
public static final int PREVIEW_INFO = 0x4;
|
||||
public static final int PREVIEW = 0x8;
|
||||
public static final int COMMENT = 0x10;
|
||||
public static final int OFFENSIVE = 0x20;
|
||||
public static final int PINING = 0x40;
|
||||
public static final int ERROR = 0x80;
|
||||
|
||||
private static final CommentSort cs = new CommentSort();
|
||||
|
||||
public String eMesg;
|
||||
public String thumb;
|
||||
public String title;
|
||||
public String title_jpn;
|
||||
public int category;
|
||||
public String uploader;
|
||||
public String posted;
|
||||
public int pages;
|
||||
public String size;
|
||||
public String resized;
|
||||
public String parent;
|
||||
public String visible;
|
||||
public String language;
|
||||
public int people;
|
||||
public float rating;
|
||||
public String firstPage;
|
||||
public int previewPerPage;
|
||||
public int previewSum;
|
||||
public LinkedHashMap<String, LinkedList<String>> tags;
|
||||
public PreviewList previewList;
|
||||
public LinkedList<Comment> comments;
|
||||
|
||||
public void reset() {
|
||||
eMesg = null;
|
||||
thumb = null;
|
||||
title = null;
|
||||
title_jpn = null;
|
||||
category = 0;
|
||||
uploader = null;
|
||||
posted = null;
|
||||
pages = 0;
|
||||
size = null;
|
||||
resized = null;
|
||||
parent = null;
|
||||
visible = null;
|
||||
language = null;
|
||||
people = 0;
|
||||
rating = 0;
|
||||
firstPage = null;
|
||||
previewPerPage = 0;
|
||||
previewSum = 0;
|
||||
tags = null;
|
||||
previewList = null;
|
||||
comments = null;
|
||||
}
|
||||
|
||||
public int parser(String body, int mode) {
|
||||
int re = 0;
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return 0;
|
||||
|
||||
if (!body.contains("<")) {
|
||||
eMesg = body;
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
return OFFENSIVE;
|
||||
}
|
||||
|
||||
if (body.contains(PINING_STRING)) {
|
||||
return PINING;
|
||||
}
|
||||
|
||||
p = Pattern.compile("<div class=\"d\">\n<p>([^<]+)</p>");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
eMesg = m.group(1);
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
// Get detail
|
||||
if ((mode & DETAIL) != 0) {
|
||||
p = Pattern
|
||||
.compile("<div id=\"gd1\"><img src=\"([^\"]+)\"[^<>]+/></div>" // thumb
|
||||
+ "</div>"
|
||||
+ "<div id=\"gd2\">"
|
||||
+ "<h1 id=\"gn\">([^<>]+)</h1>" // title
|
||||
+ "<h1 id=\"gj\">([^<>]*)</h1>" // title_jpn might be empty string
|
||||
+ "</div>"
|
||||
+ ".+"
|
||||
+ "<div id=\"gdc\"><a[^<>]+><[^<>]*alt=\"([\\w|\\-]+)\"[^<>]*/></a></div>" // category
|
||||
+ "<div id=\"gdn\"><a[^<>]+>([^<>]+)</a>" // uploader
|
||||
+ ".+"
|
||||
+ "<tr><td[^<>]*>Posted:</td><td[^<>]*>([\\w|\\-|\\s|:]+)</td></tr>" // posted
|
||||
+ "<tr><td[^<>]*>Images:</td><td[^<>]*>([\\d]+) @ ([\\w|\\.|\\s]+)</td></tr>" // pages and size
|
||||
+ "<tr><td[^<>]*>Resized:</td><td[^<>]*>([^<>]+)</td></tr>" // resized
|
||||
+ "<tr><td[^<>]*>Parent:</td><td[^<>]*>(?:<a[^<>]*>)?([^<>]+)(?:</a>)?</td></tr>" // parent
|
||||
+ "<tr><td[^<>]*>Visible:</td><td[^<>]*>([^<>]+)</td></tr>" // visible
|
||||
+ "<tr><td[^<>]*>Language:</td><td[^<>]*>([^<>]+)</td></tr>" // language
|
||||
+ ".+"
|
||||
+ "<td id=\"grt3\">\\(<span id=\"rating_count\">([\\d|,]+)</span>\\)</td>" // people
|
||||
+ "</tr>"
|
||||
+ "<tr><td[^<>]*>([^<>]+)</td>" // rating
|
||||
+ ".+"
|
||||
+ "<div id=\"gdt\"><div[^<>]*>(?:<div[^<>]*>)?<a[^<>]*href=\"([^<>\"]+)\"[^<>]*>"); // get firstPage
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
re |= DETAIL;
|
||||
|
||||
thumb = Utils.unescapeXml(m.group(1));
|
||||
title = Utils.unescapeXml(m.group(2));
|
||||
title_jpn = Utils.unescapeXml(m.group(3));
|
||||
category = EhUtils.getCategory(m.group(4));
|
||||
uploader = m.group(5);
|
||||
posted = m.group(6);
|
||||
pages = Integer.parseInt(m.group(7));
|
||||
size = m.group(8);
|
||||
resized = m.group(9);
|
||||
parent = m.group(10);
|
||||
visible = m.group(11);
|
||||
language = m.group(12);
|
||||
people = Integer.parseInt(m.group(13).replace(",", ""));
|
||||
|
||||
Pattern pattern = Pattern.compile("([\\d|\\.]+)");
|
||||
Matcher matcher = pattern.matcher(m.group(14));
|
||||
if (matcher.find())
|
||||
rating = Float.parseFloat(matcher.group(1));
|
||||
else
|
||||
rating = Float.NaN;
|
||||
|
||||
firstPage = m.group(15);
|
||||
}
|
||||
}
|
||||
// Get tag
|
||||
if ((mode & TAG) != 0) {
|
||||
tags = new LinkedHashMap<String, LinkedList<String>>();
|
||||
p = Pattern
|
||||
.compile("<tr><td[^<>]+>([\\w\\s]+):</td><td>(?:<div[^<>]+><a[^<>]+>[\\w\\s]+</a></div>)+</td></tr>");
|
||||
m = p.matcher(body);
|
||||
while (m.find()) {
|
||||
re |= TAG;
|
||||
String groupName = m.group(1);
|
||||
LinkedList<String> group = getTagGroup(m.group(0));
|
||||
if (group != null) {
|
||||
tags.put(groupName, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get preview info
|
||||
if ((mode & PREVIEW_INFO) != 0) {
|
||||
p = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
re |= PREVIEW_INFO;
|
||||
previewSum = Integer.valueOf(m.group(1).replace(",", ""));
|
||||
}
|
||||
}
|
||||
|
||||
// Get preview
|
||||
if ((mode & PREVIEW) != 0) {
|
||||
boolean isLargePreview = false;
|
||||
if (body.contains("<div class=\"gdtl\""))
|
||||
isLargePreview = true;
|
||||
|
||||
if (isLargePreview) {
|
||||
previewList = new LargePreviewList();
|
||||
p = Pattern.compile("<div class=\"gdtl\".+?<a href=\"(.+?)\"><img.+?src=\"(.+?)\"");
|
||||
m = p.matcher(body);
|
||||
while (m.find()) {
|
||||
re |= PREVIEW;
|
||||
((LargePreviewList)previewList).addItem(m.group(2), m.group(1));
|
||||
}
|
||||
} else {
|
||||
previewList = new NormalPreviewList();
|
||||
p = Pattern.compile("<div[^<>]*class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*>");
|
||||
m = p.matcher(body);
|
||||
while (m.find()) {
|
||||
re |= PREVIEW;
|
||||
((NormalPreviewList)previewList).addItem(m.group(3), m.group(4), "0", m.group(1),
|
||||
m.group(2), m.group(5));
|
||||
}
|
||||
}
|
||||
// Set previewPerPage
|
||||
if (previewList != null)
|
||||
previewPerPage = previewList.size();
|
||||
}
|
||||
// Get comment
|
||||
if ((mode & COMMENT) != 0) {
|
||||
p = Pattern
|
||||
.compile("<div class=\"c3\">Posted on ([^<>]+) by <a[^<>]+>([^<>]+)</a>.+?<div class=\"c6\"[^>]*>(.+?)</div><div class=\"c[78]\"");
|
||||
m = p.matcher(body);
|
||||
comments = new LinkedList<Comment>();
|
||||
while (m.find()) {
|
||||
re |= COMMENT;
|
||||
comments.add(new Comment(m.group(1), m.group(2), m.group(3)));
|
||||
}
|
||||
Collections.sort(comments, cs);
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
private LinkedList<String> getTagGroup(String pageContent) {
|
||||
LinkedList<String> list = new LinkedList<String>();
|
||||
Pattern p = Pattern.compile("<div[^<>]+><a[^<>]+>([\\w\\s]+)</a></div>");
|
||||
Matcher m = p.matcher(pageContent);
|
||||
while (m.find())
|
||||
list.add(m.group(1));
|
||||
if (list.size() == 0)
|
||||
return null;
|
||||
else
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class CommentSort implements Comparator<Comment> {
|
||||
private int compareNum(String n1, String n2, int median) {
|
||||
int re = 0;
|
||||
for (int i = 0; i < median; i++) {
|
||||
re *= 10;
|
||||
re += n1.charAt(i) - n2.charAt(i);
|
||||
}
|
||||
return re;
|
||||
}
|
||||
private int getMonth(String m) {
|
||||
if (m.equals("January"))
|
||||
return 1;
|
||||
else if (m.equals("February"))
|
||||
return 2;
|
||||
else if (m.equals("March"))
|
||||
return 3;
|
||||
else if (m.equals("April"))
|
||||
return 4;
|
||||
else if (m.equals("May"))
|
||||
return 5;
|
||||
else if (m.equals("June"))
|
||||
return 6;
|
||||
else if (m.equals("July"))
|
||||
return 7;
|
||||
else if (m.equals("August"))
|
||||
return 8;
|
||||
else if (m.equals("September"))
|
||||
return 9;
|
||||
else if (m.equals("October"))
|
||||
return 10;
|
||||
else if (m.equals("November"))
|
||||
return 11;
|
||||
else if (m.equals("December"))
|
||||
return 12;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Comment c1, Comment c2) {
|
||||
int re = 0;
|
||||
Pattern p = Pattern.compile("(\\d{2}) (\\w+) (\\d{4}), (\\d{2}):(\\d{2})");
|
||||
Matcher m1 = p.matcher(c1.time);
|
||||
Matcher m2 = p.matcher(c2.time);
|
||||
if (!m1.find() || !m2.find())
|
||||
return 0;
|
||||
// year
|
||||
re += compareNum(m1.group(3), m2.group(3), 4);
|
||||
re <<= 4;
|
||||
// Month
|
||||
re += getMonth(m1.group(2)) - getMonth(m2.group(2));
|
||||
re <<= 5;
|
||||
// Day
|
||||
re += compareNum(m1.group(1), m2.group(1), 2);
|
||||
re <<= 5;
|
||||
// Hour
|
||||
re += compareNum(m1.group(4), m2.group(4), 2);
|
||||
re <<= 6;
|
||||
// Minute
|
||||
re += compareNum(m1.group(5), m2.group(5), 2);
|
||||
return re;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class DetailUrlParser {
|
||||
|
||||
public int gid;
|
||||
public String token;
|
||||
|
||||
public boolean parser(String url) {
|
||||
if (url == null)
|
||||
return false;
|
||||
|
||||
Pattern p = Pattern.compile("/(\\d+)/(\\w+)");
|
||||
Matcher m = p.matcher(url);
|
||||
if (m.find()) {
|
||||
gid = Integer.parseInt(m.group(1));
|
||||
token = m.group(2);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
|
||||
public class EdDetailParser {
|
||||
|
||||
int previewPageNum;
|
||||
int previewStartIndex;
|
||||
int imageNum;
|
||||
boolean isLastPage;
|
||||
List<String> pageTokenArray;
|
||||
String emsg;
|
||||
|
||||
public boolean parser(String body, int mode, boolean needPreviewInfo) {
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
if (!body.contains("<"))
|
||||
emsg = body;
|
||||
|
||||
if (mode == EhClient.MODE_LOFI) {
|
||||
// Check is last page
|
||||
p = Pattern.compile("<a[^>]+>Next Page ></a>");
|
||||
m = p.matcher(body);
|
||||
if (m.find())
|
||||
isLastPage = false;
|
||||
else
|
||||
isLastPage = true;
|
||||
|
||||
// Parser preview
|
||||
p = Pattern.compile("<div class=\"gi\"[^>]*><a href=\"http://lofi.e-hentai.org/s/(\\w+)/\\d+-(\\d+)\"[^>]*>");
|
||||
m = p.matcher(body);
|
||||
boolean first = true;
|
||||
while(m.find()) {
|
||||
if (first) {
|
||||
pageTokenArray = new LinkedList<String>();
|
||||
previewStartIndex = Integer.parseInt(m.group(2)) - 1;
|
||||
first = false;
|
||||
}
|
||||
pageTokenArray.add(m.group(1));
|
||||
}
|
||||
if (first)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// Parser preview info
|
||||
if (needPreviewInfo) {
|
||||
p = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||
m = p.matcher(body);
|
||||
if (m.find())
|
||||
previewPageNum = Integer.valueOf(m.group(1).replace(",", ""));
|
||||
else {
|
||||
Log.e("TAG", "get needPreviewInfo failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
p = Pattern.compile("<td class=\"gdt1\">Images:</td><td class=\"gdt2\">([\\d,]+) ");
|
||||
m = p.matcher(body);
|
||||
if (m.find())
|
||||
imageNum = Integer.valueOf(m.group(1).replace(",", ""));
|
||||
else {
|
||||
Log.e("TAG", "get needPreviewInfo failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Parser preview
|
||||
|
||||
boolean first = true;
|
||||
if (body.contains("<div class=\"gdtl\"")) {
|
||||
p = Pattern.compile("<div class=\"gdtl\"[^>]*><a href=\"http://[^/]+/s/(\\w+)/\\d+-(\\d+)\">");
|
||||
m = p.matcher(body);
|
||||
while(m.find()) {
|
||||
if (first) {
|
||||
pageTokenArray = new LinkedList<String>();
|
||||
previewStartIndex = Integer.parseInt(m.group(2)) - 1;
|
||||
first = false;
|
||||
}
|
||||
pageTokenArray.add(m.group(1));
|
||||
}
|
||||
|
||||
} else {
|
||||
p = Pattern.compile("<div class=\"gdtm\"[^>]*><div[^>]*><a href=\"http://[^/]+/s/(\\w+)/\\d+-(\\d+)\">");
|
||||
m = p.matcher(body);
|
||||
while(m.find()) {
|
||||
if (first) {
|
||||
pageTokenArray = new LinkedList<String>();
|
||||
previewStartIndex = Integer.parseInt(m.group(2)) - 1;
|
||||
first = false;
|
||||
}
|
||||
pageTokenArray.add(m.group(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (first) {
|
||||
Log.e("TAG", "get gdtm");
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
import com.larvalabs.svgandroid.SVGBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Hippo
|
||||
*
|
||||
*/
|
||||
public class EhInfo {
|
||||
|
||||
private static final String TAG = EhInfo.class.getSimpleName();
|
||||
private static final String PREF_NAME = "eh_info";
|
||||
private static final String DIR_NAME = "file";
|
||||
private static final String AVATAR_NAME = "avatar.png";
|
||||
private static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG;
|
||||
private static Drawable DEFAULT_AVATAR;
|
||||
|
||||
public static final String EX_HOST = "exhentai.org";
|
||||
public static final String[] COOKIABLE_HOSTS = {"exhentai.org",
|
||||
"g.e-hentai.org", "forums.e-hentai.org", "ul.exhentai.org",
|
||||
"ul.e-hentai.org", "lofi.e-hentai.org"};
|
||||
|
||||
private static final String KEY_LOGIN = "login";
|
||||
private static final boolean DEFAULT_LOGIN = false;
|
||||
private static final String KEY_USERNAME = "username";
|
||||
private static final String KEY_DISPLAYNAME = "displayname";
|
||||
private static final String DEFAULT_NAME = "Hippo";
|
||||
private static final String KEY_MEMBER_ID = "ipb_member_id";
|
||||
private static final String DEFAULT_MEMBER_ID = null;
|
||||
private static final String KEY_PASS_HASH = "ipb_pass_hash";
|
||||
private static final String DEFAULT_PASS_HASH = null;
|
||||
|
||||
private final Context mContext;
|
||||
private final SharedPreferences mInfoPref;
|
||||
private String mUconfig;
|
||||
private boolean mIsLogin;
|
||||
private String mUsername;
|
||||
private String mDisplayname;
|
||||
private BitmapDrawable mAvatar;
|
||||
private int mDefaultCat;
|
||||
private String mPreviewMode;
|
||||
private int mExculdeTagGroup;
|
||||
private String mExculdeLanguage;
|
||||
private static EhInfo sInstance;
|
||||
|
||||
private BitmapDrawable getAvatarFromFile() {
|
||||
File dir = mContext.getDir(DIR_NAME, 0);
|
||||
File avatarFile = new File(dir, AVATAR_NAME);
|
||||
if (!avatarFile.exists())
|
||||
return null;
|
||||
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new FileInputStream(avatarFile);
|
||||
return (BitmapDrawable) BitmapDrawable.createFromStream(is, null);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
} finally {
|
||||
Utils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
|
||||
private EhInfo(final Context context){
|
||||
mContext = context;
|
||||
mInfoPref = mContext.getSharedPreferences(PREF_NAME, 0);
|
||||
if (DEFAULT_AVATAR == null)
|
||||
DEFAULT_AVATAR = new SVGBuilder().readFromResource(context.getResources(), R.raw.default_avatar).build().getDrawable();
|
||||
|
||||
mIsLogin = mInfoPref.getBoolean(KEY_LOGIN, DEFAULT_LOGIN);
|
||||
mUsername = mInfoPref.getString(KEY_USERNAME, DEFAULT_NAME);
|
||||
mDisplayname = mInfoPref.getString(KEY_DISPLAYNAME, DEFAULT_NAME);
|
||||
mAvatar = getAvatarFromFile();
|
||||
|
||||
mDefaultCat = Config.getDefaultCat();
|
||||
mPreviewMode = Config.getPreviewMode();
|
||||
mExculdeTagGroup = Config.getExculdeTagGroup();
|
||||
mExculdeLanguage = Config.getExculdeLanguage();
|
||||
updateUconfig();
|
||||
};
|
||||
|
||||
private String getUconfigString(String previewMode) {
|
||||
return new StringBuilder().append("cats_").append(mDefaultCat)
|
||||
.append("-ts_").append(previewMode == null ? mPreviewMode : previewMode)
|
||||
.append("-xns_").append(mExculdeTagGroup)
|
||||
.append("-xl_").append(mExculdeLanguage)
|
||||
.append("-tl_m-uh_y-tr_2-prn_n-dm_l-ar_0-rc_0-rx_0-ry_0-sa_y-oi_n-qb_n-tf_n-hp_-hk_-ms_n-mt_n")
|
||||
.toString();
|
||||
}
|
||||
|
||||
private void updateUconfig() {
|
||||
mUconfig = getUconfigString(null);
|
||||
}
|
||||
|
||||
public final static EhInfo getInstance(final Context context) {
|
||||
if (sInstance == null)
|
||||
sInstance = new EhInfo(context.getApplicationContext());
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private String getCookie(String cookieStr, String key) {
|
||||
String value = null;
|
||||
int index1 = -1;
|
||||
int index2 = -1;
|
||||
|
||||
if ((index1 = cookieStr.indexOf(key + "=")) != -1) {
|
||||
index1 += key.length() + 1; // size of key + "="
|
||||
if ((index2 = cookieStr.indexOf(";", index1)) != -1)
|
||||
value = cookieStr.substring(index1, index2);
|
||||
else
|
||||
value = cookieStr.substring(index1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void storeCookie(HttpURLConnection conn) {
|
||||
String ipb_member_id;
|
||||
String ipb_pass_hash;
|
||||
|
||||
List<String> cookieList = conn.getHeaderFields().get("Set-Cookie");
|
||||
if (cookieList == null)
|
||||
return;
|
||||
SharedPreferences.Editor editor = mInfoPref.edit();
|
||||
for (String str : cookieList) {
|
||||
ipb_member_id = getCookie(str, KEY_MEMBER_ID);
|
||||
ipb_pass_hash = getCookie(str, KEY_PASS_HASH);
|
||||
|
||||
if (ipb_member_id != null)
|
||||
editor.putString(KEY_MEMBER_ID, ipb_member_id);
|
||||
if (ipb_pass_hash != null)
|
||||
editor.putString(KEY_PASS_HASH, ipb_pass_hash);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public void setCookie(HttpURLConnection conn) {
|
||||
setCookie(conn, null);
|
||||
}
|
||||
|
||||
public void setCookie(HttpURLConnection conn, String previewMode) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String id = mInfoPref.getString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID);
|
||||
String hash = mInfoPref.getString(KEY_PASS_HASH, DEFAULT_PASS_HASH);
|
||||
sb.append("uconfig=").append(previewMode == null ? mUconfig : getUconfigString(previewMode))
|
||||
.append("; xres=").append(Config.getLofiResolution());
|
||||
if (id != null)
|
||||
sb.append("; ipb_member_id=").append(id);
|
||||
if (hash != null)
|
||||
sb.append("; ipb_pass_hash=").append(hash);
|
||||
// Fake
|
||||
sb.append("; igneous=");
|
||||
sb.append("; lv=");
|
||||
conn.setRequestProperty("Cookie", sb.toString());
|
||||
}
|
||||
|
||||
public boolean isLogin() {
|
||||
return mIsLogin;
|
||||
}
|
||||
|
||||
public void login(String username, String displayname) {
|
||||
mIsLogin = true;
|
||||
mUsername = username;
|
||||
mDisplayname = displayname;
|
||||
|
||||
mInfoPref.edit().putBoolean(KEY_LOGIN, true)
|
||||
.putString(KEY_USERNAME, username)
|
||||
.putString(KEY_DISPLAYNAME, displayname).apply();
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
mIsLogin = false;
|
||||
// Remove avatar
|
||||
if (mAvatar != null) {
|
||||
mAvatar.getBitmap().recycle();
|
||||
mAvatar = null;
|
||||
File dir = mContext.getDir(DIR_NAME, 0);
|
||||
File avatarFile = new File(dir, AVATAR_NAME);
|
||||
avatarFile.delete();
|
||||
}
|
||||
|
||||
mInfoPref.edit().putBoolean(KEY_LOGIN, false)
|
||||
.putString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID)
|
||||
.putString(KEY_PASS_HASH, DEFAULT_PASS_HASH).apply();
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return mUsername;
|
||||
}
|
||||
|
||||
public String getDisplayname() {
|
||||
return mDisplayname;
|
||||
}
|
||||
|
||||
public Drawable getAvatar() {
|
||||
return isLogin() && mAvatar != null ? mAvatar : DEFAULT_AVATAR;
|
||||
}
|
||||
|
||||
public void setAvatar(BitmapDrawable avatar) {
|
||||
mAvatar = avatar;
|
||||
|
||||
File dir = mContext.getDir(DIR_NAME, 0);
|
||||
File avatarFile = new File(dir, AVATAR_NAME);
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = new FileOutputStream(avatarFile);
|
||||
avatar.getBitmap().compress(AVATAR_FORMAT, 100, os);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.closeQuietly(os);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultCat(int defaultCat) {
|
||||
mDefaultCat = defaultCat;
|
||||
updateUconfig();
|
||||
}
|
||||
|
||||
public void setPreviewMode(String previewMode) {
|
||||
mPreviewMode = previewMode;
|
||||
updateUconfig();
|
||||
}
|
||||
|
||||
public void setExculdeTagGroup(int exculdeTagGroup) {
|
||||
mExculdeTagGroup = exculdeTagGroup;
|
||||
updateUconfig();
|
||||
}
|
||||
|
||||
public void setExculdeLanguage(String exculdeLanguage) {
|
||||
mExculdeLanguage = exculdeLanguage;
|
||||
updateUconfig();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,922 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Process;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.network.HttpHelper;
|
||||
import com.hippo.ehviewer.util.AutoExpandArray;
|
||||
import com.hippo.ehviewer.util.BgThread;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.EhUtils;
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
// TODO what a mess
|
||||
public final class ExDownloader implements Runnable {
|
||||
|
||||
private static final String TAG = ExDownloader.class.getSimpleName();
|
||||
|
||||
private static final int IO_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
private static final String[] URL_ARRAY_509 = new String[] {
|
||||
"http://ehgt.org/g/509.gif",
|
||||
"http://ehgt.org/g/509s.gif",
|
||||
"http://st.exhentai.net/img/509.gif",
|
||||
"http://st.exhentai.net/img/509s.gif"
|
||||
};
|
||||
|
||||
private final Context mContext;
|
||||
private final ExDownloaderManager mManager;
|
||||
|
||||
private final int mGid;
|
||||
private final String mToken;
|
||||
private final String mTitle;
|
||||
private final int mMode;
|
||||
private volatile int mPreviewPageNum = -1;
|
||||
private volatile int mPreviewPerPage = -1;
|
||||
private volatile int mImageNum = -1;
|
||||
private final AutoExpandArray<String> mPageTokeArray = new AutoExpandArray<String>();
|
||||
private final AutoExpandArray<String> mImageFilenameArray = new AutoExpandArray<String>();
|
||||
|
||||
private volatile int mOwnerNum = 0;
|
||||
private Thread mMainThread = null;
|
||||
/** Only work for EhClient.MODE_LOFI when do not know image number **/
|
||||
private int mCurMaxPreviewPage = -1;
|
||||
|
||||
private volatile int mStartIndex = 0;
|
||||
private final Queue<Integer> mRequestIndexArray = new ConcurrentLinkedQueue<Integer>();
|
||||
private final Queue<Integer> mNoTokenIndexArray = new ConcurrentLinkedQueue<Integer>();
|
||||
private final Queue<Integer> mRequestPageIndexArray = new ConcurrentLinkedQueue<Integer>();
|
||||
|
||||
/** The set contains the requsting index **/
|
||||
private final Set<Integer> mRequstingIndexSet = new HashSet<Integer>();
|
||||
|
||||
private volatile int mCurRequestPageIndex = -1;
|
||||
|
||||
private final int mWorkerNum;
|
||||
private final Worker[] mWorkerArray;
|
||||
private final HttpHelper.DownloadControlor[] mControlorArray;
|
||||
|
||||
private final Object mPageTokenLock = new Object();
|
||||
private final Object mWorkerLock = new Object();
|
||||
private final Object mWorkerGetIndexLock = new Object();
|
||||
|
||||
private volatile boolean mDownloadMode = false;
|
||||
private volatile boolean mPauseWork = false;
|
||||
private volatile boolean mStopWork = false;
|
||||
|
||||
private final File mDir;
|
||||
private ListenerForImageSet mLfis;
|
||||
private ListenerForDownload mLfd;
|
||||
|
||||
private static final long UPDATE_SPEED_INTERVAL = 500;
|
||||
private volatile long mLastUpdateSpeedTime = 0;
|
||||
private volatile long mAccumulateSize;
|
||||
private volatile HashSet<Integer> mDownloadIndexSet;
|
||||
private volatile Object mDownloadLock = new Object();
|
||||
|
||||
private int mCurReadIndex = 0;
|
||||
|
||||
/**
|
||||
* Do no work in UI thread
|
||||
* @author Hippo
|
||||
*/
|
||||
public interface ListenerForImageSet {
|
||||
public void onDownloadStart(int index);
|
||||
public void onDownloading(int index, float percent);
|
||||
public void onDownloadComplete(int index);
|
||||
public void onDownloadFail(int index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do no work in UI thread
|
||||
* @author Hippo
|
||||
*/
|
||||
public interface ListenerForDownload {
|
||||
public void onStart(int gid);
|
||||
public void onGetSum(int gid, int sum);
|
||||
public void onDownload(int gid, int downloadSize, int totalSize);
|
||||
public void onUpdateSpeed(int gid, int speed);
|
||||
public void onDownloadOver(int gid, int legacy);
|
||||
}
|
||||
|
||||
ExDownloader(Context context, int gid, String token, String title, int mode) {
|
||||
mContext = context;
|
||||
mManager = ExDownloaderManager.getInstance();
|
||||
mGid = gid;
|
||||
mToken = token;
|
||||
mTitle = title;
|
||||
mMode = mode;
|
||||
|
||||
mWorkerNum = Config.getDownloadThread();
|
||||
mWorkerArray = new Worker[mWorkerNum];
|
||||
mControlorArray = new HttpHelper.DownloadControlor[mWorkerNum];
|
||||
|
||||
// Make sure dir
|
||||
mDir = EhUtils.getGalleryDir(mGid, mTitle);
|
||||
Utils.ensureDir(mDir, true);
|
||||
}
|
||||
|
||||
public void setListenerForImageSet(ListenerForImageSet l) {
|
||||
mLfis = l;
|
||||
}
|
||||
|
||||
public void setListenerDownload(ListenerForDownload l) {
|
||||
mLfd = l;
|
||||
}
|
||||
|
||||
void occupy() {
|
||||
mOwnerNum++;
|
||||
}
|
||||
|
||||
void free() {
|
||||
mOwnerNum--;
|
||||
}
|
||||
|
||||
boolean isOrphans() {
|
||||
return mOwnerNum == 0;
|
||||
}
|
||||
|
||||
void stop() {
|
||||
// TODO stop all thread, release all resources
|
||||
mStopWork = true;
|
||||
// Stop download
|
||||
for (HttpHelper.DownloadControlor c : mControlorArray) {
|
||||
if (c != null)
|
||||
c.stop();
|
||||
}
|
||||
// Wake all thread
|
||||
synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();}
|
||||
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
|
||||
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
|
||||
}
|
||||
|
||||
boolean pause(boolean pause) {
|
||||
if (mOwnerNum > 1)
|
||||
return false;
|
||||
|
||||
mPauseWork = pause;
|
||||
if (mPauseWork) {
|
||||
// Stop download
|
||||
for (HttpHelper.DownloadControlor c : mControlorArray)
|
||||
if (c != null) c.stop();
|
||||
// Wake all thread
|
||||
synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();}
|
||||
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
|
||||
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
|
||||
} else {
|
||||
// Reset download
|
||||
for (HttpHelper.DownloadControlor c : mControlorArray)
|
||||
if (c != null) c.reset();
|
||||
ensureStart();
|
||||
ensureWorkers();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getGid() {
|
||||
return mGid;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return mToken;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public void setDownloadMode(boolean downloadMode) {
|
||||
synchronized (mDownloadLock) {
|
||||
if (downloadMode) {
|
||||
setStartIndex(0);
|
||||
if (mDownloadIndexSet == null)
|
||||
mDownloadIndexSet = new HashSet<Integer>();
|
||||
if (mDownloadMode && mLfd != null)
|
||||
mLfd.onStart(mGid);
|
||||
} else {
|
||||
mDownloadIndexSet = null;
|
||||
}
|
||||
mDownloadMode = downloadMode;
|
||||
}
|
||||
}
|
||||
|
||||
public void setStartIndex(int startIndex) {
|
||||
if (mDownloadMode) {
|
||||
addTargetIndex(startIndex);
|
||||
} else {
|
||||
if (mImageNum != -1 && startIndex >= mImageNum)
|
||||
mStartIndex = 0;
|
||||
else
|
||||
mStartIndex = startIndex;
|
||||
|
||||
ensureStart();
|
||||
ensureWorkers();
|
||||
}
|
||||
}
|
||||
|
||||
public void addTargetIndex(int index) {
|
||||
if (mImageNum != -1 && index >= mImageNum)
|
||||
return;
|
||||
|
||||
mRequestIndexArray.offer(index);
|
||||
ensureStart();
|
||||
ensureWorkers();
|
||||
}
|
||||
|
||||
public void setCurReadIndex(final int index) {
|
||||
new BgThread() {
|
||||
@Override
|
||||
public void run() {
|
||||
writeCurReadIndex(index);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
public int getMaxEnsureIndex() {
|
||||
if (mImageNum != -1) {
|
||||
return mImageNum - 1;
|
||||
} else {
|
||||
return Math.max(Math.max(Math.max(mStartIndex,
|
||||
Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage),
|
||||
mPageTokeArray.maxValidIndex()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If do not know image num, return -1.
|
||||
* You should call getMaxEnsureIndex().
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getImageNum() {
|
||||
return mImageNum;
|
||||
}
|
||||
|
||||
public AutoExpandArray<String> getImageFilenameArray() {
|
||||
return mImageFilenameArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* If targer index image is downloading, return true
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public boolean isDownloading(int index) {
|
||||
return mRequstingIndexSet.contains(index);
|
||||
}
|
||||
|
||||
private synchronized void ensureStart() {
|
||||
if (mMainThread != null)
|
||||
return;
|
||||
|
||||
mMainThread = new BgThread(this);
|
||||
mMainThread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
mMainThread.start();
|
||||
}
|
||||
|
||||
private File getExDownloadInfoFile() {
|
||||
return new File(mDir, EhUtils.EH_DOWNLOAD_FILENAME);
|
||||
}
|
||||
|
||||
private boolean checkMode(int samlpe, int target) {
|
||||
return samlpe <= EhClient.MODE_EX && target <= EhClient.MODE_EX
|
||||
|| samlpe == EhClient.MODE_LOFI && target == EhClient.MODE_LOFI;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function parser the download info file.<br>
|
||||
* The file look like this:<br>
|
||||
* <code>
|
||||
* 00000000<br>
|
||||
* 728874<br>
|
||||
* 306429c222<br>
|
||||
* 1<br>
|
||||
* 4<br>
|
||||
* 40<br>
|
||||
* 128<br>
|
||||
* 1 43a64e6e79<br>
|
||||
* </code><br>
|
||||
* Fist line is gid, a integer.
|
||||
* Second line is token, a ten-character string.
|
||||
* Third line is mode.
|
||||
* Fourth line is preview page num.
|
||||
* Fifth line is preview per page.
|
||||
*
|
||||
* @param ediFile
|
||||
* @return
|
||||
*/
|
||||
private synchronized boolean parserEdiFile(File ediFile) {
|
||||
if (!ediFile.exists() || !ediFile.isFile() || !ediFile.canRead()
|
||||
|| !ediFile.canWrite())
|
||||
return false;
|
||||
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new BufferedInputStream(new FileInputStream(ediFile),
|
||||
IO_BUFFER_SIZE);
|
||||
// skip read index
|
||||
Utils.readAsciiLine(is);
|
||||
if (mGid != Integer.valueOf(Utils.readAsciiLine(is)) ||
|
||||
!mToken.equals(Utils.readAsciiLine(is)))
|
||||
return false;
|
||||
|
||||
if (!checkMode(Integer.valueOf(Utils.readAsciiLine(is)), mMode)) {
|
||||
// If preview info is not same
|
||||
// skip preview page num and preview per page
|
||||
Utils.readAsciiLine(is);
|
||||
Utils.readAsciiLine(is);
|
||||
} else {
|
||||
mPreviewPageNum = Integer.valueOf(Utils.readAsciiLine(is));
|
||||
mPreviewPerPage = Integer.valueOf(Utils.readAsciiLine(is));
|
||||
}
|
||||
|
||||
mImageNum = Integer.parseInt(Utils.readAsciiLine(is));
|
||||
if (mImageNum != -1) {
|
||||
mPageTokeArray.setCapacity(mImageNum);
|
||||
mImageFilenameArray.setCapacity(mImageNum);
|
||||
}
|
||||
else if (mPreviewPageNum != -1 && mPreviewPerPage != -1) {
|
||||
mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
|
||||
mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
|
||||
}
|
||||
|
||||
// read page token info
|
||||
try {
|
||||
while(true) {
|
||||
String line = Utils.readAsciiLine(is);
|
||||
int pos = line.indexOf(" ");
|
||||
if (pos == -1 && line.length() - pos != 11)
|
||||
continue;
|
||||
try {
|
||||
int index = Integer.parseInt(line.substring(0, pos));
|
||||
String token = line.substring(pos + 1);
|
||||
mPageTokeArray.set(index, token);
|
||||
} catch (NumberFormatException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
// Empty
|
||||
}
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
// If read error
|
||||
return false;
|
||||
} finally {
|
||||
Utils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void writeEdiFile(File ediFile) {
|
||||
try {
|
||||
FileWriter writer = new FileWriter(ediFile);
|
||||
writer.write(String.format("%08x", mCurReadIndex));
|
||||
writer.write("\n");
|
||||
writer.write(String.valueOf(mGid));
|
||||
writer.write("\n");
|
||||
writer.write(mToken);
|
||||
writer.write("\n");
|
||||
writer.write(mMode + '0');
|
||||
writer.write("\n");
|
||||
writer.write(String.valueOf(mPreviewPageNum));
|
||||
writer.write("\n");
|
||||
writer.write(String.valueOf(mPreviewPerPage));
|
||||
writer.write("\n");
|
||||
writer.write(String.valueOf(mImageNum));
|
||||
writer.write("\n");
|
||||
for (int i = 0; i < mPageTokeArray.length(); i++) {
|
||||
String pageToken = mPageTokeArray.get(i);
|
||||
if (pageToken != null) {
|
||||
writer.write(String.valueOf(i));
|
||||
writer.write(" ");
|
||||
writer.write(pageToken);
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
|
||||
private synchronized void writeCurReadIndex(int index) {
|
||||
mCurReadIndex = index;
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(getExDownloadInfoFile(), "rw");
|
||||
raf.write(String.format("%08x", mCurReadIndex).getBytes());
|
||||
raf.close();
|
||||
} catch (Throwable e) {}
|
||||
}
|
||||
|
||||
public static int readCurReadIndex(int gid, String title) {
|
||||
try {
|
||||
RandomAccessFile raf = new RandomAccessFile(new File(EhUtils.getGalleryDir(gid, title),
|
||||
EhUtils.EH_DOWNLOAD_FILENAME), "r");
|
||||
int index = Integer.parseInt(raf.readLine(), 16);
|
||||
raf.close();
|
||||
return index;
|
||||
} catch (Throwable e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void getDetailInfo(int pageIndex, File ediFile, boolean needPreviewInfo) throws Exception {
|
||||
HttpHelper hh = new HttpHelper(mContext);
|
||||
EdDetailParser edp = new EdDetailParser();
|
||||
String url = EhClient.getDetailUrl(mGid, mToken, pageIndex, mMode);
|
||||
hh.setPreviewMode("m");
|
||||
String body = hh.get(url);
|
||||
if (body == null)
|
||||
throw new Exception(hh.getEMsg() != null ? hh.getEMsg() : "Http error, body == null");
|
||||
if (!edp.parser(body, mMode, needPreviewInfo))
|
||||
throw new Exception(edp.emsg != null ? edp.emsg : "Parser error, body is \n" + body);
|
||||
if (edp.previewStartIndex != pageIndex * mPreviewPerPage)
|
||||
throw new Exception("预测与实际不匹配, 预测: " + edp.previewStartIndex +
|
||||
", 实际: " + pageIndex * mPreviewPerPage + ", body is \n" + body);
|
||||
|
||||
List<String> pageTokenArray = edp.pageTokenArray;
|
||||
if (needPreviewInfo)
|
||||
mPreviewPerPage = pageTokenArray.size();
|
||||
if (mMode == EhClient.MODE_LOFI) {
|
||||
// update mCurMaxPreviewPage
|
||||
if (edp.isLastPage) {
|
||||
mPreviewPageNum = pageIndex + 1;
|
||||
mImageNum = pageIndex * mPreviewPerPage + pageTokenArray.size();
|
||||
} else if (!edp.isLastPage && mPreviewPageNum == -1) {
|
||||
mCurMaxPreviewPage = Math.max(mCurMaxPreviewPage, pageIndex + 1);
|
||||
}
|
||||
} else {
|
||||
if (needPreviewInfo) {
|
||||
mPreviewPageNum = edp.previewPageNum;
|
||||
mImageNum = edp.imageNum;
|
||||
mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
|
||||
mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < pageTokenArray.size(); i++)
|
||||
mPageTokeArray.set(i + edp.previewStartIndex, pageTokenArray.get(i));
|
||||
|
||||
writeEdiFile(ediFile);
|
||||
}
|
||||
|
||||
private void ensureWorkers() {
|
||||
if (mPreviewPerPage == -1)
|
||||
return;
|
||||
|
||||
synchronized(mWorkerArray) {
|
||||
for (int i = 0; i < mWorkerArray.length; i++) {
|
||||
if (mWorkerArray[i] == null) {
|
||||
Worker worker = new Worker(i);
|
||||
worker.start();
|
||||
mWorkerArray[i] = worker;
|
||||
mControlorArray[i] = new HttpHelper.DownloadControlor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDownload(int index) {
|
||||
synchronized (mDownloadLock) {
|
||||
if (mDownloadIndexSet != null && !mDownloadIndexSet.contains(index)) {
|
||||
mDownloadIndexSet.add(index);
|
||||
if (mDownloadMode && mLfd != null)
|
||||
mLfd.onDownload(mGid, mDownloadIndexSet.size(), getMaxEnsureIndex() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int BE_NOTIFY_ID = -3;
|
||||
|
||||
private static final void show509Notification(Context context) {
|
||||
NotificationManager notifyManager = (NotificationManager) context
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
|
||||
mBuilder.setSmallIcon(R.drawable.ic_stat_eh).setContentTitle(context.getString(R.string.notification_be_title))
|
||||
.setContentText(context.getString(R.string.notification_be_mesg))
|
||||
.setOngoing(false).setAutoCancel(true);
|
||||
notifyManager.notify(BE_NOTIFY_ID, mBuilder.build());
|
||||
}
|
||||
|
||||
// TODO for download , when to claim download completed
|
||||
@Override
|
||||
public void run() {
|
||||
// Try to get info from file
|
||||
try {
|
||||
File ediFile = getExDownloadInfoFile();
|
||||
if (!parserEdiFile(ediFile) || mPreviewPerPage == -1 ||
|
||||
((mMode == EhClient.MODE_EX || mMode == EhClient.MODE_G) && mImageNum == -1)) {
|
||||
getDetailInfo(0, ediFile, true);
|
||||
}
|
||||
|
||||
if (mLfd != null)
|
||||
mLfd.onGetSum(mGid, mImageNum);
|
||||
|
||||
ensureWorkers();
|
||||
|
||||
// A loop to get page token
|
||||
while (true) {
|
||||
// Check stop
|
||||
if (mStopWork)
|
||||
break;
|
||||
|
||||
int noTokenIndex = -1;
|
||||
int pageIndex = -1;
|
||||
synchronized(mPageTokenLock) {
|
||||
if (mNoTokenIndexArray.isEmpty()) {
|
||||
if (mRequestPageIndexArray.isEmpty()) {
|
||||
try {
|
||||
mPageTokenLock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
continue;
|
||||
} else {
|
||||
pageIndex = mRequestPageIndexArray.poll();
|
||||
mCurRequestPageIndex = pageIndex;
|
||||
}
|
||||
} else {
|
||||
noTokenIndex = mNoTokenIndexArray.poll();
|
||||
}
|
||||
}
|
||||
|
||||
if (noTokenIndex != -1) {
|
||||
if (mPageTokeArray.get(noTokenIndex) == null) {
|
||||
getDetailInfo(noTokenIndex / mPreviewPerPage, ediFile, false);
|
||||
} else {
|
||||
// The token is already got, no need to re
|
||||
}
|
||||
} else if (noTokenIndex == -1){
|
||||
getDetailInfo(pageIndex, ediFile, false);
|
||||
mCurRequestPageIndex = -1;
|
||||
}
|
||||
|
||||
ensureWorkers();
|
||||
|
||||
synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();}
|
||||
synchronized(mWorkerLock) {mWorkerLock.notifyAll();}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
Log.f(e);
|
||||
|
||||
synchronized (mDownloadLock) {
|
||||
if (mDownloadMode && mLfd != null && mDownloadIndexSet != null) {
|
||||
mLfd.onDownloadOver(mGid,
|
||||
getMaxEnsureIndex() + 1 - mDownloadIndexSet.size());
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
mMainThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "ExDownloader over");
|
||||
}
|
||||
|
||||
private class Worker extends BgThread {
|
||||
|
||||
private final int mIndex;
|
||||
|
||||
public Worker(int index) {
|
||||
super();
|
||||
mIndex = index;
|
||||
setPriority(Process.THREAD_PRIORITY_BACKGROUND);
|
||||
}
|
||||
|
||||
private void addNoTokenIndex(int index) {
|
||||
synchronized(mPageTokenLock) {
|
||||
mNoTokenIndexArray.offer(index);
|
||||
mPageTokenLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private void addRequstPageIndex(int pageIndex) {
|
||||
synchronized(mPageTokenLock) {
|
||||
if (mCurRequestPageIndex != pageIndex &&
|
||||
!mRequestPageIndexArray.contains(pageIndex)) {
|
||||
mRequestPageIndexArray.offer(pageIndex);
|
||||
mPageTokenLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getTargetIndex() {
|
||||
synchronized(mWorkerGetIndexLock) {
|
||||
while (true) {
|
||||
|
||||
// Check stop
|
||||
if (mStopWork || mPauseWork)
|
||||
return -1;
|
||||
|
||||
if (!mRequestIndexArray.isEmpty()) {
|
||||
return mRequestIndexArray.poll();
|
||||
} else if (mImageNum != -1 && mStartIndex == mImageNum) {
|
||||
return -1;
|
||||
} else if (mImageNum == -1 && mStartIndex > getMaxEnsureIndex()) {
|
||||
// If do not sure the index is valid
|
||||
|
||||
Log.d(TAG, "mPreviewPerPage = " + mPreviewPerPage);
|
||||
|
||||
addRequstPageIndex(getMaxEnsureIndex() / mPreviewPerPage);
|
||||
try {
|
||||
mWorkerGetIndexLock.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
} else {
|
||||
return mStartIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO what if download failed ? what to do ?
|
||||
@Override
|
||||
public void run() {
|
||||
HttpDownloadListener listener = new HttpDownloadListener();
|
||||
HttpHelper hh = new HttpHelper(mContext);
|
||||
ImagePageParser ipp = new ImagePageParser();
|
||||
|
||||
while (true) {
|
||||
// Check stop
|
||||
if (mStopWork || mPauseWork)
|
||||
break;
|
||||
|
||||
int targetIndex = getTargetIndex();
|
||||
if (targetIndex == -1)
|
||||
break;
|
||||
|
||||
// Check is the index is requsted
|
||||
if (mRequstingIndexSet.contains(targetIndex))
|
||||
continue;
|
||||
|
||||
// Check is this already downloaded
|
||||
boolean isAlreadyDownloaded = false;
|
||||
// First check mImageFilenameArray
|
||||
String imageFilename = mImageFilenameArray.get(targetIndex);
|
||||
if (imageFilename != null) {
|
||||
File file = new File(mDir, imageFilename);
|
||||
if (file.exists())
|
||||
isAlreadyDownloaded = true;
|
||||
} else {
|
||||
// Can not find image file name, just guess
|
||||
for (String possibleFilename : EhUtils.getPossibleImageFilenames(targetIndex)) {
|
||||
File file = new File(mDir, possibleFilename);
|
||||
if (file.exists()) {
|
||||
isAlreadyDownloaded = true;
|
||||
mImageFilenameArray.set(targetIndex, possibleFilename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isAlreadyDownloaded) {
|
||||
Log.d(TAG, "isAlreadyDownloaded");
|
||||
updateDownload(targetIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
mRequstingIndexSet.add(targetIndex);
|
||||
|
||||
listener.index = targetIndex;
|
||||
|
||||
String pageToken = null;
|
||||
while (true) {
|
||||
// Check stop
|
||||
if (mStopWork || mPauseWork)
|
||||
break;
|
||||
|
||||
pageToken = mPageTokeArray.get(targetIndex);
|
||||
if (pageToken == null) {
|
||||
addNoTokenIndex(targetIndex);
|
||||
synchronized(mWorkerLock) {
|
||||
try {mWorkerLock.wait();}
|
||||
catch (InterruptedException e) {}
|
||||
}
|
||||
} else {
|
||||
// Get valid page token, jump out of loop
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parser image page
|
||||
for (int i = 0; i < 2 && !mStopWork && !mPauseWork; i++) {
|
||||
hh.reset();
|
||||
ipp.reset();
|
||||
String body = hh.get(EhClient.getPageUrl(mGid, pageToken, targetIndex + 1, mMode)
|
||||
+ ((i == 1) ? "?nl=48" : ""));
|
||||
if (ipp.parser(body, mMode)) {
|
||||
// Download image
|
||||
HttpHelper.DownloadControlor c = mControlorArray[mIndex];
|
||||
|
||||
if (Config.getDownloadOriginImage() && ipp.originalImageUrl != null) {
|
||||
|
||||
Log.d(TAG, "Download origin image");
|
||||
|
||||
// Download orgin image
|
||||
String imageUrl = ipp.originalImageUrl;
|
||||
// Guess filename
|
||||
String filename = EhUtils.getImageFilename(targetIndex, "jpg");
|
||||
// Download
|
||||
hh.reset();
|
||||
if (hh.downloadOriginEhImage(imageUrl, mDir, filename, false, c, listener) == null) {
|
||||
if (hh.getException() instanceof HttpHelper.BandwidthExceededException) {
|
||||
// Get 509
|
||||
show509Notification(mContext);
|
||||
break;
|
||||
} else {
|
||||
String newImageUrl = hh.getLastUrl();
|
||||
if (newImageUrl != null) {
|
||||
// Try download from proxy
|
||||
hh.reset();
|
||||
if (hh.downloadEhImage(newImageUrl, mDir, filename, true, c, listener) == null) {
|
||||
// Download failed
|
||||
Log.d(TAG, "Download failed");
|
||||
} else {
|
||||
// Download successful
|
||||
Log.d(TAG, "Download successful");
|
||||
}
|
||||
} else {
|
||||
// Download failed
|
||||
Log.d(TAG, "Download failed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Download successful
|
||||
Log.d(TAG, "Download successful");
|
||||
}
|
||||
|
||||
// No need to refresh image url
|
||||
break;
|
||||
} else {
|
||||
// Download scale image
|
||||
String imageUrl = ipp.imageUrl;
|
||||
|
||||
//Check 509 gif
|
||||
boolean get509 = false;
|
||||
int length = URL_ARRAY_509.length;
|
||||
for (int j = 0; j < length; j++) {
|
||||
if (URL_ARRAY_509[j].equals(imageUrl)) {
|
||||
// Get 509 gif here
|
||||
get509 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (get509) {
|
||||
show509Notification(mContext);
|
||||
break;
|
||||
}
|
||||
|
||||
// Guess filename
|
||||
String filename = EhUtils.getImageFilename(targetIndex, Utils.getExtension(imageUrl, "jpg"));
|
||||
// Don't put into mImageFilenameArray, because extension maybe be wrong
|
||||
// mImageFilenameArray.set(targetIndex, filename);
|
||||
hh.reset();
|
||||
if (hh.downloadEhImage(imageUrl, mDir, filename, false, c, listener) == null) {
|
||||
hh.reset();
|
||||
if (hh.downloadEhImage(imageUrl, mDir, filename, true, c, listener) == null) {
|
||||
if (i == 1) {
|
||||
// TODO download error
|
||||
Log.d(TAG, "download error");
|
||||
}
|
||||
} else {
|
||||
// download ok
|
||||
Log.d(TAG, "download ok proxy");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// download ok
|
||||
Log.d(TAG, "download ok");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO parser error, Do somthing
|
||||
Log.d(TAG, "parser error");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from requsting set
|
||||
mRequstingIndexSet.remove(targetIndex);
|
||||
}
|
||||
|
||||
// Worker over
|
||||
synchronized(mWorkerArray) {
|
||||
// Remove from mWorkerArray
|
||||
mWorkerArray[mIndex] = null;
|
||||
mControlorArray[mIndex] = null;
|
||||
|
||||
boolean allNull = true;
|
||||
for (Worker w : mWorkerArray)
|
||||
if (w != null) {allNull = false; break;}
|
||||
|
||||
synchronized (mDownloadLock) {
|
||||
if (allNull && mDownloadMode && mLfd != null && mDownloadIndexSet != null) {
|
||||
mLfd.onDownloadOver(mGid,
|
||||
getMaxEnsureIndex() + 1 - mDownloadIndexSet.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "ExDownloader Worker over");
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpDownloadListener implements HttpHelper.OnDownloadListener {
|
||||
|
||||
public int index;
|
||||
private float lastPercent;
|
||||
private int lastDownloadSize;
|
||||
|
||||
@Override
|
||||
public void onDownloadStartConnect() {
|
||||
lastPercent = 0.0f;
|
||||
lastDownloadSize = 0;
|
||||
|
||||
if (mLfis != null)
|
||||
mLfis.onDownloadStart(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStartDownload(int totalSize) {
|
||||
lastPercent = 0.0f;
|
||||
lastDownloadSize = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStatusUpdate(int downloadSize, int totalSize) {
|
||||
if (totalSize == -1)
|
||||
return;
|
||||
|
||||
float percent = (float)downloadSize / totalSize;
|
||||
if (percent - lastPercent >= 0.05f) {
|
||||
lastPercent = percent;
|
||||
|
||||
if (mLfis != null)
|
||||
mLfis.onDownloading(index, percent);
|
||||
}
|
||||
|
||||
// Update download size
|
||||
int accumulateSize = downloadSize - lastDownloadSize;
|
||||
lastDownloadSize = downloadSize;
|
||||
mAccumulateSize += accumulateSize;
|
||||
long curTime = System.currentTimeMillis();
|
||||
long interval = curTime - mLastUpdateSpeedTime;
|
||||
if (interval > UPDATE_SPEED_INTERVAL) {
|
||||
if (mDownloadMode && mLfd != null)
|
||||
mLfd.onUpdateSpeed(mGid, (int)(mAccumulateSize * 1000 / interval));
|
||||
mLastUpdateSpeedTime = curTime;
|
||||
mAccumulateSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadOver(int status, String eMsg) {
|
||||
if (status == HttpHelper.DOWNLOAD_OK_CODE) {
|
||||
mRequstingIndexSet.remove(index);
|
||||
|
||||
updateDownload(index);
|
||||
}
|
||||
|
||||
if (mLfis != null && status == HttpHelper.DOWNLOAD_OK_CODE)
|
||||
mLfis.onDownloadComplete(index);
|
||||
else if (mLfis != null && status == HttpHelper.DOWNLOAD_FAIL_CODE)
|
||||
mLfis.onDownloadFail(index);
|
||||
|
||||
lastPercent = 0.0f;
|
||||
lastDownloadSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.SparseArray;
|
||||
|
||||
public class ExDownloaderManager {
|
||||
|
||||
private static final String TAG = ExDownloaderManager.class.getSimpleName();
|
||||
|
||||
private final Context mContext;
|
||||
private final SparseArray<ExDownloader> mExDownloadList;
|
||||
|
||||
private static ExDownloaderManager sInstance;
|
||||
|
||||
public static void createInstance(Context context) {
|
||||
sInstance = new ExDownloaderManager(context.getApplicationContext());
|
||||
}
|
||||
|
||||
public static ExDownloaderManager getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private ExDownloaderManager(Context context) {
|
||||
mContext = context;
|
||||
mExDownloadList = new SparseArray<ExDownloader>();
|
||||
}
|
||||
|
||||
public synchronized ExDownloader getExDownloader(int gid, String token, String title, int mode) {
|
||||
ExDownloader exDownloader = mExDownloadList.get(gid);
|
||||
if (exDownloader == null) {
|
||||
exDownloader = new ExDownloader(mContext, gid, token, title, mode);
|
||||
mExDownloadList.append(gid, exDownloader);
|
||||
}
|
||||
exDownloader.occupy();
|
||||
return exDownloader;
|
||||
}
|
||||
|
||||
public synchronized void freeExDownloader(ExDownloader exDownloader) {
|
||||
exDownloader.free();
|
||||
if (exDownloader.isOrphans()) {
|
||||
exDownloader.stop();
|
||||
mExDownloadList.remove(exDownloader.getGid());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class ImagePageParser {
|
||||
|
||||
public String imageUrl;
|
||||
public String originalImageUrl;
|
||||
public int gid;
|
||||
public String token;
|
||||
|
||||
public void reset() {
|
||||
imageUrl = null;
|
||||
originalImageUrl = null;
|
||||
gid = 0;
|
||||
token = null;
|
||||
}
|
||||
|
||||
public boolean parser(String body, int mode) {
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
if (mode ==EhClient.MODE_LOFI) {
|
||||
p = Pattern.compile("<img id=\"sm\" src=\"([^\"]+)\"[^>]+>");
|
||||
m = p.matcher(body);
|
||||
if (m.find())
|
||||
imageUrl = Utils.unescapeXml(m.group(1));
|
||||
else
|
||||
return false;
|
||||
|
||||
p = Pattern.compile("<a href=\"(?:http|https)://.+?/g/(\\d+)/(\\w+)/?\">Back</a>");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
gid = Integer.parseInt(m.group(1));
|
||||
token = m.group(2);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
p = Pattern.compile("<div id=\"i3\">.+?<img id=\"img\" src=\"(.+?)\""
|
||||
+ ".+?"
|
||||
+ "<div id=\"i5\"><div class=\"sb\"><a href=\"(?:http|https)://.+?/g/(\\d+)/(\\w+)");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
imageUrl = Utils.unescapeXml(m.group(1));
|
||||
gid = Integer.parseInt(m.group(2));
|
||||
token = m.group(3);
|
||||
|
||||
p = Pattern.compile("<a href=\"([^\"]+)fullimg.php([^\"]+)\">");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
originalImageUrl = Utils.unescapeXml(
|
||||
new StringBuilder(m.group(1)).append("fullimg.php").append(m.group(2)).toString());
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hippo.ehviewer.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.data.LofiGalleryInfo;
|
||||
import com.hippo.ehviewer.util.EhUtils;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class ListParser {
|
||||
|
||||
public static final int TARGET_PAGE_IS_LAST = -1;
|
||||
|
||||
public static final int ALL = 0x1;
|
||||
public static final int NOT_FOUND = 0x2;
|
||||
public static final int PARSER_ERROR = -1;
|
||||
public static final int INDEX_ERROR = -2;
|
||||
|
||||
public int pageNum;
|
||||
|
||||
public ArrayList<GalleryInfo> giList;
|
||||
|
||||
/**
|
||||
* If NOT_FOUND maxPage is 0,
|
||||
* if can not get maxPage, set it Integer.MAX_VALUE
|
||||
*
|
||||
* @param pageContext
|
||||
* @param mode
|
||||
* @return
|
||||
*/
|
||||
public int parser(String body, int mode) {
|
||||
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return 0;
|
||||
|
||||
giList = new ArrayList<GalleryInfo>(25);
|
||||
|
||||
switch (mode) {
|
||||
case EhClient.MODE_LOFI:
|
||||
p = Pattern.compile("<td class=\"ii\"><a href=\"(.+?)\">" // detail url
|
||||
+ "<img src=\"(.+?)\".+?/>" // thumb url
|
||||
+ ".+?<a class=\"b\" href=\".+?\">(.+?)</a>" // title
|
||||
+ ".+?<td class=\"ik ip\">Posted:</td><td class=\"ip\">(.+?)</td>" // Posted and uploader
|
||||
+ "</tr><tr><td class=\"ik\">Category:</td><td>(.+?)</td>" // Category
|
||||
+ "</tr><tr><td class=\"ik\">Tags:</td><td>(.+?)</td>" // Tags
|
||||
+ "</tr><tr><td class=\"ik\">Rating:</td><td class=\"ir\">(.+?)</td>"); // rating
|
||||
m = p.matcher(body);
|
||||
DetailUrlParser dup = new DetailUrlParser();
|
||||
while (m.find()) {
|
||||
LofiGalleryInfo lgi = new LofiGalleryInfo();
|
||||
String[] pau = new String[2];
|
||||
|
||||
if (dup.parser(m.group(1))) {
|
||||
lgi.gid = dup.gid;
|
||||
lgi.token = dup.token;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
lgi.thumb = Utils.unescapeXml(m.group(2));
|
||||
lgi.title = Utils.unescapeXml(m.group(3));
|
||||
if (getPostedAndUploader(m.group(4), pau)) {
|
||||
lgi.posted = pau[0];
|
||||
lgi.uploader = pau[1];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
lgi.category = EhUtils.getCategory(m.group(5));
|
||||
String tags = m.group(6);
|
||||
if (tags.equals("-"))
|
||||
lgi.lofiTags = null;
|
||||
else
|
||||
lgi.lofiTags = tags.split(", ");
|
||||
String rating = m.group(7);
|
||||
if (rating.equals("-"))
|
||||
lgi.rating = Float.NaN;
|
||||
else
|
||||
lgi.rating = getStartNum(rating);
|
||||
lgi.generateSLang();
|
||||
|
||||
giList.add(lgi);
|
||||
}
|
||||
|
||||
if (giList.size() == 0) {
|
||||
if (body.contains("No hits found</div>")) {
|
||||
pageNum = 0;
|
||||
return NOT_FOUND;
|
||||
} else if (body.contains("No more hits found</div>")) {
|
||||
return INDEX_ERROR;
|
||||
} else {
|
||||
return PARSER_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (!body.contains("Next Page ></a>"))
|
||||
pageNum = TARGET_PAGE_IS_LAST;
|
||||
else
|
||||
pageNum = Integer.MAX_VALUE;
|
||||
return ALL;
|
||||
}
|
||||
|
||||
case EhClient.MODE_G:
|
||||
case EhClient.MODE_EX:
|
||||
default:
|
||||
p = Pattern.compile("<a[^<>]+>([\\d]+)</a></td><td[^<>]+>(?:<a[^<>]+>)?&");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
pageNum = Integer.parseInt(m.group(1));
|
||||
} else if (body.contains("No hits found</p>")) {
|
||||
pageNum = 0;
|
||||
return NOT_FOUND;
|
||||
} else {
|
||||
return PARSER_ERROR;
|
||||
}
|
||||
|
||||
p = Pattern.compile("<td class=\"itdc\">(?:<a.+?>)?<img.+?alt=\"(.+?)\".+?/>(?:</a>)?</td>" // category
|
||||
+ "<td.+?>(.+?)</td>" // posted
|
||||
+ "<td.+?><div.+?><div.+?height:(\\d+)px; width:(\\d+)px\">"
|
||||
+ "(?:<img.+?src=\"(.+?)\".+?alt=\"(.+?)\" style.+?/>"
|
||||
+ "|init~([^<>\"~]+~[^<>\"~]+)~([^<>]+))" // thumb and title
|
||||
+ "</div>"
|
||||
+ ".+?"
|
||||
+ "<div class=\"it5\"><a href=\"([^<>\"]+)\"[^<>]+>(.+?)</a></div>" // url and title
|
||||
+ ".+?"
|
||||
+ "<div class=\"ir it4r\" style=\"([^<>\"]+)\">" // rating
|
||||
+ ".+?"
|
||||
+ "<td class=\"itu\"><div><a.+?>(.+?)</a>"); // uploader
|
||||
m = p.matcher(body);
|
||||
while (m.find()) {
|
||||
GalleryInfo gi = new GalleryInfo();
|
||||
|
||||
gi.category = EhUtils.getCategory(m.group(1));
|
||||
gi.posted = m.group(2);
|
||||
gi.thumbHeight = Integer.parseInt(m.group(3));
|
||||
gi.thumbWidth = Integer.parseInt(m.group(4));
|
||||
|
||||
if (m.group(5) == null) {
|
||||
gi.thumb = Utils.unescapeXml("http://"
|
||||
+ m.group(7).replace('~', '/'));
|
||||
gi.title = Utils.unescapeXml(m.group(8));
|
||||
} else {
|
||||
gi.thumb = Utils.unescapeXml(m.group(5));
|
||||
gi.title = Utils.unescapeXml(m.group(6));
|
||||
}
|
||||
|
||||
Pattern pattern = Pattern
|
||||
.compile("/(\\d+)/(\\w+)");
|
||||
Matcher matcher = pattern.matcher(m.group(9));
|
||||
if (matcher.find()) {
|
||||
gi.gid = Integer.parseInt(matcher.group(1));
|
||||
gi.token = matcher.group(2);
|
||||
} else
|
||||
continue;
|
||||
|
||||
gi.rating = Float.parseFloat(getRate(m.group(11)));
|
||||
gi.uploader = m.group(12);
|
||||
gi.generateSLang();
|
||||
|
||||
giList.add(gi);
|
||||
}
|
||||
return ALL;
|
||||
}
|
||||
}
|
||||
|
||||
private String getRate(String rawRate) {
|
||||
Pattern p = Pattern.compile("\\d+px");
|
||||
Matcher m = p.matcher(rawRate);
|
||||
int num1;
|
||||
int num2;
|
||||
int rate = 5;
|
||||
String re;
|
||||
if (m.find())
|
||||
num1 = Integer.parseInt(m.group().replace("px", ""));
|
||||
else
|
||||
return null;
|
||||
if (m.find())
|
||||
num2 = Integer.parseInt(m.group().replace("px", ""));
|
||||
else
|
||||
return null;
|
||||
rate = rate - num1 / 16;
|
||||
if (num2 == 21) {
|
||||
rate--;
|
||||
re = Integer.toString(rate);
|
||||
re = re + ".5";
|
||||
} else
|
||||
re = Integer.toString(rate);
|
||||
return re;
|
||||
}
|
||||
|
||||
private static final String PAU_SPACER = " by ";
|
||||
|
||||
private boolean getPostedAndUploader(String raw, String[] pau) {
|
||||
int index = raw.indexOf(PAU_SPACER);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
} else {
|
||||
pau[0] = raw.substring(0, index);
|
||||
pau[1] = raw.substring(index + PAU_SPACER.length());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private int getStartNum(String str) {
|
||||
int startNum = 0;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
if (str.charAt(i) == '*')
|
||||
startNum++;
|
||||
}
|
||||
return startNum;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.hippo.ehviewer.data.LargePreviewList;
|
||||
|
||||
|
||||
public class LofiDetailParser {
|
||||
|
||||
public boolean isLastPage;
|
||||
public LargePreviewList preview;
|
||||
public String emesg;
|
||||
|
||||
public boolean parser(String body) {
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
// Get preview
|
||||
p = Pattern.compile("<div class=\"gi\"[^>]+><a[^>]+href=\"([^>\"]+)\"[^>]+><img[^>]+src=\"([^>\"]+)\"[^>]+>");
|
||||
m = p.matcher(body);
|
||||
while (m.find()) {
|
||||
if (preview == null)
|
||||
preview = new LargePreviewList();
|
||||
preview.addItem(m.group(2), m.group(1));
|
||||
}
|
||||
// Check is last page
|
||||
p = Pattern.compile("<a[^>]+>Next Page ></a>");
|
||||
m = p.matcher(body);
|
||||
if (m.find())
|
||||
isLastPage = false;
|
||||
else
|
||||
isLastPage = true;
|
||||
|
||||
return preview != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class LoginParser {
|
||||
public String displayname;
|
||||
public String eMesg;
|
||||
|
||||
public boolean parser(String body) {
|
||||
Pattern p;
|
||||
Matcher m;
|
||||
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
p = Pattern.compile("<p>You are now logged in as: (.+?)<br />");
|
||||
m = p.matcher(body);
|
||||
if (m.find()) {
|
||||
displayname = m.group(1);
|
||||
return true;
|
||||
} else {
|
||||
p = Pattern.compile("(?:<h4>The error returned was:</h4>\\s*<p>(.+?)</p>)"
|
||||
+ "|(?:<span class=\"postcolor\">(.+?)</span>)");
|
||||
m = p.matcher(body);
|
||||
|
||||
if (m.find())
|
||||
eMesg = m.group(1) == null ? m.group(2) : m.group(1);
|
||||
else
|
||||
eMesg = "Unknown error";
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class RateParser {
|
||||
|
||||
public float mRatingAvg;
|
||||
public int mRatingCnt;
|
||||
|
||||
boolean parser(String body) {
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(body);
|
||||
mRatingAvg = (float)jsonObject.getDouble("rating_avg");
|
||||
mRatingCnt = jsonObject.getInt("rating_cnt");
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.ehclient;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VoteParser {
|
||||
|
||||
public String mTagPane;
|
||||
|
||||
boolean parser(String body) {
|
||||
if (body == null)
|
||||
return false;
|
||||
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(body);
|
||||
mTagPane = jsonObject.getString("tagpane");
|
||||
return true;
|
||||
} catch (JSONException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.exception;
|
||||
|
||||
public class FileException extends Exception {
|
||||
|
||||
public static final int MKDIR_ERROR = 0x0;
|
||||
public static final int CREATE_FILE_ERROR = 0x1;
|
||||
public static final int NOT_DIR = 0x2;
|
||||
public static final int NOT_FILE = 0x3;
|
||||
public static final int CANNOT_EXECUTE = 0x4;
|
||||
public static final int CANNOT_READ = 0x5;
|
||||
public static final int CANNOT_WRITE = 0x6;
|
||||
|
||||
private int mErrorCode;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public FileException(int errorCode) {
|
||||
mErrorCode = errorCode;
|
||||
}
|
||||
|
||||
public int getErrorCode() {
|
||||
return mErrorCode;
|
||||
}
|
||||
|
||||
|
||||
// TODO
|
||||
public String getMessage() {
|
||||
switch (mErrorCode) {
|
||||
case MKDIR_ERROR:
|
||||
return "mkdir error";
|
||||
case CREATE_FILE_ERROR:
|
||||
return "create file error";
|
||||
case NOT_DIR:
|
||||
return "it is not a dir";
|
||||
case NOT_FILE:
|
||||
return "it is not a file";
|
||||
case CANNOT_EXECUTE:
|
||||
return "can not execute";
|
||||
case CANNOT_READ:
|
||||
return "can not read";
|
||||
case CANNOT_WRITE:
|
||||
return "can not write";
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.exception;
|
||||
|
||||
public class NoSdCardException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.exception;
|
||||
|
||||
public class ParserException extends Exception{
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.exception;
|
||||
|
||||
public class StopRequestException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.anim;
|
||||
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
// Animation calculates a value according to the current input time.
|
||||
//
|
||||
// 1. First we need to use setDuration(int) to set the duration of the
|
||||
// animation. The duration is in milliseconds.
|
||||
// 2. Then we should call start(). The actual start time is the first value
|
||||
// passed to calculate(long).
|
||||
// 3. Each time we want to get an animation value, we call
|
||||
// calculate(long currentTimeMillis) to ask the Animation to calculate it.
|
||||
// The parameter passed to calculate(long) should be nonnegative.
|
||||
// 4. Use get() to get that value.
|
||||
//
|
||||
// In step 3, onCalculate(float progress) is called so subclasses can calculate
|
||||
// the value according to progress (progress is a value in [0,1]).
|
||||
//
|
||||
// Before onCalculate(float) is called, There is an optional interpolator which
|
||||
// can change the progress value. The interpolator can be set by
|
||||
// setInterpolator(Interpolator). If the interpolator is used, the value passed
|
||||
// to onCalculate may be (for example, the overshoot effect).
|
||||
//
|
||||
// The isActive() method returns true after the animation start() is called and
|
||||
// before calculate is passed a value which reaches the duration of the
|
||||
// animation.
|
||||
//
|
||||
// The start() method can be called again to restart the Animation.
|
||||
//
|
||||
abstract public class Animation {
|
||||
private static final long ANIMATION_START = -1;
|
||||
private static final long NO_ANIMATION = -2;
|
||||
|
||||
private long mStartTime = NO_ANIMATION;
|
||||
private int mDuration;
|
||||
private Interpolator mInterpolator;
|
||||
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
mInterpolator = interpolator;
|
||||
}
|
||||
|
||||
public void setDuration(int duration) {
|
||||
mDuration = duration;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
mStartTime = ANIMATION_START;
|
||||
}
|
||||
|
||||
public void setStartTime(long time) {
|
||||
mStartTime = time;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
public void forceStop() {
|
||||
mStartTime = NO_ANIMATION;
|
||||
}
|
||||
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
if (mStartTime == NO_ANIMATION) return false;
|
||||
if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis;
|
||||
int elapse = (int) (currentTimeMillis - mStartTime);
|
||||
float x = MathUtils.clamp((float) elapse / mDuration, 0f, 1f);
|
||||
Interpolator i = mInterpolator;
|
||||
onCalculate(i != null ? i.getInterpolation(x) : x);
|
||||
if (elapse >= mDuration) mStartTime = NO_ANIMATION;
|
||||
return mStartTime != NO_ANIMATION;
|
||||
}
|
||||
|
||||
abstract protected void onCalculate(float progress);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.anim;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
|
||||
public abstract class CanvasAnimation extends Animation {
|
||||
|
||||
public abstract int getCanvasSaveFlags();
|
||||
public abstract void apply(GLCanvas canvas);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.anim;
|
||||
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import com.hippo.ehviewer.gallery.glrenderer.GLCanvas;
|
||||
import com.hippo.ehviewer.gallery.glrenderer.RawTexture;
|
||||
import com.hippo.ehviewer.gallery.ui.GLView;
|
||||
|
||||
public class StateTransitionAnimation extends Animation {
|
||||
|
||||
public static class Spec {
|
||||
public static final Spec OUTGOING;
|
||||
public static final Spec INCOMING;
|
||||
public static final Spec PHOTO_INCOMING;
|
||||
|
||||
private static final Interpolator DEFAULT_INTERPOLATOR =
|
||||
new DecelerateInterpolator();
|
||||
|
||||
public int duration = 330;
|
||||
public float backgroundAlphaFrom = 0;
|
||||
public float backgroundAlphaTo = 0;
|
||||
public float backgroundScaleFrom = 0;
|
||||
public float backgroundScaleTo = 0;
|
||||
public float contentAlphaFrom = 1;
|
||||
public float contentAlphaTo = 1;
|
||||
public float contentScaleFrom = 1;
|
||||
public float contentScaleTo = 1;
|
||||
public float overlayAlphaFrom = 0;
|
||||
public float overlayAlphaTo = 0;
|
||||
public float overlayScaleFrom = 0;
|
||||
public float overlayScaleTo = 0;
|
||||
public Interpolator interpolator = DEFAULT_INTERPOLATOR;
|
||||
|
||||
static {
|
||||
OUTGOING = new Spec();
|
||||
OUTGOING.backgroundAlphaFrom = 0.5f;
|
||||
OUTGOING.backgroundAlphaTo = 0f;
|
||||
OUTGOING.backgroundScaleFrom = 1f;
|
||||
OUTGOING.backgroundScaleTo = 0f;
|
||||
OUTGOING.contentAlphaFrom = 0.5f;
|
||||
OUTGOING.contentAlphaTo = 1f;
|
||||
OUTGOING.contentScaleFrom = 3f;
|
||||
OUTGOING.contentScaleTo = 1f;
|
||||
|
||||
INCOMING = new Spec();
|
||||
INCOMING.overlayAlphaFrom = 1f;
|
||||
INCOMING.overlayAlphaTo = 0f;
|
||||
INCOMING.overlayScaleFrom = 1f;
|
||||
INCOMING.overlayScaleTo = 3f;
|
||||
INCOMING.contentAlphaFrom = 0f;
|
||||
INCOMING.contentAlphaTo = 1f;
|
||||
INCOMING.contentScaleFrom = 0.25f;
|
||||
INCOMING.contentScaleTo = 1f;
|
||||
|
||||
PHOTO_INCOMING = INCOMING;
|
||||
}
|
||||
|
||||
private static Spec specForTransition(Transition t) {
|
||||
switch (t) {
|
||||
case Outgoing:
|
||||
return Spec.OUTGOING;
|
||||
case Incoming:
|
||||
return Spec.INCOMING;
|
||||
case PhotoIncoming:
|
||||
return Spec.PHOTO_INCOMING;
|
||||
case None:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Transition { None, Outgoing, Incoming, PhotoIncoming }
|
||||
|
||||
private final Spec mTransitionSpec;
|
||||
private float mCurrentContentScale;
|
||||
private float mCurrentContentAlpha;
|
||||
private float mCurrentBackgroundScale;
|
||||
private float mCurrentBackgroundAlpha;
|
||||
private float mCurrentOverlayScale;
|
||||
private float mCurrentOverlayAlpha;
|
||||
private RawTexture mOldScreenTexture;
|
||||
|
||||
public StateTransitionAnimation(Transition t, RawTexture oldScreen) {
|
||||
this(Spec.specForTransition(t), oldScreen);
|
||||
}
|
||||
|
||||
public StateTransitionAnimation(Spec spec, RawTexture oldScreen) {
|
||||
mTransitionSpec = spec != null ? spec : Spec.OUTGOING;
|
||||
setDuration(mTransitionSpec.duration);
|
||||
setInterpolator(mTransitionSpec.interpolator);
|
||||
mOldScreenTexture = oldScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean calculate(long currentTimeMillis) {
|
||||
boolean retval = super.calculate(currentTimeMillis);
|
||||
if (!isActive()) {
|
||||
if (mOldScreenTexture != null) {
|
||||
mOldScreenTexture.recycle();
|
||||
mOldScreenTexture = null;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCalculate(float progress) {
|
||||
mCurrentContentScale = mTransitionSpec.contentScaleFrom
|
||||
+ (mTransitionSpec.contentScaleTo - mTransitionSpec.contentScaleFrom) * progress;
|
||||
mCurrentContentAlpha = mTransitionSpec.contentAlphaFrom
|
||||
+ (mTransitionSpec.contentAlphaTo - mTransitionSpec.contentAlphaFrom) * progress;
|
||||
mCurrentBackgroundAlpha = mTransitionSpec.backgroundAlphaFrom
|
||||
+ (mTransitionSpec.backgroundAlphaTo - mTransitionSpec.backgroundAlphaFrom)
|
||||
* progress;
|
||||
mCurrentBackgroundScale = mTransitionSpec.backgroundScaleFrom
|
||||
+ (mTransitionSpec.backgroundScaleTo - mTransitionSpec.backgroundScaleFrom)
|
||||
* progress;
|
||||
mCurrentOverlayScale = mTransitionSpec.overlayScaleFrom
|
||||
+ (mTransitionSpec.overlayScaleTo - mTransitionSpec.overlayScaleFrom) * progress;
|
||||
mCurrentOverlayAlpha = mTransitionSpec.overlayAlphaFrom
|
||||
+ (mTransitionSpec.overlayAlphaTo - mTransitionSpec.overlayAlphaFrom) * progress;
|
||||
}
|
||||
|
||||
private void applyOldTexture(GLView view, GLCanvas canvas, float alpha, float scale, boolean clear) {
|
||||
if (mOldScreenTexture == null)
|
||||
return;
|
||||
if (clear) canvas.clearBuffer(view.getBackgroundColor());
|
||||
canvas.save();
|
||||
canvas.setAlpha(alpha);
|
||||
int xOffset = view.getWidth() / 2;
|
||||
int yOffset = view.getHeight() / 2;
|
||||
canvas.translate(xOffset, yOffset);
|
||||
canvas.scale(scale, scale, 1);
|
||||
mOldScreenTexture.draw(canvas, -xOffset, -yOffset);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
public void applyBackground(GLView view, GLCanvas canvas) {
|
||||
if (mCurrentBackgroundAlpha > 0f) {
|
||||
applyOldTexture(view, canvas, mCurrentBackgroundAlpha, mCurrentBackgroundScale, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyContentTransform(GLView view, GLCanvas canvas) {
|
||||
int xOffset = view.getWidth() / 2;
|
||||
int yOffset = view.getHeight() / 2;
|
||||
canvas.translate(xOffset, yOffset);
|
||||
canvas.scale(mCurrentContentScale, mCurrentContentScale, 1);
|
||||
canvas.translate(-xOffset, -yOffset);
|
||||
canvas.setAlpha(mCurrentContentAlpha);
|
||||
}
|
||||
|
||||
public void applyOverlay(GLView view, GLCanvas canvas) {
|
||||
if (mCurrentOverlayAlpha > 0f) {
|
||||
applyOldTexture(view, canvas, mCurrentOverlayAlpha, mCurrentOverlayScale, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Movie;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.hippo.ehviewer.AppHandler;
|
||||
import com.hippo.ehviewer.ehclient.ExDownloader;
|
||||
import com.hippo.ehviewer.ehclient.ExDownloaderManager;
|
||||
import com.hippo.ehviewer.gallery.image.Image;
|
||||
import com.hippo.ehviewer.util.AutoExpandArray;
|
||||
import com.hippo.ehviewer.util.BgThread;
|
||||
import com.hippo.ehviewer.util.Config;
|
||||
import com.hippo.ehviewer.util.EhUtils;
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class ImageSet implements ExDownloader.ListenerForImageSet {
|
||||
|
||||
private static final String TAG = ImageSet.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* These are code reture by getImage()
|
||||
*/
|
||||
public static final int RESULT_NONE = 0x0;
|
||||
public static final int RESULT_DOWNLOADING = 0x1;
|
||||
public static final int RESULT_DECODE = 0x2;
|
||||
|
||||
private final ExDownloaderManager mEdManager;
|
||||
private final ExDownloader mExDownloader;
|
||||
|
||||
private final File mDir;
|
||||
private final AutoExpandArray<String> mImageFilenameArray;
|
||||
private ImageListener mListener;
|
||||
private final Queue<DecodeInfo> mDecodeQueue = new ConcurrentLinkedQueue<DecodeInfo>();
|
||||
private final SparseArray<Float> mPercentMap = new SparseArray<Float>(5);
|
||||
private final DecodeWorker mDecodeWorker;
|
||||
/** If true, then wake Worker, worker will stop **/
|
||||
private volatile boolean mStopWork = false;
|
||||
|
||||
public class DecodeInfo {
|
||||
public int index;
|
||||
public String filename;
|
||||
|
||||
public DecodeInfo(int index, String filename) {
|
||||
this.index = index;
|
||||
this.filename = filename;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ImageListener {
|
||||
/**
|
||||
* It will be invoke when ExDownloader download successful
|
||||
*
|
||||
* @param index
|
||||
*/
|
||||
void onGetImage(int index);
|
||||
|
||||
void onDownloading(int index, float percent);
|
||||
|
||||
/**
|
||||
* It will be invoke when decode image over
|
||||
*
|
||||
* @param index
|
||||
* @param res
|
||||
*/
|
||||
void onDecodeOver(int index, Object res);
|
||||
}
|
||||
|
||||
public ImageSet(int gid, String token, String title, int startIndex) {
|
||||
|
||||
mDir = EhUtils.getGalleryDir(gid, title);
|
||||
Utils.ensureDir(mDir, true);
|
||||
|
||||
// Init ExDownloader
|
||||
mEdManager = ExDownloaderManager.getInstance();
|
||||
mExDownloader = mEdManager.getExDownloader(gid, token, title, Config.getMode());
|
||||
mExDownloader.setListenerForImageSet(this);
|
||||
mExDownloader.setStartIndex(startIndex);
|
||||
|
||||
mImageFilenameArray = mExDownloader.getImageFilenameArray();
|
||||
|
||||
// Start decode worker
|
||||
mDecodeWorker = new DecodeWorker();
|
||||
mDecodeWorker.start();
|
||||
}
|
||||
|
||||
public boolean isStop() {
|
||||
return mStopWork;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return mExDownloader.getImageNum();
|
||||
}
|
||||
|
||||
public int getEnsureSize() {
|
||||
return mExDownloader.getMaxEnsureIndex() + 1;
|
||||
}
|
||||
|
||||
public void setImageListener(ImageListener l) {
|
||||
mListener = l;
|
||||
}
|
||||
|
||||
public void addTargetIndex(int index) {
|
||||
mExDownloader.addTargetIndex(index);
|
||||
}
|
||||
|
||||
public void setStartIndex(int startIndex) {
|
||||
mExDownloader.setStartIndex(startIndex);
|
||||
}
|
||||
|
||||
public void setCurReadIndex(int index) {
|
||||
mExDownloader.setCurReadIndex(index);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return mExDownloader.getTitle();
|
||||
}
|
||||
|
||||
public File getImageFile(int index) {
|
||||
File file = null;
|
||||
String name = getImageFilename(index);
|
||||
if (name != null)
|
||||
file = new File(mDir, name);
|
||||
return file;
|
||||
}
|
||||
|
||||
private String getImageFilename(int index) {
|
||||
String filename = mImageFilenameArray.get(index);
|
||||
if (filename == null) {
|
||||
// Just guess filename
|
||||
for (String possibleFilename : EhUtils.getPossibleImageFilenames(index)) {
|
||||
if (new File(mDir, possibleFilename).exists()) {
|
||||
// Get filename
|
||||
filename = possibleFilename;
|
||||
mImageFilenameArray.set(index, filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
public void redownload(int index) {
|
||||
String filename = getImageFilename(index);
|
||||
if (filename != null) {
|
||||
// Delete file
|
||||
new File(mDir, filename).delete();
|
||||
}
|
||||
mExDownloader.addTargetIndex(index);
|
||||
mListener.onGetImage(index);
|
||||
}
|
||||
|
||||
public Object getImage(int index) {
|
||||
String filename = getImageFilename(index);
|
||||
Float percent;
|
||||
|
||||
if ((percent = mPercentMap.get(index)) != null) {
|
||||
return percent;
|
||||
} else if (mExDownloader.isDownloading(index)) {
|
||||
// downloading
|
||||
return RESULT_DOWNLOADING;
|
||||
} else if (filename == null || !new File(mDir, filename).exists()) {
|
||||
// Target index has not being downloading
|
||||
return RESULT_NONE;
|
||||
} else {
|
||||
mDecodeQueue.offer(new DecodeInfo(index, filename));
|
||||
// wake decode worker
|
||||
synchronized(mDecodeQueue) {mDecodeQueue.notify();}
|
||||
return RESULT_DECODE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* You must call it when you do not need it any more
|
||||
*/
|
||||
public void free() {
|
||||
mStopWork = true;
|
||||
// wake decode worker to wash wash sleep
|
||||
synchronized(mDecodeQueue) {mDecodeQueue.notify();}
|
||||
|
||||
// Remove listener for ImageSet
|
||||
mExDownloader.setListenerForImageSet(null);
|
||||
// Free ExDownloader
|
||||
mEdManager.freeExDownloader(mExDownloader);
|
||||
}
|
||||
|
||||
private class DecodeWorker extends BgThread {
|
||||
@Override
|
||||
public void run() {
|
||||
DecodeInfo decodeInfo;
|
||||
while (true) {
|
||||
// Check stop work or not
|
||||
if (mStopWork)
|
||||
break;
|
||||
|
||||
// Get decode task
|
||||
synchronized (mDecodeQueue) {
|
||||
if (mDecodeQueue.isEmpty()) {
|
||||
try {
|
||||
mDecodeQueue.wait();
|
||||
} catch (InterruptedException e) {}
|
||||
continue;
|
||||
}
|
||||
decodeInfo = mDecodeQueue.poll();
|
||||
}
|
||||
|
||||
// do decode
|
||||
final int index = decodeInfo.index;
|
||||
String pathName = Utils.getPathName(mDir.getPath(), decodeInfo.filename);
|
||||
Object res = null;
|
||||
|
||||
if (Utils.SUPPORT_IMAGE && Config.getCustomCodec()) {
|
||||
res = Image.decodeFile(pathName, Config.getDecodeFormat());
|
||||
} else {
|
||||
if (Utils.getExtension(pathName, "jpg").equals("gif"))
|
||||
res = Movie.decodeFile(pathName);
|
||||
else
|
||||
res = BitmapFactory.decodeFile(pathName);
|
||||
}
|
||||
|
||||
final Object _res = res;
|
||||
// Post to UI thread
|
||||
AppHandler.getInstance().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mListener != null)
|
||||
mListener.onDecodeOver(index, _res);
|
||||
}
|
||||
});
|
||||
}
|
||||
Log.d(TAG, "DecodeWorker stop working");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadStart(final int index) {
|
||||
AppHandler.getInstance().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mListener != null)
|
||||
mListener.onDownloading(index, 0.0f);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloading(final int index, final float percent) {
|
||||
mPercentMap.append(index, percent);
|
||||
|
||||
AppHandler.getInstance().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mListener != null)
|
||||
mListener.onDownloading(index, percent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadComplete(final int index) {
|
||||
mPercentMap.remove(index);
|
||||
|
||||
AppHandler.getInstance().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mListener != null)
|
||||
mListener.onGetImage(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFail(int index) {
|
||||
mPercentMap.remove(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
// BasicTexture is a Texture corresponds to a real GL texture.
|
||||
// The state of a BasicTexture indicates whether its data is loaded to GL memory.
|
||||
// If a BasicTexture is loaded into GL memory, it has a GL texture id.
|
||||
public abstract class BasicTexture implements Texture {
|
||||
|
||||
private static final String TAG = "BasicTexture";
|
||||
protected static final int UNSPECIFIED = -1;
|
||||
|
||||
protected static final int STATE_UNLOADED = 0;
|
||||
protected static final int STATE_LOADED = 1;
|
||||
protected static final int STATE_ERROR = -1;
|
||||
|
||||
// Log a warning if a texture is larger along a dimension
|
||||
private static final int MAX_TEXTURE_SIZE = 4096;
|
||||
|
||||
protected int mId = -1;
|
||||
protected int mState;
|
||||
|
||||
protected int mWidth = UNSPECIFIED;
|
||||
protected int mHeight = UNSPECIFIED;
|
||||
|
||||
protected int mTextureWidth;
|
||||
protected int mTextureHeight;
|
||||
|
||||
private boolean mHasBorder;
|
||||
|
||||
protected GLCanvas mCanvasRef = null;
|
||||
private static WeakHashMap<BasicTexture, Object> sAllTextures
|
||||
= new WeakHashMap<BasicTexture, Object>();
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static ThreadLocal sInFinalizer = new ThreadLocal();
|
||||
|
||||
protected BasicTexture(GLCanvas canvas, int id, int state) {
|
||||
setAssociatedCanvas(canvas);
|
||||
mId = id;
|
||||
mState = state;
|
||||
synchronized (sAllTextures) {
|
||||
sAllTextures.put(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
protected BasicTexture() {
|
||||
this(null, 0, STATE_UNLOADED);
|
||||
}
|
||||
|
||||
protected void setAssociatedCanvas(GLCanvas canvas) {
|
||||
mCanvasRef = canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content size of this texture. In OpenGL, the actual texture
|
||||
* size must be of power of 2, the size of the content may be smaller.
|
||||
*/
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0;
|
||||
mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0;
|
||||
if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
|
||||
Log.w(TAG, String.format("texture is too large: %d x %d",
|
||||
mTextureWidth, mTextureHeight), new Exception());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFlippedVertically() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
// Returns the width rounded to the next power of 2.
|
||||
public int getTextureWidth() {
|
||||
return mTextureWidth;
|
||||
}
|
||||
|
||||
// Returns the height rounded to the next power of 2.
|
||||
public int getTextureHeight() {
|
||||
return mTextureHeight;
|
||||
}
|
||||
|
||||
// Returns true if the texture has one pixel transparent border around the
|
||||
// actual content. This is used to avoid jigged edges.
|
||||
//
|
||||
// The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
|
||||
// mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
|
||||
// covered by the texture will use the color of the edge texel. If we add
|
||||
// the transparent border, the color of the edge texel will be mixed with
|
||||
// appropriate amount of transparent.
|
||||
//
|
||||
// Currently our background is black, so we can draw the thumbnails without
|
||||
// enabling blending.
|
||||
public boolean hasBorder() {
|
||||
return mHasBorder;
|
||||
}
|
||||
|
||||
protected void setBorder(boolean hasBorder) {
|
||||
mHasBorder = hasBorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
canvas.drawTexture(this, x, y, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.drawTexture(this, x, y, w, h);
|
||||
}
|
||||
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
canvas.drawTexture(this, source, target);
|
||||
}
|
||||
|
||||
// onBind is called before GLCanvas binds this texture.
|
||||
// It should make sure the data is uploaded to GL memory.
|
||||
abstract protected boolean onBind(GLCanvas canvas);
|
||||
|
||||
// Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
|
||||
abstract protected int getTarget();
|
||||
|
||||
public boolean isLoaded() {
|
||||
return mState == STATE_LOADED;
|
||||
}
|
||||
|
||||
// recycle() is called when the texture will never be used again,
|
||||
// so it can free all resources.
|
||||
public void recycle() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
// yield() is called when the texture will not be used temporarily,
|
||||
// so it can free some resources.
|
||||
// The default implementation unloads the texture from GL memory, so
|
||||
// the subclass should make sure it can reload the texture to GL memory
|
||||
// later, or it will have to override this method.
|
||||
public void yield() {
|
||||
freeResource();
|
||||
}
|
||||
|
||||
private void freeResource() {
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas != null && mId != -1) {
|
||||
canvas.unloadTexture(this);
|
||||
mId = -1; // Don't free it again.
|
||||
}
|
||||
mState = STATE_UNLOADED;
|
||||
setAssociatedCanvas(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void finalize() {
|
||||
sInFinalizer.set(BasicTexture.class);
|
||||
recycle();
|
||||
sInFinalizer.set(null);
|
||||
}
|
||||
|
||||
// This is for deciding if we can call Bitmap's recycle().
|
||||
// We cannot call Bitmap's recycle() in finalizer because at that point
|
||||
// the finalizer of Bitmap may already be called so recycle() will crash.
|
||||
public static boolean inFinalizer() {
|
||||
return sInFinalizer.get() != null;
|
||||
}
|
||||
|
||||
public static void yieldAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidateAllTextures() {
|
||||
synchronized (sAllTextures) {
|
||||
for (BasicTexture t : sAllTextures.keySet()) {
|
||||
t.mState = STATE_UNLOADED;
|
||||
t.setAssociatedCanvas(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
|
||||
//
|
||||
// The texture does not own the Bitmap. The user should make sure the Bitmap
|
||||
// is valid during the texture's lifetime. When the texture is recycled, it
|
||||
// does not free the Bitmap.
|
||||
public class BitmapTexture extends UploadedTexture {
|
||||
protected Bitmap mContentBitmap;
|
||||
|
||||
public BitmapTexture(Bitmap bitmap) {
|
||||
this(bitmap, false);
|
||||
}
|
||||
|
||||
public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
|
||||
super(hasBorder);
|
||||
Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
|
||||
mContentBitmap = bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mContentBitmap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
|
||||
// CanvasTexture is a texture whose content is the drawing on a Canvas.
|
||||
// The subclasses should override onDraw() to draw on the bitmap.
|
||||
// By default CanvasTexture is not opaque.
|
||||
abstract class CanvasTexture extends UploadedTexture {
|
||||
protected Canvas mCanvas;
|
||||
private final Config mConfig;
|
||||
|
||||
public CanvasTexture(int width, int height) {
|
||||
mConfig = Config.ARGB_8888;
|
||||
setSize(width, height);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("WrongCall")
|
||||
protected Bitmap onGetBitmap() {
|
||||
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
|
||||
mCanvas = new Canvas(bitmap);
|
||||
onDraw(mCanvas, bitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected void onDraw(Canvas canvas, Bitmap backing);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
// ColorTexture is a texture which fills the rectangle with the specified color.
|
||||
public class ColorTexture implements Texture {
|
||||
|
||||
private final int mColor;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
public ColorTexture(int color) {
|
||||
mColor = color;
|
||||
mWidth = 1;
|
||||
mHeight = 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
canvas.fillRect(x, y, w, h, mColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return Utils.isOpaque(mColor);
|
||||
}
|
||||
|
||||
public void setSize(int width, int height) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
// ExtTexture is a texture whose content comes from a external texture.
|
||||
// Before drawing, setSize() should be called.
|
||||
public class ExtTexture extends BasicTexture {
|
||||
|
||||
private final int mTarget;
|
||||
|
||||
public ExtTexture(GLCanvas canvas, int target) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
canvas.setTextureParameters(this);
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
|
||||
// FadeInTexture is a texture which begins with a color, then gradually animates
|
||||
// into a given texture.
|
||||
public class FadeInTexture extends FadeTexture implements Texture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeInTexture";
|
||||
|
||||
private final int mColor;
|
||||
private final TiledTexture mTexture;
|
||||
|
||||
public FadeInTexture(int color, TiledTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mColor = color;
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
|
||||
} else {
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
|
||||
// FadeOutTexture is a texture which begins with a given texture, then gradually animates
|
||||
// into fading out totally.
|
||||
public class FadeOutTexture extends FadeTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeOutTexture";
|
||||
|
||||
private final BasicTexture mTexture;
|
||||
|
||||
public FadeOutTexture(BasicTexture texture) {
|
||||
super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
|
||||
mTexture = texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (isAnimating()) {
|
||||
canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
|
||||
canvas.setAlpha(getRatio());
|
||||
mTexture.draw(canvas, x, y, w, h);
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.AnimationTime;
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
// FadeTexture is a texture which fades the given texture along the time.
|
||||
public abstract class FadeTexture implements Texture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "FadeTexture";
|
||||
|
||||
// The duration of the fading animation in milliseconds
|
||||
public static final int DURATION = 180;
|
||||
|
||||
private final long mStartTime;
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final boolean mIsOpaque;
|
||||
private boolean mIsAnimating;
|
||||
|
||||
public FadeTexture(int width, int height, boolean opaque) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mIsOpaque = opaque;
|
||||
mStartTime = now();
|
||||
mIsAnimating = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mIsOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public boolean isAnimating() {
|
||||
if (mIsAnimating) {
|
||||
if (now() - mStartTime >= DURATION) {
|
||||
mIsAnimating = false;
|
||||
}
|
||||
}
|
||||
return mIsAnimating;
|
||||
}
|
||||
|
||||
protected float getRatio() {
|
||||
float r = (float)(now() - mStartTime) / DURATION;
|
||||
return MathUtils.clamp(1.0f - r, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private long now() {
|
||||
return AnimationTime.get();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
|
||||
//
|
||||
// GLCanvas gives a convenient interface to draw using OpenGL.
|
||||
//
|
||||
// When a rectangle is specified in this interface, it means the region
|
||||
// [x, x+width) * [y, y+height)
|
||||
//
|
||||
public interface GLCanvas {
|
||||
|
||||
public GLId getGLId();
|
||||
|
||||
// Tells GLCanvas the size of the underlying GL surface. This should be
|
||||
// called before first drawing and when the size of GL surface is changed.
|
||||
// This is called by GLRoot and should not be called by the clients
|
||||
// who only want to draw on the GLCanvas. Both width and height must be
|
||||
// nonnegative.
|
||||
public abstract void setSize(int width, int height);
|
||||
|
||||
// Clear the drawing buffers. This should only be used by GLRoot.
|
||||
public abstract void clearBuffer();
|
||||
|
||||
public abstract void clearBuffer(float[] argb);
|
||||
|
||||
// Sets and gets the current alpha, alpha must be in [0, 1].
|
||||
public abstract void setAlpha(float alpha);
|
||||
|
||||
public abstract float getAlpha();
|
||||
|
||||
// (current alpha) = (current alpha) * alpha
|
||||
public abstract void multiplyAlpha(float alpha);
|
||||
|
||||
// Change the current transform matrix.
|
||||
public abstract void translate(float x, float y, float z);
|
||||
|
||||
public abstract void translate(float x, float y);
|
||||
|
||||
public abstract void scale(float sx, float sy, float sz);
|
||||
|
||||
public abstract void rotate(float angle, float x, float y, float z);
|
||||
|
||||
public abstract void multiplyMatrix(float[] mMatrix, int offset);
|
||||
|
||||
// Pushes the configuration state (matrix, and alpha) onto
|
||||
// a private stack.
|
||||
public abstract void save();
|
||||
|
||||
// Same as save(), but only save those specified in saveFlags.
|
||||
public abstract void save(int saveFlags);
|
||||
|
||||
public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
|
||||
public static final int SAVE_FLAG_ALPHA = 0x01;
|
||||
public static final int SAVE_FLAG_MATRIX = 0x02;
|
||||
|
||||
// Pops from the top of the stack as current configuration state (matrix,
|
||||
// alpha, and clip). This call balances a previous call to save(), and is
|
||||
// used to remove all modifications to the configuration state since the
|
||||
// last save call.
|
||||
public abstract void restore();
|
||||
|
||||
// Draws a line using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
|
||||
// (Both end points are included).
|
||||
public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
|
||||
|
||||
// Fills the specified rectangle with the specified color.
|
||||
public abstract void fillRect(float x, float y, float width, float height, int color);
|
||||
|
||||
// Draws a texture to the specified rectangle.
|
||||
public abstract void drawTexture(
|
||||
BasicTexture texture, int x, int y, int width, int height);
|
||||
|
||||
public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
|
||||
int uvBuffer, int indexBuffer, int indexCount);
|
||||
|
||||
// Draws the source rectangle part of the texture to the target rectangle.
|
||||
public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
|
||||
|
||||
// Draw a texture with a specified texture transform.
|
||||
public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
|
||||
int x, int y, int w, int h);
|
||||
|
||||
// Draw two textures to the specified rectangle. The actual texture used is
|
||||
// from * (1 - ratio) + to * ratio
|
||||
// The two textures must have the same size.
|
||||
public abstract void drawMixed(BasicTexture from, int toColor,
|
||||
float ratio, int x, int y, int w, int h);
|
||||
|
||||
// Draw a region of a texture and a specified color to the specified
|
||||
// rectangle. The actual color used is from * (1 - ratio) + to * ratio.
|
||||
// The region of the texture is defined by parameter "src". The target
|
||||
// rectangle is specified by parameter "target".
|
||||
public abstract void drawMixed(BasicTexture from, int toColor,
|
||||
float ratio, RectF src, RectF target);
|
||||
|
||||
// Unloads the specified texture from the canvas. The resource allocated
|
||||
// to draw the texture will be released. The specified texture will return
|
||||
// to the unloaded state. This function should be called only from
|
||||
// BasicTexture or its descendant
|
||||
public abstract boolean unloadTexture(BasicTexture texture);
|
||||
|
||||
// Delete the specified buffer object, similar to unloadTexture.
|
||||
public abstract void deleteBuffer(int bufferId);
|
||||
|
||||
// Delete the textures and buffers in GL side. This function should only be
|
||||
// called in the GL thread.
|
||||
public abstract void deleteRecycledResources();
|
||||
|
||||
// Dump statistics information and clear the counters. For debug only.
|
||||
public abstract void dumpStatisticsAndClear();
|
||||
|
||||
public abstract void beginRenderTarget(RawTexture texture);
|
||||
|
||||
public abstract void endRenderTarget();
|
||||
|
||||
/**
|
||||
* Sets texture parameters to use GL_CLAMP_TO_EDGE for both
|
||||
* GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
|
||||
* GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
|
||||
* bindTexture() must be called prior to this.
|
||||
*
|
||||
* @param texture The texture to set parameters on.
|
||||
*/
|
||||
public abstract void setTextureParameters(BasicTexture texture);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
|
||||
|
||||
/**
|
||||
* Initializes the texture to a size by calling texImage2D on it.
|
||||
*
|
||||
* @param texture The texture to initialize the size.
|
||||
* @param bitmap The bitmap to initialize the bitmap with.
|
||||
*/
|
||||
public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
|
||||
|
||||
/**
|
||||
* Calls glTexSubImage2D to upload a bitmap to the texture.
|
||||
*
|
||||
* @param texture The target texture to write to.
|
||||
* @param xOffset Specifies a texel offset in the x direction within the
|
||||
* texture array.
|
||||
* @param yOffset Specifies a texel offset in the y direction within the
|
||||
* texture array.
|
||||
* @param format The texture format (e.g. GL_RGBA)
|
||||
* @param type The texture type (e.g. GL_UNSIGNED_BYTE)
|
||||
*/
|
||||
public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
|
||||
Bitmap bitmap,
|
||||
int format, int type);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
|
||||
|
||||
/**
|
||||
* Generates buffers and uploads the element array buffer data.
|
||||
*
|
||||
* @param buffer The buffer to upload
|
||||
* @return The buffer ID that was generated.
|
||||
*/
|
||||
public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
|
||||
|
||||
/**
|
||||
* After LightCycle makes GL calls, this method is called to restore the GL
|
||||
* configuration to the one expected by GLCanvas.
|
||||
*/
|
||||
public abstract void recoverFromLightCycle();
|
||||
|
||||
/**
|
||||
* Gets the bounds given by x, y, width, and height as well as the internal
|
||||
* matrix state. There is no special handling for non-90-degree rotations.
|
||||
* It only considers the lower-left and upper-right corners as the bounds.
|
||||
*
|
||||
* @param bounds The output bounds to write to.
|
||||
* @param x The left side of the input rectangle.
|
||||
* @param y The bottom of the input rectangle.
|
||||
* @param width The width of the input rectangle.
|
||||
* @param height The height of the input rectangle.
|
||||
*/
|
||||
public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
|
||||
}
|
||||
@@ -0,0 +1,995 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11Ext;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.opengl.GLU;
|
||||
import android.opengl.GLUtils;
|
||||
import android.opengl.Matrix;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.util.IntArray;
|
||||
import com.hippo.ehviewer.util.Utils;
|
||||
|
||||
public class GLES11Canvas implements GLCanvas {
|
||||
private static final String TAG = "GLCanvasImp";
|
||||
|
||||
private static final float OPAQUE_ALPHA = 0.95f;
|
||||
|
||||
private static final int OFFSET_FILL_RECT = 0;
|
||||
private static final int OFFSET_DRAW_LINE = 4;
|
||||
private static final int OFFSET_DRAW_RECT = 6;
|
||||
private static final float[] BOX_COORDINATES = {
|
||||
0, 0, 1, 0, 0, 1, 1, 1, // used for filling a rectangle
|
||||
0, 0, 1, 1, // used for drawing a line
|
||||
0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
|
||||
|
||||
private final GL11 mGL;
|
||||
|
||||
private final float mMatrixValues[] = new float[16];
|
||||
private final float mTextureMatrixValues[] = new float[16];
|
||||
|
||||
// The results of mapPoints are stored in this buffer, and the order is
|
||||
// x1, y1, x2, y2.
|
||||
private final float mMapPointsBuffer[] = new float[4];
|
||||
|
||||
private final float mTextureColor[] = new float[4];
|
||||
|
||||
private final int mBoxCoords;
|
||||
|
||||
private final GLState mGLState;
|
||||
private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
|
||||
|
||||
private float mAlpha;
|
||||
private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
|
||||
private ConfigState mRecycledRestoreAction;
|
||||
|
||||
private final RectF mDrawTextureSourceRect = new RectF();
|
||||
private final RectF mDrawTextureTargetRect = new RectF();
|
||||
private final float[] mTempMatrix = new float[32];
|
||||
private final IntArray mUnboundTextures = new IntArray();
|
||||
private final IntArray mDeleteBuffers = new IntArray();
|
||||
private int mScreenWidth;
|
||||
private int mScreenHeight;
|
||||
private final boolean mBlendEnabled = true;
|
||||
private final int mFrameBuffer[] = new int[1];
|
||||
private static float[] sCropRect = new float[4];
|
||||
|
||||
private RawTexture mTargetTexture;
|
||||
|
||||
// Drawing statistics
|
||||
int mCountDrawLine;
|
||||
int mCountFillRect;
|
||||
int mCountDrawMesh;
|
||||
int mCountTextureRect;
|
||||
int mCountTextureOES;
|
||||
|
||||
private static GLId mGLId = new GLES11IdImpl();
|
||||
|
||||
public GLES11Canvas(GL11 gl) {
|
||||
mGL = gl;
|
||||
mGLState = new GLState(gl);
|
||||
// First create an nio buffer, then create a VBO from it.
|
||||
int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE;
|
||||
FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0);
|
||||
|
||||
int[] name = new int[1];
|
||||
mGLId.glGenBuffers(1, name, 0);
|
||||
mBoxCoords = name[0];
|
||||
|
||||
gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
|
||||
gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
|
||||
xyBuffer, GL11.GL_STATIC_DRAW);
|
||||
|
||||
gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
// Enable the texture coordinate array for Texture 1
|
||||
gl.glClientActiveTexture(GL11.GL_TEXTURE1);
|
||||
gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
gl.glClientActiveTexture(GL11.GL_TEXTURE0);
|
||||
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
// mMatrixValues and mAlpha will be initialized in setSize()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize(int width, int height) {
|
||||
Assert.assertTrue(width >= 0 && height >= 0);
|
||||
|
||||
if (mTargetTexture == null) {
|
||||
mScreenWidth = width;
|
||||
mScreenHeight = height;
|
||||
}
|
||||
mAlpha = 1.0f;
|
||||
|
||||
GL11 gl = mGL;
|
||||
gl.glViewport(0, 0, width, height);
|
||||
gl.glMatrixMode(GL11.GL_PROJECTION);
|
||||
gl.glLoadIdentity();
|
||||
GLU.gluOrtho2D(gl, 0, width, 0, height);
|
||||
|
||||
gl.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
gl.glLoadIdentity();
|
||||
|
||||
float matrix[] = mMatrixValues;
|
||||
Matrix.setIdentityM(matrix, 0);
|
||||
// to match the graphic coordinate system in android, we flip it vertically.
|
||||
if (mTargetTexture == null) {
|
||||
Matrix.translateM(matrix, 0, 0, height, 0);
|
||||
Matrix.scaleM(matrix, 0, 1, -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(float alpha) {
|
||||
Assert.assertTrue(alpha >= 0 && alpha <= 1);
|
||||
mAlpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyAlpha(float alpha) {
|
||||
Assert.assertTrue(alpha >= 0 && alpha <= 1);
|
||||
mAlpha *= alpha;
|
||||
}
|
||||
|
||||
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawRect(float x, float y, float width, float height, GLPaint paint) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
mGLState.setColorMode(paint.getColor(), mAlpha);
|
||||
mGLState.setLineWidth(paint.getLineWidth());
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawLine++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
mGLState.setColorMode(paint.getColor(), mAlpha);
|
||||
mGLState.setLineWidth(paint.getLineWidth());
|
||||
|
||||
saveTransform();
|
||||
translate(x1, y1);
|
||||
scale(x2 - x1, y2 - y1, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawLine++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillRect(float x, float y, float width, float height, int color) {
|
||||
mGLState.setColorMode(color, mAlpha);
|
||||
GL11 gl = mGL;
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountFillRect++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void translate(float x, float y, float z) {
|
||||
Matrix.translateM(mMatrixValues, 0, x, y, z);
|
||||
}
|
||||
|
||||
// This is a faster version of translate(x, y, z) because
|
||||
// (1) we knows z = 0, (2) we inline the Matrix.translateM call,
|
||||
// (3) we unroll the loop
|
||||
@Override
|
||||
public void translate(float x, float y) {
|
||||
float[] m = mMatrixValues;
|
||||
m[12] += m[0] * x + m[4] * y;
|
||||
m[13] += m[1] * x + m[5] * y;
|
||||
m[14] += m[2] * x + m[6] * y;
|
||||
m[15] += m[3] * x + m[7] * y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale(float sx, float sy, float sz) {
|
||||
Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rotate(float angle, float x, float y, float z) {
|
||||
if (angle == 0) return;
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.setRotateM(temp, 0, angle, x, y, z);
|
||||
Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0);
|
||||
System.arraycopy(temp, 16, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multiplyMatrix(float matrix[], int offset) {
|
||||
float[] temp = mTempMatrix;
|
||||
Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
|
||||
System.arraycopy(temp, 0, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
private void textureRect(float x, float y, float width, float height) {
|
||||
GL11 gl = mGL;
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
scale(width, height, 1);
|
||||
|
||||
gl.glLoadMatrixf(mMatrixValues, 0);
|
||||
gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
|
||||
|
||||
restoreTransform();
|
||||
mCountTextureRect++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
|
||||
int uvBuffer, int indexBuffer, int indexCount) {
|
||||
float alpha = mAlpha;
|
||||
if (!bindTexture(tex)) return;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!tex.isOpaque() || alpha < OPAQUE_ALPHA));
|
||||
mGLState.setTextureAlpha(alpha);
|
||||
|
||||
// Reset the texture matrix. We will set our own texture coordinates
|
||||
// below.
|
||||
setTextureCoords(0, 0, 1, 1);
|
||||
|
||||
saveTransform();
|
||||
translate(x, y);
|
||||
|
||||
mGL.glLoadMatrixf(mMatrixValues, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer);
|
||||
mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer);
|
||||
mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
|
||||
indexCount, GL11.GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
|
||||
mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
|
||||
|
||||
restoreTransform();
|
||||
mCountDrawMesh++;
|
||||
}
|
||||
|
||||
// Transforms two points by the given matrix m. The result
|
||||
// {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
|
||||
private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
|
||||
float[] r = mMapPointsBuffer;
|
||||
|
||||
// Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
|
||||
float x3 = m[0] * x1 + m[4] * y1 + m[12];
|
||||
float y3 = m[1] * x1 + m[5] * y1 + m[13];
|
||||
float w3 = m[3] * x1 + m[7] * y1 + m[15];
|
||||
r[0] = x3 / w3;
|
||||
r[1] = y3 / w3;
|
||||
|
||||
// Same for x2 y2.
|
||||
float x4 = m[0] * x2 + m[4] * y2 + m[12];
|
||||
float y4 = m[1] * x2 + m[5] * y2 + m[13];
|
||||
float w4 = m[3] * x2 + m[7] * y2 + m[15];
|
||||
r[2] = x4 / w4;
|
||||
r[3] = y4 / w4;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
private void drawBoundTexture(
|
||||
BasicTexture texture, int x, int y, int width, int height) {
|
||||
// Test whether it has been rotated or flipped, if so, glDrawTexiOES
|
||||
// won't work
|
||||
if (isMatrixRotatedOrFlipped(mMatrixValues)) {
|
||||
if (texture.hasBorder()) {
|
||||
setTextureCoords(
|
||||
1.0f / texture.getTextureWidth(),
|
||||
1.0f / texture.getTextureHeight(),
|
||||
(texture.getWidth() - 1.0f) / texture.getTextureWidth(),
|
||||
(texture.getHeight() - 1.0f) / texture.getTextureHeight());
|
||||
} else {
|
||||
setTextureCoords(0, 0,
|
||||
(float) texture.getWidth() / texture.getTextureWidth(),
|
||||
(float) texture.getHeight() / texture.getTextureHeight());
|
||||
}
|
||||
textureRect(x, y, width, height);
|
||||
} else {
|
||||
// draw the rect from bottom-left to top-right
|
||||
float points[] = mapPoints(
|
||||
mMatrixValues, x, y + height, x + width, y);
|
||||
x = (int) (points[0] + 0.5f);
|
||||
y = (int) (points[1] + 0.5f);
|
||||
width = (int) (points[2] + 0.5f) - x;
|
||||
height = (int) (points[3] + 0.5f) - y;
|
||||
if (width > 0 && height > 0) {
|
||||
((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
|
||||
mCountTextureOES++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(
|
||||
BasicTexture texture, int x, int y, int width, int height) {
|
||||
drawTexture(texture, x, y, width, height, mAlpha);
|
||||
}
|
||||
|
||||
private void drawTexture(BasicTexture texture,
|
||||
int x, int y, int width, int height, float alpha) {
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || alpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
mGLState.setTextureAlpha(alpha);
|
||||
drawBoundTexture(texture, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(BasicTexture texture, RectF source, RectF target) {
|
||||
if (target.width() <= 0 || target.height() <= 0) return;
|
||||
|
||||
// Copy the input to avoid changing it.
|
||||
mDrawTextureSourceRect.set(source);
|
||||
mDrawTextureTargetRect.set(target);
|
||||
source = mDrawTextureSourceRect;
|
||||
target = mDrawTextureTargetRect;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
convertCoordinate(source, target, texture);
|
||||
setTextureCoords(source);
|
||||
mGLState.setTextureAlpha(mAlpha);
|
||||
textureRect(target.left, target.top, target.width(), target.height());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTexture(BasicTexture texture, float[] mTextureTransform,
|
||||
int x, int y, int w, int h) {
|
||||
mGLState.setBlendEnabled(mBlendEnabled
|
||||
&& (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
|
||||
if (!bindTexture(texture)) return;
|
||||
setTextureCoords(mTextureTransform);
|
||||
mGLState.setTextureAlpha(mAlpha);
|
||||
textureRect(x, y, w, h);
|
||||
}
|
||||
|
||||
// This function changes the source coordinate to the texture coordinates.
|
||||
// It also clips the source and target coordinates if it is beyond the
|
||||
// bound of the texture.
|
||||
private static void convertCoordinate(RectF source, RectF target,
|
||||
BasicTexture texture) {
|
||||
|
||||
int width = texture.getWidth();
|
||||
int height = texture.getHeight();
|
||||
int texWidth = texture.getTextureWidth();
|
||||
int texHeight = texture.getTextureHeight();
|
||||
// Convert to texture coordinates
|
||||
source.left /= texWidth;
|
||||
source.right /= texWidth;
|
||||
source.top /= texHeight;
|
||||
source.bottom /= texHeight;
|
||||
|
||||
// Clip if the rendering range is beyond the bound of the texture.
|
||||
float xBound = (float) width / texWidth;
|
||||
if (source.right > xBound) {
|
||||
target.right = target.left + target.width() *
|
||||
(xBound - source.left) / source.width();
|
||||
source.right = xBound;
|
||||
}
|
||||
float yBound = (float) height / texHeight;
|
||||
if (source.bottom > yBound) {
|
||||
target.bottom = target.top + target.height() *
|
||||
(yBound - source.top) / source.height();
|
||||
source.bottom = yBound;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMixed(BasicTexture from,
|
||||
int toColor, float ratio, int x, int y, int w, int h) {
|
||||
drawMixed(from, toColor, ratio, x, y, w, h, mAlpha);
|
||||
}
|
||||
|
||||
private boolean bindTexture(BasicTexture texture) {
|
||||
if (!texture.onBind(this)) return false;
|
||||
int target = texture.getTarget();
|
||||
mGLState.setTextureTarget(target);
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setTextureColor(float r, float g, float b, float alpha) {
|
||||
float[] color = mTextureColor;
|
||||
color[0] = r;
|
||||
color[1] = g;
|
||||
color[2] = b;
|
||||
color[3] = alpha;
|
||||
}
|
||||
|
||||
private void setMixedColor(int toColor, float ratio, float alpha) {
|
||||
//
|
||||
// The formula we want:
|
||||
// alpha * ((1 - ratio) * from + ratio * to)
|
||||
//
|
||||
// The formula that GL supports is in the form of:
|
||||
// combo * from + (1 - combo) * to * scale
|
||||
//
|
||||
// So, we have combo = alpha * (1 - ratio)
|
||||
// and scale = alpha * ratio / (1 - combo)
|
||||
//
|
||||
float combo = alpha * (1 - ratio);
|
||||
float scale = alpha * ratio / (1 - combo);
|
||||
|
||||
// Specify the interpolation factor via the alpha component of
|
||||
// GL_TEXTURE_ENV_COLORs.
|
||||
// RGB component are get from toColor and will used as SRC1
|
||||
float colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
|
||||
setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
|
||||
((toColor >>> 8) & 0xff) * colorScale,
|
||||
(toColor & 0xff) * colorScale, combo);
|
||||
GL11 gl = mGL;
|
||||
gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
|
||||
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Wire up the interpolation factor for RGB.
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Wire up the interpolation factor for alpha.
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT);
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawMixed(BasicTexture from, int toColor, float ratio,
|
||||
RectF source, RectF target) {
|
||||
if (target.width() <= 0 || target.height() <= 0) return;
|
||||
|
||||
if (ratio <= 0.01f) {
|
||||
drawTexture(from, source, target);
|
||||
return;
|
||||
} else if (ratio >= 1) {
|
||||
fillRect(target.left, target.top, target.width(), target.height(), toColor);
|
||||
return;
|
||||
}
|
||||
|
||||
float alpha = mAlpha;
|
||||
|
||||
// Copy the input to avoid changing it.
|
||||
mDrawTextureSourceRect.set(source);
|
||||
mDrawTextureTargetRect.set(target);
|
||||
source = mDrawTextureSourceRect;
|
||||
target = mDrawTextureTargetRect;
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
|
||||
|| !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
|
||||
|
||||
if (!bindTexture(from)) return;
|
||||
|
||||
// Interpolate the RGB and alpha values between both textures.
|
||||
mGLState.setTexEnvMode(GL11.GL_COMBINE);
|
||||
setMixedColor(toColor, ratio, alpha);
|
||||
convertCoordinate(source, target, from);
|
||||
setTextureCoords(source);
|
||||
textureRect(target.left, target.top, target.width(), target.height());
|
||||
mGLState.setTexEnvMode(GL11.GL_REPLACE);
|
||||
}
|
||||
|
||||
private void drawMixed(BasicTexture from, int toColor,
|
||||
float ratio, int x, int y, int width, int height, float alpha) {
|
||||
// change from 0 to 0.01f to prevent getting divided by zero below
|
||||
if (ratio <= 0.01f) {
|
||||
drawTexture(from, x, y, width, height, alpha);
|
||||
return;
|
||||
} else if (ratio >= 1) {
|
||||
fillRect(x, y, width, height, toColor);
|
||||
return;
|
||||
}
|
||||
|
||||
mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
|
||||
|| !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
|
||||
|
||||
if (!bindTexture(from)) return;
|
||||
|
||||
// Interpolate the RGB and alpha values between both textures.
|
||||
mGLState.setTexEnvMode(GL11.GL_COMBINE);
|
||||
setMixedColor(toColor, ratio, alpha);
|
||||
|
||||
drawBoundTexture(from, x, y, width, height);
|
||||
mGLState.setTexEnvMode(GL11.GL_REPLACE);
|
||||
}
|
||||
|
||||
// TODO: the code only work for 2D should get fixed for 3D or removed
|
||||
private static final int MSKEW_X = 4;
|
||||
private static final int MSKEW_Y = 1;
|
||||
private static final int MSCALE_X = 0;
|
||||
private static final int MSCALE_Y = 5;
|
||||
|
||||
private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
|
||||
final float eps = 1e-5f;
|
||||
return Math.abs(matrix[MSKEW_X]) > eps
|
||||
|| Math.abs(matrix[MSKEW_Y]) > eps
|
||||
|| matrix[MSCALE_X] < -eps
|
||||
|| matrix[MSCALE_Y] > eps;
|
||||
}
|
||||
|
||||
private static class GLState {
|
||||
|
||||
private final GL11 mGL;
|
||||
|
||||
private int mTexEnvMode = GL11.GL_REPLACE;
|
||||
private float mTextureAlpha = 1.0f;
|
||||
private int mTextureTarget = GL11.GL_TEXTURE_2D;
|
||||
private boolean mBlendEnabled = true;
|
||||
private float mLineWidth = 1.0f;
|
||||
|
||||
public GLState(GL11 gl) {
|
||||
mGL = gl;
|
||||
|
||||
// Disable unused state
|
||||
gl.glDisable(GL11.GL_LIGHTING);
|
||||
|
||||
// Enable used features
|
||||
gl.glEnable(GL11.GL_DITHER);
|
||||
|
||||
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
|
||||
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
|
||||
gl.glEnable(GL11.GL_TEXTURE_2D);
|
||||
|
||||
gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
|
||||
GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
|
||||
|
||||
// Set the background color
|
||||
gl.glClearColor(0f, 0f, 0f, 0f);
|
||||
|
||||
gl.glEnable(GL11.GL_BLEND);
|
||||
gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// We use 565 or 8888 format, so set the alignment to 2 bytes/pixel.
|
||||
gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
|
||||
}
|
||||
|
||||
public void setTexEnvMode(int mode) {
|
||||
if (mTexEnvMode == mode) return;
|
||||
mTexEnvMode = mode;
|
||||
mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode);
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
if (mLineWidth == width) return;
|
||||
mLineWidth = width;
|
||||
mGL.glLineWidth(width);
|
||||
}
|
||||
|
||||
public void setTextureAlpha(float alpha) {
|
||||
if (mTextureAlpha == alpha) return;
|
||||
mTextureAlpha = alpha;
|
||||
if (alpha >= OPAQUE_ALPHA) {
|
||||
// The alpha is need for those texture without alpha channel
|
||||
mGL.glColor4f(1, 1, 1, 1);
|
||||
setTexEnvMode(GL11.GL_REPLACE);
|
||||
} else {
|
||||
mGL.glColor4f(alpha, alpha, alpha, alpha);
|
||||
setTexEnvMode(GL11.GL_MODULATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColorMode(int color, float alpha) {
|
||||
setBlendEnabled(!Utils.isOpaque(color) || alpha < OPAQUE_ALPHA);
|
||||
|
||||
// Set mTextureAlpha to an invalid value, so that it will reset
|
||||
// again in setTextureAlpha(float) later.
|
||||
mTextureAlpha = -1.0f;
|
||||
|
||||
setTextureTarget(0);
|
||||
|
||||
float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f;
|
||||
mGL.glColor4x(
|
||||
Math.round(((color >> 16) & 0xFF) * prealpha),
|
||||
Math.round(((color >> 8) & 0xFF) * prealpha),
|
||||
Math.round((color & 0xFF) * prealpha),
|
||||
Math.round(255 * prealpha));
|
||||
}
|
||||
|
||||
// target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
|
||||
public void setTextureTarget(int target) {
|
||||
if (mTextureTarget == target) return;
|
||||
if (mTextureTarget != 0) {
|
||||
mGL.glDisable(mTextureTarget);
|
||||
}
|
||||
mTextureTarget = target;
|
||||
if (mTextureTarget != 0) {
|
||||
mGL.glEnable(mTextureTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlendEnabled(boolean enabled) {
|
||||
if (mBlendEnabled == enabled) return;
|
||||
mBlendEnabled = enabled;
|
||||
if (enabled) {
|
||||
mGL.glEnable(GL11.GL_BLEND);
|
||||
} else {
|
||||
mGL.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBuffer(float[] argb) {
|
||||
if(argb != null && argb.length == 4) {
|
||||
mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]);
|
||||
} else {
|
||||
mGL.glClearColor(0, 0, 0, 1);
|
||||
}
|
||||
mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearBuffer() {
|
||||
clearBuffer(null);
|
||||
}
|
||||
|
||||
private void setTextureCoords(RectF source) {
|
||||
setTextureCoords(source.left, source.top, source.right, source.bottom);
|
||||
}
|
||||
|
||||
private void setTextureCoords(float left, float top,
|
||||
float right, float bottom) {
|
||||
mGL.glMatrixMode(GL11.GL_TEXTURE);
|
||||
mTextureMatrixValues[0] = right - left;
|
||||
mTextureMatrixValues[5] = bottom - top;
|
||||
mTextureMatrixValues[10] = 1;
|
||||
mTextureMatrixValues[12] = left;
|
||||
mTextureMatrixValues[13] = top;
|
||||
mTextureMatrixValues[15] = 1;
|
||||
mGL.glLoadMatrixf(mTextureMatrixValues, 0);
|
||||
mGL.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
private void setTextureCoords(float[] mTextureTransform) {
|
||||
mGL.glMatrixMode(GL11.GL_TEXTURE);
|
||||
mGL.glLoadMatrixf(mTextureTransform, 0);
|
||||
mGL.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
// unloadTexture and deleteBuffer can be called from the finalizer thread,
|
||||
// so we synchronized on the mUnboundTextures object.
|
||||
@Override
|
||||
public boolean unloadTexture(BasicTexture t) {
|
||||
synchronized (mUnboundTextures) {
|
||||
if (!t.isLoaded()) return false;
|
||||
mUnboundTextures.add(t.mId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBuffer(int bufferId) {
|
||||
synchronized (mUnboundTextures) {
|
||||
mDeleteBuffers.add(bufferId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRecycledResources() {
|
||||
synchronized (mUnboundTextures) {
|
||||
IntArray ids = mUnboundTextures;
|
||||
if (ids.size() > 0) {
|
||||
mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
|
||||
ids.clear();
|
||||
}
|
||||
|
||||
ids = mDeleteBuffers;
|
||||
if (ids.size() > 0) {
|
||||
mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
|
||||
ids.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
save(SAVE_FLAG_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(int saveFlags) {
|
||||
ConfigState config = obtainRestoreConfig();
|
||||
|
||||
if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
|
||||
config.mAlpha = mAlpha;
|
||||
} else {
|
||||
config.mAlpha = -1;
|
||||
}
|
||||
|
||||
if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
|
||||
System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
|
||||
} else {
|
||||
config.mMatrix[0] = Float.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
mRestoreStack.add(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restore() {
|
||||
if (mRestoreStack.isEmpty()) throw new IllegalStateException();
|
||||
ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
|
||||
config.restore(this);
|
||||
freeRestoreConfig(config);
|
||||
}
|
||||
|
||||
private void freeRestoreConfig(ConfigState action) {
|
||||
action.mNextFree = mRecycledRestoreAction;
|
||||
mRecycledRestoreAction = action;
|
||||
}
|
||||
|
||||
private ConfigState obtainRestoreConfig() {
|
||||
if (mRecycledRestoreAction != null) {
|
||||
ConfigState result = mRecycledRestoreAction;
|
||||
mRecycledRestoreAction = result.mNextFree;
|
||||
return result;
|
||||
}
|
||||
return new ConfigState();
|
||||
}
|
||||
|
||||
private static class ConfigState {
|
||||
float mAlpha;
|
||||
float mMatrix[] = new float[16];
|
||||
ConfigState mNextFree;
|
||||
|
||||
public void restore(GLES11Canvas canvas) {
|
||||
if (mAlpha >= 0) canvas.setAlpha(mAlpha);
|
||||
if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
|
||||
System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("DefaultLocale")
|
||||
public void dumpStatisticsAndClear() {
|
||||
String line = String.format(
|
||||
"MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d",
|
||||
mCountDrawMesh, mCountTextureRect, mCountTextureOES,
|
||||
mCountFillRect, mCountDrawLine);
|
||||
mCountDrawMesh = 0;
|
||||
mCountTextureRect = 0;
|
||||
mCountTextureOES = 0;
|
||||
mCountFillRect = 0;
|
||||
mCountDrawLine = 0;
|
||||
Log.d(TAG, line);
|
||||
}
|
||||
|
||||
private void saveTransform() {
|
||||
System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16);
|
||||
}
|
||||
|
||||
private void restoreTransform() {
|
||||
System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16);
|
||||
}
|
||||
|
||||
private void setRenderTarget(RawTexture texture) {
|
||||
GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
|
||||
|
||||
if (mTargetTexture == null && texture != null) {
|
||||
mGLId.glGenBuffers(1, mFrameBuffer, 0);
|
||||
gl11ep.glBindFramebufferOES(
|
||||
GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
|
||||
}
|
||||
if (mTargetTexture != null && texture == null) {
|
||||
gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
|
||||
gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
|
||||
}
|
||||
|
||||
mTargetTexture = texture;
|
||||
if (texture == null) {
|
||||
setSize(mScreenWidth, mScreenHeight);
|
||||
} else {
|
||||
setSize(texture.getWidth(), texture.getHeight());
|
||||
|
||||
if (!texture.isLoaded()) texture.prepare(this);
|
||||
|
||||
gl11ep.glFramebufferTexture2DOES(
|
||||
GL11ExtensionPack.GL_FRAMEBUFFER_OES,
|
||||
GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
|
||||
GL11.GL_TEXTURE_2D, texture.getId(), 0);
|
||||
|
||||
checkFramebufferStatus(gl11ep);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endRenderTarget() {
|
||||
RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
|
||||
setRenderTarget(texture);
|
||||
restore(); // restore matrix and alpha
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginRenderTarget(RawTexture texture) {
|
||||
save(); // save matrix and alpha
|
||||
mTargetStack.add(mTargetTexture);
|
||||
setRenderTarget(texture);
|
||||
}
|
||||
|
||||
private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
|
||||
int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
|
||||
if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
|
||||
String msg = "";
|
||||
switch (status) {
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
|
||||
msg = "FRAMEBUFFER_FORMATS";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
|
||||
msg = "FRAMEBUFFER_ATTACHMENT";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
|
||||
msg = "FRAMEBUFFER_MISSING_ATTACHMENT";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
|
||||
msg = "FRAMEBUFFER_DRAW_BUFFER";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
|
||||
msg = "FRAMEBUFFER_READ_BUFFER";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
|
||||
msg = "FRAMEBUFFER_UNSUPPORTED";
|
||||
break;
|
||||
case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
|
||||
msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
|
||||
break;
|
||||
}
|
||||
throw new RuntimeException(msg + ":" + Integer.toHexString(status));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTextureParameters(BasicTexture texture) {
|
||||
int width = texture.getWidth();
|
||||
int height = texture.getHeight();
|
||||
// Define a vertically flipped crop rectangle for OES_draw_texture.
|
||||
// The four values in sCropRect are: left, bottom, width, and
|
||||
// height. Negative value of width or height means flip.
|
||||
sCropRect[0] = 0;
|
||||
sCropRect[1] = height;
|
||||
sCropRect[2] = width;
|
||||
sCropRect[3] = -height;
|
||||
|
||||
// Set texture parameters.
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
|
||||
mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
|
||||
mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
|
||||
mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTextureSize(BasicTexture texture, int format, int type) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
int width = texture.getTextureWidth();
|
||||
int height = texture.getTextureHeight();
|
||||
mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
GLUtils.texImage2D(target, 0, bitmap, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
|
||||
int format, int type) {
|
||||
int target = texture.getTarget();
|
||||
mGL.glBindTexture(target, texture.getId());
|
||||
GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uploadBuffer(FloatBuffer buf) {
|
||||
return uploadBuffer(buf, Float.SIZE / Byte.SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int uploadBuffer(ByteBuffer buf) {
|
||||
return uploadBuffer(buf, 1);
|
||||
}
|
||||
|
||||
private int uploadBuffer(Buffer buf, int elementSize) {
|
||||
int[] bufferIds = new int[1];
|
||||
mGLId.glGenBuffers(bufferIds.length, bufferIds, 0);
|
||||
int bufferId = bufferIds[0];
|
||||
mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId);
|
||||
mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
|
||||
GL11.GL_STATIC_DRAW);
|
||||
return bufferId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recoverFromLightCycle() {
|
||||
// This is only required for GLES20
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getBounds(Rect bounds, int x, int y, int width, int height) {
|
||||
// This is only required for GLES20
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLId getGLId() {
|
||||
return mGLId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
/**
|
||||
* Open GL ES 1.1 implementation for generating and destroying texture IDs and
|
||||
* buffer IDs
|
||||
*/
|
||||
public class GLES11IdImpl implements GLId {
|
||||
private static int sNextId = 1;
|
||||
// Mutex for sNextId
|
||||
private static Object sLock = new Object();
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
synchronized (sLock) {
|
||||
return sNextId++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
while (n-- > 0) {
|
||||
buffers[offset + n] = sNextId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteTextures(n, textures, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl.glDeleteBuffers(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
synchronized (sLock) {
|
||||
gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,42 @@
|
||||
package com.hippo.ehviewer.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
|
||||
public class GLES20IdImpl implements GLId {
|
||||
private final int[] mTempIntArray = new int[1];
|
||||
|
||||
@Override
|
||||
public int generateTexture() {
|
||||
GLES20.glGenTextures(1, mTempIntArray, 0);
|
||||
GLES20Canvas.checkError();
|
||||
return mTempIntArray[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenBuffers(int n, int[] buffers, int offset) {
|
||||
GLES20.glGenBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
|
||||
GLES20.glDeleteTextures(n, textures, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteBuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
|
||||
GLES20.glDeleteFramebuffers(n, buffers, offset);
|
||||
GLES20Canvas.checkError();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
import javax.microedition.khronos.opengles.GL11ExtensionPack;
|
||||
|
||||
// This mimics corresponding GL functions.
|
||||
public interface GLId {
|
||||
public int generateTexture();
|
||||
|
||||
public void glGenBuffers(int n, int[] buffers, int offset);
|
||||
|
||||
public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
|
||||
|
||||
public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
|
||||
|
||||
public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class GLPaint {
|
||||
private float mLineWidth = 1f;
|
||||
private int mColor = 0;
|
||||
|
||||
public GLPaint(int color, float lineWidth) {
|
||||
setColor(color);
|
||||
setLineWidth(lineWidth);
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mColor = color;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return mColor;
|
||||
}
|
||||
|
||||
public void setLineWidth(float width) {
|
||||
Assert.assertTrue(width >= 0);
|
||||
mLineWidth = width;
|
||||
}
|
||||
|
||||
public float getLineWidth() {
|
||||
return mLineWidth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.gallery.image.Image;
|
||||
|
||||
public class ImageTexture extends BasicTexture implements Uploaded {
|
||||
|
||||
private static final String TAG = ImageTexture.class.getSimpleName();
|
||||
|
||||
private Image mImage;
|
||||
private boolean mContentValid = true;
|
||||
private boolean mOpaque = true;
|
||||
|
||||
public ImageTexture(Image image) {
|
||||
mImage = image;
|
||||
setSize(image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mImage != null)
|
||||
mImage.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mImage != null)
|
||||
mImage.stop();
|
||||
}
|
||||
|
||||
public boolean isAnimated() {
|
||||
if (mImage == null || mImage.isRecycled())
|
||||
return false;
|
||||
else
|
||||
return mImage.isAnimated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
public void setOpaque(boolean opaque) {
|
||||
mOpaque = opaque;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content on GPU is valid.
|
||||
*/
|
||||
@Override
|
||||
public boolean isContentValid() {
|
||||
return isLoaded() && mContentValid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
return mImage.getFormat();
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mImage.getType();
|
||||
}
|
||||
|
||||
public static void checkError() {
|
||||
int error = GLES20.glGetError();
|
||||
if (error != 0) {
|
||||
Throwable t = new Throwable();
|
||||
Log.e(TAG, "GL error: " + error, t);
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
mId = canvas.getGLId().generateTexture();
|
||||
int format = getFormat();
|
||||
int type = getType();
|
||||
|
||||
canvas.setTextureParameters(this);
|
||||
canvas.initializeTextureSize(this, format, type);
|
||||
|
||||
GLES20.glBindTexture(getTarget(), mId);
|
||||
checkError();
|
||||
mImage.render();
|
||||
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
mContentValid = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateContent(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
uploadToCanvas(canvas);
|
||||
} else if (isAnimated()) {
|
||||
GLES20.glBindTexture(getTarget(), mId);
|
||||
checkError();
|
||||
mImage.render();
|
||||
|
||||
mContentValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (mImage == null || mImage.isRecycled()) {
|
||||
return false;
|
||||
} else {
|
||||
updateContent(canvas);
|
||||
return isContentValid();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
if (mImage != null) {
|
||||
mImage.recycle();
|
||||
mImage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.glrenderer;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Movie;
|
||||
|
||||
public class MovieTexture extends CanvasTexture {
|
||||
private static final int DEFAULT_MOVIE_DURATION = 1000;
|
||||
|
||||
private final Movie mMovie;
|
||||
private final int mDuration;
|
||||
private final long mMovieStart;
|
||||
|
||||
private MovieTexture(Movie movie, int width, int height) {
|
||||
super(width, height);
|
||||
mMovie = movie;
|
||||
int dur = mMovie.duration();
|
||||
if (dur == 0)
|
||||
mDuration = DEFAULT_MOVIE_DURATION;
|
||||
else
|
||||
mDuration = dur;
|
||||
mMovieStart = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static MovieTexture newInstance(Movie movie) {
|
||||
Assert.assertTrue(movie != null);
|
||||
return new MovieTexture(movie, movie.width(), movie.height());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
invalidateContent();
|
||||
setSize(mMovie.width(), mMovie.height());
|
||||
return super.onBind(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
mMovie.setTime((int)((System.currentTimeMillis() - mMovieStart) % mDuration));
|
||||
mMovie.draw(canvas, 0, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.text.Layout;
|
||||
import android.text.StaticLayout;
|
||||
import android.text.TextPaint;
|
||||
|
||||
|
||||
// MultiLineTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a MultiLineTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
class MultiLineTexture extends CanvasTexture {
|
||||
private final Layout mLayout;
|
||||
|
||||
private MultiLineTexture(Layout layout) {
|
||||
super(layout.getWidth(), layout.getHeight());
|
||||
mLayout = layout;
|
||||
}
|
||||
|
||||
public static MultiLineTexture newInstance(
|
||||
String text, int maxWidth, float textSize, int color,
|
||||
Layout.Alignment alignment) {
|
||||
TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
|
||||
Layout layout = new StaticLayout(text, 0, text.length(), paint,
|
||||
maxWidth, alignment, 1, 0, true, null, 0);
|
||||
|
||||
return new MultiLineTexture(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
mLayout.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
|
||||
// NinePatch chunk.
|
||||
class NinePatchChunk {
|
||||
|
||||
public static final int NO_COLOR = 0x00000001;
|
||||
public static final int TRANSPARENT_COLOR = 0x00000000;
|
||||
|
||||
public Rect mPaddings = new Rect();
|
||||
|
||||
public int mDivX[];
|
||||
public int mDivY[];
|
||||
public int mColor[];
|
||||
|
||||
private static void readIntArray(int[] data, ByteBuffer buffer) {
|
||||
for (int i = 0, n = data.length; i < n; ++i) {
|
||||
data[i] = buffer.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDivCount(int length) {
|
||||
if (length == 0 || (length & 0x01) != 0) {
|
||||
throw new RuntimeException("invalid nine-patch: " + length);
|
||||
}
|
||||
}
|
||||
|
||||
public static NinePatchChunk deserialize(byte[] data) {
|
||||
ByteBuffer byteBuffer =
|
||||
ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
|
||||
byte wasSerialized = byteBuffer.get();
|
||||
if (wasSerialized == 0) return null;
|
||||
|
||||
NinePatchChunk chunk = new NinePatchChunk();
|
||||
chunk.mDivX = new int[byteBuffer.get()];
|
||||
chunk.mDivY = new int[byteBuffer.get()];
|
||||
chunk.mColor = new int[byteBuffer.get()];
|
||||
|
||||
checkDivCount(chunk.mDivX.length);
|
||||
checkDivCount(chunk.mDivY.length);
|
||||
|
||||
// skip 8 bytes
|
||||
byteBuffer.getInt();
|
||||
byteBuffer.getInt();
|
||||
|
||||
chunk.mPaddings.left = byteBuffer.getInt();
|
||||
chunk.mPaddings.right = byteBuffer.getInt();
|
||||
chunk.mPaddings.top = byteBuffer.getInt();
|
||||
chunk.mPaddings.bottom = byteBuffer.getInt();
|
||||
|
||||
// skip 4 bytes
|
||||
byteBuffer.getInt();
|
||||
|
||||
readIntArray(chunk.mDivX, byteBuffer);
|
||||
readIntArray(chunk.mDivY, byteBuffer);
|
||||
readIntArray(chunk.mColor, byteBuffer);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.hippo.ehviewer.util.MathUtils;
|
||||
|
||||
// NinePatchTexture is a texture backed by a NinePatch resource.
|
||||
//
|
||||
// getPaddings() returns paddings specified in the NinePatch.
|
||||
// getNinePatchChunk() returns the layout data specified in the NinePatch.
|
||||
//
|
||||
public class NinePatchTexture extends ResourceTexture {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchTexture";
|
||||
private NinePatchChunk mChunk;
|
||||
private final SmallCache<NinePatchInstance> mInstanceCache
|
||||
= new SmallCache<NinePatchInstance>();
|
||||
|
||||
public NinePatchTexture(Context context, int resId) {
|
||||
super(context, resId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
if (mBitmap != null) return mBitmap;
|
||||
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
Bitmap bitmap = BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
mBitmap = bitmap;
|
||||
setSize(bitmap.getWidth(), bitmap.getHeight());
|
||||
byte[] chunkData = bitmap.getNinePatchChunk();
|
||||
mChunk = chunkData == null
|
||||
? null
|
||||
: NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
|
||||
if (mChunk == null) {
|
||||
throw new RuntimeException("invalid nine-patch image: " + mResId);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public Rect getPaddings() {
|
||||
// get the paddings from nine patch
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk.mPaddings;
|
||||
}
|
||||
|
||||
public NinePatchChunk getNinePatchChunk() {
|
||||
if (mChunk == null) onGetBitmap();
|
||||
return mChunk;
|
||||
}
|
||||
|
||||
// This is a simple cache for a small number of things. Linear search
|
||||
// is used because the cache is small. It also tries to remove less used
|
||||
// item when the cache is full by moving the often-used items to the front.
|
||||
private static class SmallCache<V> {
|
||||
private static final int CACHE_SIZE = 16;
|
||||
private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
|
||||
private final int[] mKey = new int[CACHE_SIZE];
|
||||
@SuppressWarnings("unchecked")
|
||||
private final V[] mValue = (V[]) new Object[CACHE_SIZE];
|
||||
private int mCount; // number of items in this cache
|
||||
|
||||
// Puts a value into the cache. If the cache is full, also returns
|
||||
// a less used item, otherwise returns null.
|
||||
public V put(int key, V value) {
|
||||
if (mCount == CACHE_SIZE) {
|
||||
V old = mValue[CACHE_SIZE - 1]; // remove the last item
|
||||
mKey[CACHE_SIZE - 1] = key;
|
||||
mValue[CACHE_SIZE - 1] = value;
|
||||
return old;
|
||||
} else {
|
||||
mKey[mCount] = key;
|
||||
mValue[mCount] = value;
|
||||
mCount++;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public V get(int key) {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
if (mKey[i] == key) {
|
||||
// Move the accessed item one position to the front, so it
|
||||
// will less likely to be removed when cache is full. Only
|
||||
// do this if the cache is starting to get full.
|
||||
if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
|
||||
int tmpKey = mKey[i];
|
||||
mKey[i] = mKey[i - 1];
|
||||
mKey[i - 1] = tmpKey;
|
||||
|
||||
V tmpValue = mValue[i];
|
||||
mValue[i] = mValue[i - 1];
|
||||
mValue[i - 1] = tmpValue;
|
||||
}
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (int i = 0; i < mCount; i++) {
|
||||
mValue[i] = null; // make sure it's can be garbage-collected.
|
||||
}
|
||||
mCount = 0;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
public V valueAt(int i) {
|
||||
return mValue[i];
|
||||
}
|
||||
}
|
||||
|
||||
private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
|
||||
int key = w;
|
||||
key = (key << 16) | h;
|
||||
NinePatchInstance instance = mInstanceCache.get(key);
|
||||
|
||||
if (instance == null) {
|
||||
instance = new NinePatchInstance(this, w, h);
|
||||
NinePatchInstance removed = mInstanceCache.put(key, instance);
|
||||
if (removed != null) {
|
||||
removed.recycle(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h) {
|
||||
if (!isLoaded()) {
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
|
||||
if (w != 0 && h != 0) {
|
||||
findInstance(canvas, w, h).draw(canvas, this, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
GLCanvas canvas = mCanvasRef;
|
||||
if (canvas == null) return;
|
||||
int n = mInstanceCache.size();
|
||||
for (int i = 0; i < n; i++) {
|
||||
NinePatchInstance instance = mInstanceCache.valueAt(i);
|
||||
instance.recycle(canvas);
|
||||
}
|
||||
mInstanceCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// This keeps data for a specialization of NinePatchTexture with the size
|
||||
// (width, height). We pre-compute the coordinates for efficiency.
|
||||
class NinePatchInstance {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "NinePatchInstance";
|
||||
|
||||
// We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
|
||||
private static final int VERTEX_BUFFER_SIZE = 16 * 2;
|
||||
|
||||
// We need 22 indices for a normal nine-patch image, plus 2 for each
|
||||
// transparent region. Current there are at most 1 transparent region.
|
||||
private static final int INDEX_BUFFER_SIZE = 22 + 2;
|
||||
|
||||
private FloatBuffer mXyBuffer;
|
||||
private FloatBuffer mUvBuffer;
|
||||
private ByteBuffer mIndexBuffer;
|
||||
|
||||
// Names for buffer names: xy, uv, index.
|
||||
private int mXyBufferName = -1;
|
||||
private int mUvBufferName;
|
||||
private int mIndexBufferName;
|
||||
|
||||
private int mIdxCount;
|
||||
|
||||
public NinePatchInstance(NinePatchTexture tex, int width, int height) {
|
||||
NinePatchChunk chunk = tex.getNinePatchChunk();
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
throw new RuntimeException("invalid dimension");
|
||||
}
|
||||
|
||||
// The code should be easily extended to handle the general cases by
|
||||
// allocating more space for buffers. But let's just handle the only
|
||||
// use case.
|
||||
if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
|
||||
throw new RuntimeException("unsupported nine patch");
|
||||
}
|
||||
|
||||
float divX[] = new float[4];
|
||||
float divY[] = new float[4];
|
||||
float divU[] = new float[4];
|
||||
float divV[] = new float[4];
|
||||
|
||||
int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
|
||||
int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
|
||||
|
||||
prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stretches the texture according to the nine-patch rules. It will
|
||||
* linearly distribute the strechy parts defined in the nine-patch chunk to
|
||||
* the target area.
|
||||
*
|
||||
* <pre>
|
||||
* source
|
||||
* /--------------^---------------\
|
||||
* u0 u1 u2 u3 u4 u5
|
||||
* div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
|
||||
* | div0 div1 div2 div3 |
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* | | / / / /
|
||||
* |fffff|ssss|fff|sss|ffff| ---> x
|
||||
* x0 x1 x2 x3 x4 x5
|
||||
* \----------v------------/
|
||||
* target
|
||||
*
|
||||
* f: fixed segment
|
||||
* s: stretchy segment
|
||||
* </pre>
|
||||
*
|
||||
* @param div the stretch parts defined in nine-patch chunk
|
||||
* @param source the length of the texture
|
||||
* @param target the length on the drawing plan
|
||||
* @param u output, the positions of these dividers in the texture
|
||||
* coordinate
|
||||
* @param x output, the corresponding position of these dividers on the
|
||||
* drawing plan
|
||||
* @return the number of these dividers.
|
||||
*/
|
||||
private static int stretch(
|
||||
float x[], float u[], int div[], int source, int target) {
|
||||
int textureSize = MathUtils.nextPowerOf2(source);
|
||||
float textureBound = (float) source / textureSize;
|
||||
|
||||
float stretch = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
stretch += div[i + 1] - div[i];
|
||||
}
|
||||
|
||||
float remaining = target - source + stretch;
|
||||
|
||||
float lastX = 0;
|
||||
float lastU = 0;
|
||||
|
||||
x[0] = 0;
|
||||
u[0] = 0;
|
||||
for (int i = 0, n = div.length; i < n; i += 2) {
|
||||
// Make the stretchy segment a little smaller to prevent sampling
|
||||
// on neighboring fixed segments.
|
||||
// fixed segment
|
||||
x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
|
||||
u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
|
||||
|
||||
// stretchy segment
|
||||
float partU = div[i + 1] - div[i];
|
||||
float partX = remaining * partU / stretch;
|
||||
remaining -= partX;
|
||||
stretch -= partU;
|
||||
|
||||
lastX = x[i + 1] + partX;
|
||||
lastU = div[i + 1];
|
||||
x[i + 2] = lastX - 0.5f;
|
||||
u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
|
||||
}
|
||||
// the last fixed segment
|
||||
x[div.length + 1] = target;
|
||||
u[div.length + 1] = textureBound;
|
||||
|
||||
// remove segments with length 0.
|
||||
int last = 0;
|
||||
for (int i = 1, n = div.length + 2; i < n; ++i) {
|
||||
if ((x[i] - x[last]) < 1f) continue;
|
||||
x[++last] = x[i];
|
||||
u[last] = u[i];
|
||||
}
|
||||
return last + 1;
|
||||
}
|
||||
|
||||
private void prepareVertexData(float x[], float y[], float u[], float v[],
|
||||
int nx, int ny, int[] color) {
|
||||
/*
|
||||
* Given a 3x3 nine-patch image, the vertex order is defined as the
|
||||
* following graph:
|
||||
*
|
||||
* (0) (1) (2) (3)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (4) (5) (6) (7)
|
||||
* | \ | \ | \ |
|
||||
* | \| \| \|
|
||||
* (8) (9) (A) (B)
|
||||
* | /| /| /|
|
||||
* | / | / | / |
|
||||
* (C) (D) (E) (F)
|
||||
*
|
||||
* And we draw the triangle strip in the following index order:
|
||||
*
|
||||
* index: 04152637B6A5948C9DAEBF
|
||||
*/
|
||||
int pntCount = 0;
|
||||
float xy[] = new float[VERTEX_BUFFER_SIZE];
|
||||
float uv[] = new float[VERTEX_BUFFER_SIZE];
|
||||
for (int j = 0; j < ny; ++j) {
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
int xIndex = (pntCount++) << 1;
|
||||
int yIndex = xIndex + 1;
|
||||
xy[xIndex] = x[i];
|
||||
xy[yIndex] = y[j];
|
||||
uv[xIndex] = u[i];
|
||||
uv[yIndex] = v[j];
|
||||
}
|
||||
}
|
||||
|
||||
int idxCount = 1;
|
||||
boolean isForward = false;
|
||||
byte index[] = new byte[INDEX_BUFFER_SIZE];
|
||||
for (int row = 0; row < ny - 1; row++) {
|
||||
--idxCount;
|
||||
isForward = !isForward;
|
||||
|
||||
int start, end, inc;
|
||||
if (isForward) {
|
||||
start = 0;
|
||||
end = nx;
|
||||
inc = 1;
|
||||
} else {
|
||||
start = nx - 1;
|
||||
end = -1;
|
||||
inc = -1;
|
||||
}
|
||||
|
||||
for (int col = start; col != end; col += inc) {
|
||||
int k = row * nx + col;
|
||||
if (col != start) {
|
||||
int colorIdx = row * (nx - 1) + col;
|
||||
if (isForward) colorIdx--;
|
||||
if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
|
||||
index[idxCount] = index[idxCount - 1];
|
||||
++idxCount;
|
||||
index[idxCount++] = (byte) k;
|
||||
}
|
||||
}
|
||||
|
||||
index[idxCount++] = (byte) k;
|
||||
index[idxCount++] = (byte) (k + nx);
|
||||
}
|
||||
}
|
||||
|
||||
mIdxCount = idxCount;
|
||||
|
||||
int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
|
||||
mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
|
||||
mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
|
||||
|
||||
mXyBuffer.put(xy, 0, pntCount * 2).position(0);
|
||||
mUvBuffer.put(uv, 0, pntCount * 2).position(0);
|
||||
mIndexBuffer.put(index, 0, idxCount).position(0);
|
||||
}
|
||||
|
||||
private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
|
||||
return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
|
||||
private void prepareBuffers(GLCanvas canvas) {
|
||||
mXyBufferName = canvas.uploadBuffer(mXyBuffer);
|
||||
mUvBufferName = canvas.uploadBuffer(mUvBuffer);
|
||||
mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
|
||||
|
||||
// These buffers are never used again.
|
||||
mXyBuffer = null;
|
||||
mUvBuffer = null;
|
||||
mIndexBuffer = null;
|
||||
}
|
||||
|
||||
public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
|
||||
if (mXyBufferName == -1) {
|
||||
prepareBuffers(canvas);
|
||||
}
|
||||
canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
|
||||
}
|
||||
|
||||
public void recycle(GLCanvas canvas) {
|
||||
if (mXyBuffer == null) {
|
||||
canvas.deleteBuffer(mXyBufferName);
|
||||
canvas.deleteBuffer(mUvBufferName);
|
||||
canvas.deleteBuffer(mIndexBufferName);
|
||||
mXyBufferName = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class RawTexture extends BasicTexture {
|
||||
private static final String TAG = "RawTexture";
|
||||
|
||||
private final boolean mOpaque;
|
||||
private boolean mIsFlipped;
|
||||
|
||||
public RawTexture(int width, int height, boolean opaque) {
|
||||
mOpaque = opaque;
|
||||
setSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlippedVertically() {
|
||||
return mIsFlipped;
|
||||
}
|
||||
|
||||
public void setIsFlippedVertically(boolean isFlipped) {
|
||||
mIsFlipped = isFlipped;
|
||||
}
|
||||
|
||||
protected void prepare(GLCanvas canvas) {
|
||||
GLId glId = canvas.getGLId();
|
||||
mId = glId.generateTexture();
|
||||
canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
|
||||
canvas.setTextureParameters(this);
|
||||
mState = STATE_LOADED;
|
||||
setAssociatedCanvas(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
if (isLoaded()) return true;
|
||||
Log.w(TAG, "lost the content due to context change");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void yield() {
|
||||
// we cannot free the texture because we have no backup.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
|
||||
// ResourceTexture is a texture whose Bitmap is decoded from a resource.
|
||||
// By default ResourceTexture is not opaque.
|
||||
public class ResourceTexture extends UploadedTexture {
|
||||
|
||||
protected final Context mContext;
|
||||
protected final int mResId;
|
||||
|
||||
public ResourceTexture(Context context, int resId) {
|
||||
Assert.assertNotNull(context);
|
||||
mContext = context;
|
||||
mResId = resId;
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
return BitmapFactory.decodeResource(
|
||||
mContext.getResources(), mResId, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
if (!inFinalizer()) {
|
||||
bitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Typeface;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
|
||||
// StringTexture is a texture shows the content of a specified String.
|
||||
//
|
||||
// To create a StringTexture, use the newInstance() method and specify
|
||||
// the String, the font size, and the color.
|
||||
public class StringTexture extends CanvasTexture {
|
||||
private final String mText;
|
||||
private final TextPaint mPaint;
|
||||
private final FontMetricsInt mMetrics;
|
||||
|
||||
private StringTexture(String text, TextPaint paint,
|
||||
FontMetricsInt metrics, int width, int height) {
|
||||
super(width, height);
|
||||
mText = text;
|
||||
mPaint = paint;
|
||||
mMetrics = metrics;
|
||||
}
|
||||
|
||||
public static TextPaint getDefaultPaint(float textSize, int color) {
|
||||
TextPaint paint = new TextPaint();
|
||||
paint.setTextSize(textSize);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(color);
|
||||
paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
|
||||
return paint;
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color) {
|
||||
return newInstance(text, getDefaultPaint(textSize, color));
|
||||
}
|
||||
|
||||
public static StringTexture newInstance(
|
||||
String text, float textSize, int color,
|
||||
float lengthLimit, boolean isBold) {
|
||||
TextPaint paint = getDefaultPaint(textSize, color);
|
||||
if (isBold) {
|
||||
paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
|
||||
}
|
||||
if (lengthLimit > 0) {
|
||||
text = TextUtils.ellipsize(
|
||||
text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
|
||||
}
|
||||
return newInstance(text, paint);
|
||||
}
|
||||
|
||||
private static StringTexture newInstance(String text, TextPaint paint) {
|
||||
FontMetricsInt metrics = paint.getFontMetricsInt();
|
||||
int width = (int)Math.ceil(paint.measureText(text));
|
||||
int height = metrics.bottom - metrics.top;
|
||||
// The texture size needs to be at least 1x1.
|
||||
if (width <= 0) width = 1;
|
||||
if (height <= 0) height = 1;
|
||||
return new StringTexture(text, paint, metrics, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas, Bitmap backing) {
|
||||
canvas.translate(0, -mMetrics.ascent);
|
||||
canvas.drawText(mText, 0, 0, mPaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
|
||||
// Texture is a rectangular image which can be drawn on GLCanvas.
|
||||
// The isOpaque() function gives a hint about whether the texture is opaque,
|
||||
// so the drawing can be done faster.
|
||||
//
|
||||
// This is the current texture hierarchy:
|
||||
//
|
||||
// Texture
|
||||
// -- ColorTexture
|
||||
// -- FadeInTexture
|
||||
// -- BasicTexture
|
||||
// -- UploadedTexture
|
||||
// -- BitmapTexture
|
||||
// -- Tile
|
||||
// -- ResourceTexture
|
||||
// -- NinePatchTexture
|
||||
// -- CanvasTexture
|
||||
// -- StringTexture
|
||||
//
|
||||
public interface Texture {
|
||||
public int getWidth();
|
||||
public int getHeight();
|
||||
public void draw(GLCanvas canvas, int x, int y);
|
||||
public void draw(GLCanvas canvas, int x, int y, int w, int h);
|
||||
public boolean isOpaque();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot;
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
|
||||
|
||||
public class TextureUploader implements OnGLIdleListener {
|
||||
private static final int INIT_CAPACITY = 64;
|
||||
private static final int QUOTA_PER_FRAME = 1;
|
||||
|
||||
private final ArrayDeque<UploadedTexture> mFgTextures =
|
||||
new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
|
||||
private final ArrayDeque<UploadedTexture> mBgTextures =
|
||||
new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
|
||||
private final GLRoot mGLRoot;
|
||||
private volatile boolean mIsQueued = false;
|
||||
|
||||
public TextureUploader(GLRoot root) {
|
||||
mGLRoot = root;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
while (!mFgTextures.isEmpty()) {
|
||||
mFgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
while (!mBgTextures.isEmpty()) {
|
||||
mBgTextures.pop().setIsUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
// caller should hold synchronized on "this"
|
||||
private void queueSelfIfNeed() {
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGLRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
public synchronized void addBgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mBgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
public synchronized void addFgTexture(UploadedTexture t) {
|
||||
if (t.isContentValid()) return;
|
||||
mFgTextures.addLast(t);
|
||||
t.setIsUploading(true);
|
||||
queueSelfIfNeed();
|
||||
}
|
||||
|
||||
private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
|
||||
int uploadQuota, boolean isBackground) {
|
||||
while (uploadQuota > 0) {
|
||||
UploadedTexture t;
|
||||
synchronized (this) {
|
||||
if (deque.isEmpty()) break;
|
||||
t = deque.removeFirst();
|
||||
t.setIsUploading(false);
|
||||
if (t.isContentValid()) continue;
|
||||
|
||||
// this has to be protected by the synchronized block
|
||||
// to prevent the inner bitmap get recycled
|
||||
t.updateContent(canvas);
|
||||
}
|
||||
|
||||
// It will took some more time for a texture to be drawn for
|
||||
// the first time.
|
||||
// Thus, when scrolling, if a new column appears on screen,
|
||||
// it may cause a UI jank even these textures are uploaded.
|
||||
if (isBackground) t.draw(canvas, 0, 0);
|
||||
--uploadQuota;
|
||||
}
|
||||
return uploadQuota;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
int uploadQuota = QUOTA_PER_FRAME;
|
||||
uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
|
||||
if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
|
||||
upload(canvas, mBgTextures, uploadQuota, true);
|
||||
synchronized (this) {
|
||||
mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff.Mode;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot;
|
||||
import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener;
|
||||
|
||||
// This class is similar to BitmapTexture, except the bitmap is
|
||||
// split into tiles. By doing so, we may increase the time required to
|
||||
// upload the whole bitmap but we reduce the time of uploading each tile
|
||||
// so it make the animation more smooth and prevents jank.
|
||||
public class TiledTexture implements Texture {
|
||||
private static final int CONTENT_SIZE = 254;
|
||||
private static final int BORDER_SIZE = 1;
|
||||
private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
|
||||
private static final int INIT_CAPACITY = 8;
|
||||
|
||||
// We are targeting at 60fps, so we have 16ms for each frame.
|
||||
// In this 16ms, we use about 4~8 ms to upload tiles.
|
||||
private static final long UPLOAD_TILE_LIMIT = 4; // ms
|
||||
|
||||
private static Tile sFreeTileHead = null;
|
||||
private static final Object sFreeTileLock = new Object();
|
||||
|
||||
private static Bitmap sUploadBitmap;
|
||||
private static Canvas sCanvas;
|
||||
private static Paint sBitmapPaint;
|
||||
private static Paint sPaint;
|
||||
|
||||
private int mUploadIndex = 0;
|
||||
|
||||
private final Tile[] mTiles; // Can be modified in different threads.
|
||||
// Should be protected by "synchronized."
|
||||
private final int mWidth;
|
||||
private final int mHeight;
|
||||
private final RectF mSrcRect = new RectF();
|
||||
private final RectF mDestRect = new RectF();
|
||||
|
||||
public static class Uploader implements OnGLIdleListener {
|
||||
private final ArrayDeque<TiledTexture> mTextures =
|
||||
new ArrayDeque<TiledTexture>(INIT_CAPACITY);
|
||||
|
||||
private final GLRoot mGlRoot;
|
||||
private boolean mIsQueued = false;
|
||||
|
||||
public Uploader(GLRoot glRoot) {
|
||||
mGlRoot = glRoot;
|
||||
}
|
||||
|
||||
public synchronized void clear() {
|
||||
mTextures.clear();
|
||||
}
|
||||
|
||||
public synchronized void addTexture(TiledTexture t) {
|
||||
if (t.isReady()) return;
|
||||
mTextures.addLast(t);
|
||||
|
||||
if (mIsQueued) return;
|
||||
mIsQueued = true;
|
||||
mGlRoot.addOnGLIdleListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
|
||||
ArrayDeque<TiledTexture> deque = mTextures;
|
||||
synchronized (this) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
long dueTime = now + UPLOAD_TILE_LIMIT;
|
||||
while (now < dueTime && !deque.isEmpty()) {
|
||||
TiledTexture t = deque.peekFirst();
|
||||
if (t.uploadNextTile(canvas)) {
|
||||
deque.removeFirst();
|
||||
mGlRoot.requestRender();
|
||||
}
|
||||
now = SystemClock.uptimeMillis();
|
||||
}
|
||||
mIsQueued = !mTextures.isEmpty();
|
||||
|
||||
// return true to keep this listener in the queue
|
||||
return mIsQueued;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Tile extends UploadedTexture {
|
||||
public int offsetX;
|
||||
public int offsetY;
|
||||
public Bitmap bitmap;
|
||||
public Tile nextFreeTile;
|
||||
public int contentWidth;
|
||||
public int contentHeight;
|
||||
|
||||
@Override
|
||||
public void setSize(int width, int height) {
|
||||
contentWidth = width;
|
||||
contentHeight = height;
|
||||
mWidth = width + 2 * BORDER_SIZE;
|
||||
mHeight = height + 2 * BORDER_SIZE;
|
||||
mTextureWidth = TILE_SIZE;
|
||||
mTextureHeight = TILE_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap onGetBitmap() {
|
||||
// make a local copy of the reference to the bitmap,
|
||||
// since it might be null'd in a different thread. b/8694871
|
||||
Bitmap localBitmapRef = bitmap;
|
||||
bitmap = null;
|
||||
|
||||
if (localBitmapRef != null) {
|
||||
int x = BORDER_SIZE - offsetX;
|
||||
int y = BORDER_SIZE - offsetY;
|
||||
int r = localBitmapRef.getWidth() + x;
|
||||
int b = localBitmapRef.getHeight() + y;
|
||||
sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint);
|
||||
localBitmapRef = null;
|
||||
|
||||
// draw borders if need
|
||||
if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
|
||||
if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
|
||||
if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
|
||||
if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
|
||||
}
|
||||
|
||||
return sUploadBitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFreeBitmap(Bitmap bitmap) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private static void freeTile(Tile tile) {
|
||||
tile.invalidateContent();
|
||||
tile.bitmap = null;
|
||||
synchronized (sFreeTileLock) {
|
||||
tile.nextFreeTile = sFreeTileHead;
|
||||
sFreeTileHead = tile;
|
||||
}
|
||||
}
|
||||
|
||||
private static Tile obtainTile() {
|
||||
synchronized (sFreeTileLock) {
|
||||
Tile result = sFreeTileHead;
|
||||
if (result == null) return new Tile();
|
||||
sFreeTileHead = result.nextFreeTile;
|
||||
result.nextFreeTile = null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean uploadNextTile(GLCanvas canvas) {
|
||||
if (mUploadIndex == mTiles.length) return true;
|
||||
|
||||
synchronized (mTiles) {
|
||||
Tile next = mTiles[mUploadIndex++];
|
||||
|
||||
// Make sure tile has not already been recycled by the time
|
||||
// this is called (race condition in onGLIdle)
|
||||
if (next.bitmap != null) {
|
||||
boolean hasBeenLoad = next.isLoaded();
|
||||
next.updateContent(canvas);
|
||||
|
||||
// It will take some time for a texture to be drawn for the first
|
||||
// time. When scrolling, we need to draw several tiles on the screen
|
||||
// at the same time. It may cause a UI jank even these textures has
|
||||
// been uploaded.
|
||||
if (!hasBeenLoad) next.draw(canvas, 0, 0);
|
||||
}
|
||||
}
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
public TiledTexture(Bitmap bitmap) {
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
ArrayList<Tile> list = new ArrayList<Tile>();
|
||||
|
||||
for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
|
||||
for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
|
||||
Tile tile = obtainTile();
|
||||
tile.offsetX = x;
|
||||
tile.offsetY = y;
|
||||
tile.bitmap = bitmap;
|
||||
tile.setSize(
|
||||
Math.min(CONTENT_SIZE, mWidth - x),
|
||||
Math.min(CONTENT_SIZE, mHeight - y));
|
||||
list.add(tile);
|
||||
}
|
||||
}
|
||||
mTiles = list.toArray(new Tile[list.size()]);
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return mUploadIndex == mTiles.length;
|
||||
}
|
||||
|
||||
// Can be called in UI thread.
|
||||
public void recycle() {
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
freeTile(mTiles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void freeResources() {
|
||||
sUploadBitmap = null;
|
||||
sCanvas = null;
|
||||
sBitmapPaint = null;
|
||||
sPaint = null;
|
||||
}
|
||||
|
||||
public static void prepareResources() {
|
||||
sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
|
||||
sCanvas = new Canvas(sUploadBitmap);
|
||||
sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
|
||||
sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
|
||||
sPaint = new Paint();
|
||||
sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
|
||||
sPaint.setColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
// We want to draw the "source" on the "target".
|
||||
// This method is to find the "output" rectangle which is
|
||||
// the corresponding area of the "src".
|
||||
// (x,y) target
|
||||
// (x0,y0) source +---------------+
|
||||
// +----------+ | |
|
||||
// | src | | output |
|
||||
// | +--+ | linear map | +----+ |
|
||||
// | +--+ | ----------> | | | |
|
||||
// | | by (scaleX, scaleY) | +----+ |
|
||||
// +----------+ | |
|
||||
// Texture +---------------+
|
||||
// Canvas
|
||||
private static void mapRect(RectF output,
|
||||
RectF src, float x0, float y0, float x, float y, float scaleX,
|
||||
float scaleY) {
|
||||
output.set(x + (src.left - x0) * scaleX,
|
||||
y + (src.top - y0) * scaleY,
|
||||
x + (src.right - x0) * scaleX,
|
||||
y + (src.bottom - y0) * scaleY);
|
||||
}
|
||||
|
||||
// Draws a mixed color of this texture and a specified color onto the
|
||||
// a rectangle. The used color is: from * (1 - ratio) + to * ratio.
|
||||
public void drawMixed(GLCanvas canvas, int color, float ratio,
|
||||
int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws the texture on to the specified rectangle.
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y, int width, int height) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float scaleX = (float) width / mWidth;
|
||||
float scaleY = (float) height / mHeight;
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, mSrcRect, mDestRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a sub region of this texture on to the specified rectangle.
|
||||
public void draw(GLCanvas canvas, RectF source, RectF target) {
|
||||
RectF src = mSrcRect;
|
||||
RectF dest = mDestRect;
|
||||
float x0 = source.left;
|
||||
float y0 = source.top;
|
||||
float x = target.left;
|
||||
float y = target.top;
|
||||
float scaleX = target.width() / source.width();
|
||||
float scaleY = target.height() / source.height();
|
||||
|
||||
synchronized (mTiles) {
|
||||
for (int i = 0, n = mTiles.length; i < n; ++i) {
|
||||
Tile t = mTiles[i];
|
||||
src.set(0, 0, t.contentWidth, t.contentHeight);
|
||||
src.offset(t.offsetX, t.offsetY);
|
||||
if (!src.intersect(source)) continue;
|
||||
mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
|
||||
src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
|
||||
canvas.drawTexture(t, src, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GLCanvas canvas, int x, int y) {
|
||||
draw(canvas, x, y, mWidth, mHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.glrenderer;
|
||||
|
||||
public interface Uploaded {
|
||||
|
||||
public boolean isContentValid();
|
||||
|
||||
public void updateContent(GLCanvas canvas);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.glrenderer;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.opengl.GLUtils;
|
||||
|
||||
// UploadedTextures use a Bitmap for the content of the texture.
|
||||
//
|
||||
// Subclasses should implement onGetBitmap() to provide the Bitmap and
|
||||
// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
|
||||
// is not needed anymore.
|
||||
//
|
||||
// isContentValid() is meaningful only when the isLoaded() returns true.
|
||||
// It means whether the content needs to be updated.
|
||||
//
|
||||
// The user of this class should call recycle() when the texture is not
|
||||
// needed anymore.
|
||||
//
|
||||
// By default an UploadedTexture is opaque (so it can be drawn faster without
|
||||
// blending). The user or subclass can override it using setOpaque().
|
||||
public abstract class UploadedTexture extends BasicTexture implements Uploaded {
|
||||
|
||||
// To prevent keeping allocation the borders, we store those used borders here.
|
||||
// Since the length will be power of two, it won't use too much memory.
|
||||
private static HashMap<BorderKey, Bitmap> sBorderLines =
|
||||
new HashMap<BorderKey, Bitmap>();
|
||||
private static BorderKey sBorderKey = new BorderKey();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "Texture";
|
||||
private boolean mContentValid = true;
|
||||
|
||||
// indicate this textures is being uploaded in background
|
||||
private boolean mIsUploading = false;
|
||||
private boolean mOpaque = true;
|
||||
private boolean mThrottled = false;
|
||||
private static int sUploadedCount;
|
||||
private static final int UPLOAD_LIMIT = 100;
|
||||
|
||||
protected Bitmap mBitmap;
|
||||
private int mBorder;
|
||||
|
||||
protected UploadedTexture() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
protected UploadedTexture(boolean hasBorder) {
|
||||
super(null, 0, STATE_UNLOADED);
|
||||
if (hasBorder) {
|
||||
setBorder(true);
|
||||
mBorder = 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setIsUploading(boolean uploading) {
|
||||
mIsUploading = uploading;
|
||||
}
|
||||
|
||||
public boolean isUploading() {
|
||||
return mIsUploading;
|
||||
}
|
||||
|
||||
private static class BorderKey implements Cloneable {
|
||||
public boolean vertical;
|
||||
public Config config;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int x = config.hashCode() ^ length;
|
||||
return vertical ? x : -x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (!(object instanceof BorderKey)) return false;
|
||||
BorderKey o = (BorderKey) object;
|
||||
return vertical == o.vertical
|
||||
&& config == o.config && length == o.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BorderKey clone() {
|
||||
try {
|
||||
return (BorderKey) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setThrottled(boolean throttled) {
|
||||
mThrottled = throttled;
|
||||
}
|
||||
|
||||
private static Bitmap getBorderLine(
|
||||
boolean vertical, Config config, int length) {
|
||||
BorderKey key = sBorderKey;
|
||||
key.vertical = vertical;
|
||||
key.config = config;
|
||||
key.length = length;
|
||||
Bitmap bitmap = sBorderLines.get(key);
|
||||
if (bitmap == null) {
|
||||
bitmap = vertical
|
||||
? Bitmap.createBitmap(1, length, config)
|
||||
: Bitmap.createBitmap(length, 1, config);
|
||||
sBorderLines.put(key.clone(), bitmap);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private Bitmap getBitmap() {
|
||||
if (mBitmap == null) {
|
||||
mBitmap = onGetBitmap();
|
||||
int w = mBitmap.getWidth() + mBorder * 2;
|
||||
int h = mBitmap.getHeight() + mBorder * 2;
|
||||
if (mWidth == UNSPECIFIED) {
|
||||
setSize(w, h);
|
||||
}
|
||||
}
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
private void freeBitmap() {
|
||||
Assert.assertTrue(mBitmap != null);
|
||||
onFreeBitmap(mBitmap);
|
||||
mBitmap = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
if (mWidth == UNSPECIFIED) getBitmap();
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
protected abstract Bitmap onGetBitmap();
|
||||
|
||||
protected abstract void onFreeBitmap(Bitmap bitmap);
|
||||
|
||||
protected void invalidateContent() {
|
||||
if (mBitmap != null) freeBitmap();
|
||||
mContentValid = false;
|
||||
mWidth = UNSPECIFIED;
|
||||
mHeight = UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the content on GPU is valid.
|
||||
*/
|
||||
@Override
|
||||
public boolean isContentValid() {
|
||||
return isLoaded() && mContentValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the content on GPU's memory.
|
||||
* @param canvas
|
||||
*/
|
||||
@Override
|
||||
public void updateContent(GLCanvas canvas) {
|
||||
if (!isLoaded()) {
|
||||
if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
|
||||
return;
|
||||
}
|
||||
uploadToCanvas(canvas);
|
||||
} else if (!mContentValid) {
|
||||
Bitmap bitmap = getBitmap();
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
freeBitmap();
|
||||
mContentValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetUploadLimit() {
|
||||
sUploadedCount = 0;
|
||||
}
|
||||
|
||||
public static boolean uploadLimitReached() {
|
||||
return sUploadedCount > UPLOAD_LIMIT;
|
||||
}
|
||||
|
||||
private void uploadToCanvas(GLCanvas canvas) {
|
||||
|
||||
Bitmap bitmap = getBitmap();
|
||||
if (bitmap != null) {
|
||||
try {
|
||||
int bWidth = bitmap.getWidth();
|
||||
int bHeight = bitmap.getHeight();
|
||||
int texWidth = getTextureWidth();
|
||||
int texHeight = getTextureHeight();
|
||||
|
||||
Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
|
||||
|
||||
// Upload the bitmap to a new texture.
|
||||
mId = canvas.getGLId().generateTexture();
|
||||
canvas.setTextureParameters(this);
|
||||
|
||||
if (bWidth == texWidth && bHeight == texHeight) {
|
||||
canvas.initializeTexture(this, bitmap);
|
||||
} else {
|
||||
int format = GLUtils.getInternalFormat(bitmap);
|
||||
int type = GLUtils.getType(bitmap);
|
||||
Config config = bitmap.getConfig();
|
||||
|
||||
canvas.initializeTextureSize(this, format, type);
|
||||
canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
|
||||
|
||||
if (mBorder > 0) {
|
||||
// Left border
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
|
||||
// Top border
|
||||
line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Right border
|
||||
if (mBorder + bWidth < texWidth) {
|
||||
Bitmap line = getBorderLine(true, config, texHeight);
|
||||
canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
if (mBorder + bHeight < texHeight) {
|
||||
Bitmap line = getBorderLine(false, config, texWidth);
|
||||
canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
freeBitmap();
|
||||
}
|
||||
// Update texture state.
|
||||
setAssociatedCanvas(canvas);
|
||||
mState = STATE_LOADED;
|
||||
mContentValid = true;
|
||||
} else {
|
||||
mState = STATE_ERROR;
|
||||
throw new RuntimeException("Texture load fail, no bitmap");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onBind(GLCanvas canvas) {
|
||||
updateContent(canvas);
|
||||
return isContentValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTarget() {
|
||||
return GL11.GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
public void setOpaque(boolean isOpaque) {
|
||||
mOpaque = isOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque() {
|
||||
return mOpaque;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
super.recycle();
|
||||
if (mBitmap != null) freeBitmap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.image;
|
||||
|
||||
import com.hippo.ehviewer.util.Log;
|
||||
|
||||
public class GifImage extends Image {
|
||||
|
||||
private static final String TAG = Image.class.getSimpleName();
|
||||
|
||||
private final int mImageCount;
|
||||
private final int[] mDelayArray;
|
||||
private long mDelaySum;
|
||||
private volatile long mStartTime;
|
||||
private volatile long mStopTime = -1;
|
||||
private volatile boolean mRunning = true;
|
||||
|
||||
private int mLastIndex = -1;
|
||||
|
||||
protected GifImage(long nativeImage, int fileFormat, int width, int height,
|
||||
int format, int type, int[] delayArray) {
|
||||
super(nativeImage, fileFormat, width, height, format, type);
|
||||
mImageCount = delayArray.length;
|
||||
mDelayArray = delayArray;
|
||||
|
||||
mDelaySum = 0;
|
||||
for (int delay : delayArray)
|
||||
mDelaySum += delay;
|
||||
}
|
||||
|
||||
private int getCurIndex() {
|
||||
int index = 0;
|
||||
if (mRunning) {
|
||||
if (mLastIndex == -1) {
|
||||
index = 0;
|
||||
mStartTime = System.currentTimeMillis();
|
||||
} else {
|
||||
long curTime = System.currentTimeMillis();
|
||||
long time = (curTime - mStartTime) % mDelaySum;
|
||||
int end = mImageCount - 1;
|
||||
for (; index < end && (time -= mDelayArray[index]) > 0; index++)
|
||||
;
|
||||
}
|
||||
return index;
|
||||
} else {
|
||||
return mLastIndex == -1 ? index : mLastIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (!mRunning) {
|
||||
mRunning = true;
|
||||
long curTime = System.currentTimeMillis();
|
||||
mStartTime = curTime - (mStopTime == -1 ? curTime : mStopTime)
|
||||
+ mStartTime;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (mRunning) {
|
||||
mRunning = false;
|
||||
mStopTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnimated() {
|
||||
return mImageCount > 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
render(getCurIndex());
|
||||
}
|
||||
|
||||
public void render(int index) {
|
||||
if (mNativeImage != 0) {
|
||||
if (index >= mImageCount || index < 0) {
|
||||
Log.d(TAG, "GifImage try render index " + index + ", but image count is " + mImageCount);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLastIndex != index) {
|
||||
nativeRender(mFormat, mType, mNativeImage, FILE_FORMAT_GIF,
|
||||
index);
|
||||
mLastIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
System.loadLibrary("image");
|
||||
}
|
||||
|
||||
private static native void nativeRender(int format, int type,
|
||||
long nativeImage, int fileFormat, int index);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.gallery.image;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL11;
|
||||
|
||||
public class Image {
|
||||
|
||||
private static final String TAG = Image.class.getSimpleName();
|
||||
|
||||
public static final int FILE_FORMAT_JPEG = 0x0;
|
||||
public static final int FILE_FORMAT_PNG = 0x1;
|
||||
public static final int FILE_FORMAT_BMP = 0x2;
|
||||
public static final int FILE_FORMAT_GIF = 0x3;
|
||||
|
||||
public static final int FORMAT_AUTO = 0x0;
|
||||
public static final int FORMAT_GRAY = GL11.GL_LUMINANCE;
|
||||
public static final int FORMAT_GRAY_ALPHA = GL11.GL_LUMINANCE_ALPHA;
|
||||
public static final int FORMAT_RGB = GL11.GL_RGB;
|
||||
public static final int FORMAT_RGBA = GL11.GL_RGBA;
|
||||
|
||||
protected long mNativeImage;
|
||||
protected final int mFileFormat;
|
||||
protected final int mWidth;
|
||||
protected final int mHeight;
|
||||
|
||||
protected final int mFormat;
|
||||
protected final int mType;
|
||||
|
||||
protected Image(long nativeImage, int fileFormat, int width, int height,
|
||||
int format, int type) {
|
||||
mNativeImage = nativeImage;
|
||||
mFileFormat = fileFormat;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mFormat = format;
|
||||
mType = type;
|
||||
}
|
||||
|
||||
public int getFileFormat() {
|
||||
return mFileFormat;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public int getFormat() {
|
||||
return mFormat;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public boolean isAnimated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (mNativeImage != 0) {
|
||||
nativeRender(mFormat, mType, mNativeImage, mFileFormat);
|
||||
}
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
if (mNativeImage != 0) {
|
||||
nativeFree(mNativeImage, mFileFormat);
|
||||
mNativeImage = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRecycled() {
|
||||
return mNativeImage == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* format can be GL11.GL_RGB, GL11.GL_RGBA, GL11.GL_LUMINANCE,
|
||||
* GL11.GL_LUMINANCE_ALPHA
|
||||
*
|
||||
* @param is
|
||||
* @param format
|
||||
* @return
|
||||
*/
|
||||
public static final Image decodeStream(InputStream is, int format) {
|
||||
return nativeDecodeStream(is, format);
|
||||
}
|
||||
|
||||
public static final Image decodeFile(String pathName, int format) {
|
||||
return nativeDecodeFile(pathName, format);
|
||||
}
|
||||
|
||||
static {
|
||||
System.loadLibrary("image");
|
||||
}
|
||||
|
||||
private static native Image nativeDecodeStream(InputStream is, int format);
|
||||
|
||||
private static native Image nativeDecodeFile(String pathName, int format);
|
||||
|
||||
private static native void nativeFree(long nativeImage, int format);
|
||||
|
||||
private static native void nativeRender(int format, int type,
|
||||
long nativeImage, int fileFormat);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2012 The Android Open Source Project
|
||||
*
|
||||
* 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.gallery.ui;
|
||||
|
||||
import android.os.SystemClock;
|
||||
|
||||
//
|
||||
// The animation time should ideally be the vsync time the frame will be
|
||||
// displayed, but that is an unknown time in the future. So we use the system
|
||||
// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called)
|
||||
// as a approximation.
|
||||
//
|
||||
public class AnimationTime {
|
||||
private static volatile long sTime;
|
||||
|
||||
// Sets current time as the animation time.
|
||||
public static void update() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
// Returns the animation time.
|
||||
public static long get() {
|
||||
return sTime;
|
||||
}
|
||||
|
||||
public static long startTime() {
|
||||
sTime = SystemClock.uptimeMillis();
|
||||
return sTime;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 658 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user