Update dependencies

Settings activity refactor
This commit is contained in:
NekoInverter committed 2020-03-23 14:26:54 +08:00
1 parent ba6d41b91f
commit a9b7df3284
248 files changed
+8700 -8980

No files matched your search

@@ -30,6 +30,7 @@ import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import androidx.annotation.CallSuper;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
@@ -48,7 +49,7 @@ import androidx.core.app.NavUtils;
import androidx.core.app.TaskStackBuilder;
public abstract class AppCompatPreferenceActivity extends PreferenceActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
private AppCompatDelegate mDelegate;
private int mThemeId = 0;
@@ -187,7 +188,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
final ActionBar ab = getSupportActionBar();
if (item.getItemId() == android.R.id.home && ab != null &&
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
(ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
return onSupportNavigateUp();
}
return false;
@@ -213,9 +214,8 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
* </p>
*
* @param featureId The desired feature as defined in
* {@link android.view.Window} or {@link androidx.core.view.WindowCompat}.
* {@link android.view.Window} or {@link androidx.core.view.WindowCompat}.
* @return Returns true if the requested feature is supported and now enabled.
*
* @see android.app.Activity#requestWindowFeature
* @see android.view.Window#requestFeature
*/
@@ -311,7 +311,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
/**
* Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}.
* This method will be called on all platform versions.
*
* <p>
* Define the synthetic task stack that will be generated during Up navigation from
* a different task.
*
@@ -338,7 +338,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
/**
* Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}.
* This method will be called on all platform versions.
*
* <p>
* Prepare the synthetic task stack that will be generated during Up navigation
* from a different task.
*
@@ -374,7 +374,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
* {@link #supportNavigateUpTo(android.content.Intent)} for help implementing custom Up navigation.</p>
*
* @return true if Up navigation completed successfully and this Activity was finished,
* false otherwise.
* false otherwise.
*/
public boolean onSupportNavigateUp() {
Intent upIntent = getSupportParentActivityIntent();
@@ -429,7 +429,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
*
* @param targetIntent An intent representing the target destination for up navigation
* @return true if navigating up should recreate a new task stack, false if the same task
* should be used for the destination
* should be used for the destination
*/
public boolean supportShouldUpRecreateTask(@NonNull Intent targetIntent) {
return NavUtils.shouldUpRecreateTask(this, targetIntent);
@@ -504,7 +504,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
final int keyCode = event.getKeyCode();
final ActionBar actionBar = getSupportActionBar();
if (keyCode == KeyEvent.KEYCODE_MENU
&& actionBar != null && actionBar.onMenuKeyEvent(event)) {
&& actionBar != null && actionBar.onMenuKeyEvent(event)) {
return true;
}
return super.dispatchKeyEvent(event);
@@ -526,15 +526,13 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
*/
private boolean performMenuItemShortcut(int keycode, KeyEvent event) {
if (!(Build.VERSION.SDK_INT >= 26) && !event.isCtrlPressed()
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
final Window currentWindow = getWindow();
if (currentWindow != null && currentWindow.getDecorView() != null) {
final View decorView = currentWindow.getDecorView();
if (decorView.dispatchKeyShortcutEvent(event)) {
return true;
}
return decorView.dispatchKeyShortcutEvent(event);
}
}
return false;
@@ -553,7 +551,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
public void openOptionsMenu() {
ActionBar actionBar = getSupportActionBar();
if (getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)
&& (actionBar == null || !actionBar.openOptionsMenu())) {
&& (actionBar == null || !actionBar.openOptionsMenu())) {
super.openOptionsMenu();
}
}
@@ -563,7 +561,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity imp
public void closeOptionsMenu() {
ActionBar actionBar = getSupportActionBar();
if (getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)
&& (actionBar == null || !actionBar.closeOptionsMenu())) {
&& (actionBar == null || !actionBar.closeOptionsMenu())) {
super.closeOptionsMenu();
}
}
@@ -22,7 +22,9 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
public class CheckBoxDialogBuilder extends AlertDialog.Builder {
@@ -25,7 +25,9 @@ import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.R;
@@ -24,7 +24,9 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
@@ -36,7 +38,7 @@ public class ListCheckBoxDialogBuilder extends AlertDialog.Builder {
@SuppressLint("InflateParams")
public ListCheckBoxDialogBuilder(Context context, CharSequence[] items,
final OnItemClickListener listener, String checkText, boolean checked) {
final OnItemClickListener listener, String checkText, boolean checked) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null);
setView(view);
@@ -1,113 +0,0 @@
/*
* Copyright 2018 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.app;
import android.content.Context;
import android.preference.PreferenceActivity;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.R;
import java.util.ArrayList;
import java.util.List;
public abstract class PrettyPreferenceActivity extends AppCompatPreferenceActivity {
@Override
public void setListAdapter(ListAdapter adapter) {
if (adapter == null) {
super.setListAdapter(null);
return;
}
int count = adapter.getCount();
List<PreferenceActivity.Header> headers = new ArrayList<>(count);
for (int i = 0; i < count; ++i) {
headers.add((PreferenceActivity.Header) adapter.getItem(i));
}
super.setListAdapter(new HeaderAdapter(this, headers, R.layout.item_preference_header, true));
}
private static class HeaderAdapter extends ArrayAdapter<PreferenceActivity.Header> {
private static class HeaderViewHolder {
ImageView icon;
TextView title;
TextView summary;
}
private LayoutInflater mInflater;
private int mLayoutResId;
private boolean mRemoveIconIfEmpty;
private HeaderAdapter(Context context, List<PreferenceActivity.Header> objects, int layoutResId,
boolean removeIconBehavior) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutResId = layoutResId;
mRemoveIconIfEmpty = removeIconBehavior;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
HeaderViewHolder holder;
View view;
if (convertView == null) {
view = mInflater.inflate(mLayoutResId, parent, false);
holder = new HeaderViewHolder();
holder.icon = view.findViewById(android.R.id.icon);
holder.title = view.findViewById(android.R.id.title);
holder.summary = view.findViewById(android.R.id.summary);
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
// All view fields must be updated every time, because the view may be recycled
PreferenceActivity.Header header = getItem(position);
if (mRemoveIconIfEmpty) {
if (header.iconRes == 0) {
holder.icon.setVisibility(View.GONE);
} else {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(header.iconRes);
}
} else {
holder.icon.setImageResource(header.iconRes);
}
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
return view;
}
}
}
@@ -28,8 +28,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import java.text.NumberFormat;
/**
@@ -40,12 +43,14 @@ import java.text.NumberFormat;
*/
public class ProgressDialog extends AlertDialog {
/** Creates a ProgressDialog with a circular, spinning progress
/**
* 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.
/**
* Creates a ProgressDialog with a horizontal progress bar.
*/
public static final int STYLE_HORIZONTAL = 1;
@@ -81,30 +86,24 @@ public class ProgressDialog extends AlertDialog {
initFormats();
}
private void initFormats() {
mProgressNumberFormat = "%1d/%2d";
mProgressPercentFormat = NumberFormat.getPercentInstance();
mProgressPercentFormat.setMaximumFractionDigits(0);
}
public static ProgressDialog show(Context context, CharSequence title,
CharSequence message) {
CharSequence message) {
return show(context, title, message, false);
}
public static ProgressDialog show(Context context, CharSequence title,
CharSequence message, boolean indeterminate) {
CharSequence message, boolean indeterminate) {
return show(context, title, message, indeterminate, false, null);
}
public static ProgressDialog show(Context context, CharSequence title,
CharSequence message, boolean indeterminate, boolean cancelable) {
CharSequence message, boolean indeterminate, boolean cancelable) {
return show(context, title, message, indeterminate, cancelable, null);
}
public static ProgressDialog show(Context context, CharSequence title,
CharSequence message, boolean indeterminate,
boolean cancelable, OnCancelListener cancelListener) {
CharSequence message, boolean indeterminate,
boolean cancelable, OnCancelListener cancelListener) {
ProgressDialog dialog = new ProgressDialog(context);
dialog.setTitle(title);
dialog.setMessage(message);
@@ -115,6 +114,12 @@ public class ProgressDialog extends AlertDialog {
return dialog;
}
private void initFormats() {
mProgressNumberFormat = "%1d/%2d";
mProgressPercentFormat = NumberFormat.getPercentInstance();
mProgressPercentFormat.setMaximumFractionDigits(0);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -200,6 +205,13 @@ public class ProgressDialog extends AlertDialog {
mHasStarted = false;
}
public int getProgress() {
if (mProgress != null) {
return mProgress.getProgress();
}
return mProgressVal;
}
public void setProgress(int value) {
if (mHasStarted) {
mProgress.setProgress(value);
@@ -209,6 +221,13 @@ public class ProgressDialog extends AlertDialog {
}
}
public int getSecondaryProgress() {
if (mProgress != null) {
return mProgress.getSecondaryProgress();
}
return mSecondaryProgressVal;
}
public void setSecondaryProgress(int secondaryProgress) {
if (mProgress != null) {
mProgress.setSecondaryProgress(secondaryProgress);
@@ -218,20 +237,6 @@ public class ProgressDialog extends AlertDialog {
}
}
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();
@@ -282,6 +287,13 @@ public class ProgressDialog extends AlertDialog {
}
}
public boolean isIndeterminate() {
if (mProgress != null) {
return mProgress.isIndeterminate();
}
return mIndeterminate;
}
public void setIndeterminate(boolean indeterminate) {
if (mProgress != null) {
mProgress.setIndeterminate(indeterminate);
@@ -290,13 +302,6 @@ public class ProgressDialog extends AlertDialog {
}
}
public boolean isIndeterminate() {
if (mProgress != null) {
return mProgress.isIndeterminate();
}
return mIndeterminate;
}
@Override
public void setMessage(CharSequence message) {
if (mProgress != null) {
@@ -318,9 +323,10 @@ public class ProgressDialog extends AlertDialog {
* 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.
* use "%1d" for the current number and "%2d" for the maximum. If null,
* nothing will be shown.
*/
public void setProgressNumberFormat(String format) {
mProgressNumberFormat = format;
@@ -332,8 +338,9 @@ public class ProgressDialog extends AlertDialog {
* 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.
* percentage text. If null, nothing will be shown.
*/
public void setProgressPercentFormat(NumberFormat format) {
mProgressPercentFormat = format;
@@ -26,36 +26,37 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;
public class ContextLocalWrapper extends ContextWrapper {
public ContextLocalWrapper(Context base) {
super(base);
}
public static ContextLocalWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
public ContextLocalWrapper(Context base) {
super(base);
}
return new ContextLocalWrapper(context);
}
public static ContextLocalWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextLocalWrapper(context);
}
}
@@ -16,9 +16,6 @@
package com.hippo.content;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.content.ClipData;
import android.content.ContentProvider;
import android.content.ContentValues;
@@ -36,13 +33,19 @@ import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import androidx.core.content.ContextCompat;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.xmlpull.v1.XmlPullParserException;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
/**
* FileProvider is a special subclass of {@link ContentProvider} that facilitates secure sharing
@@ -91,7 +94,7 @@ import org.xmlpull.v1.XmlPullParserException;
* >android:grantUriPermissions</a> attribute to <code>true</code>, to allow you
* to grant temporary access to files. For example:
* <pre class="prettyprint">
*&lt;manifest&gt;
* &lt;manifest&gt;
* ...
* &lt;application&gt;
* ...
@@ -104,7 +107,7 @@ import org.xmlpull.v1.XmlPullParserException;
* &lt;/provider&gt;
* ...
* &lt;/application&gt;
*&lt;/manifest&gt;</pre>
* &lt;/manifest&gt;</pre>
* <p>
* If you want to override any of the default behavior of FileProvider methods, extend
* the FileProvider class and use the fully-qualified class name in the <code>android:name</code>
@@ -116,19 +119,19 @@ import org.xmlpull.v1.XmlPullParserException;
* For example, the following <code>paths</code> element tells FileProvider that you intend to
* request content URIs for the <code>images/</code> subdirectory of your private file area.
* <pre class="prettyprint">
*&lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;files-path name="my_images" path="images/"/&gt;
* ...
*&lt;/paths&gt;
*</pre>
* &lt;/paths&gt;
* </pre>
* <p>
* The <code>&lt;paths&gt;</code> element must contain one or more of the following child elements:
* </p>
* <dl>
* <dt>
* <pre class="prettyprint">
*&lt;files-path name="<i>name</i>" path="<i>path</i>" /&gt;
*</pre>
* &lt;files-path name="<i>name</i>" path="<i>path</i>" /&gt;
* </pre>
* </dt>
* <dd>
* Represents files in the <code>files/</code> subdirectory of your app's internal storage
@@ -137,8 +140,8 @@ import org.xmlpull.v1.XmlPullParserException;
* </dd>
* <dt>
* <pre>
*&lt;cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
*</pre>
* &lt;cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
* </pre>
* <dt>
* <dd>
* Represents files in the cache subdirectory of your app's internal storage area. The root path
@@ -147,8 +150,8 @@ import org.xmlpull.v1.XmlPullParserException;
* </dd>
* <dt>
* <pre class="prettyprint">
*&lt;external-path name="<i>name</i>" path="<i>path</i>" /&gt;
*</pre>
* &lt;external-path name="<i>name</i>" path="<i>path</i>" /&gt;
* </pre>
* </dt>
* <dd>
* Represents files in the root of the external storage area. The root path of this subdirectory
@@ -157,8 +160,8 @@ import org.xmlpull.v1.XmlPullParserException;
* </dd>
* <dt>
* <pre class="prettyprint">
*&lt;external-files-path name="<i>name</i>" path="<i>path</i>" /&gt;
*</pre>
* &lt;external-files-path name="<i>name</i>" path="<i>path</i>" /&gt;
* </pre>
* </dt>
* <dd>
* Represents files in the root of your app's external storage area. The root path of this
@@ -167,8 +170,8 @@ import org.xmlpull.v1.XmlPullParserException;
* </dd>
* <dt>
* <pre class="prettyprint">
*&lt;external-cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
*</pre>
* &lt;external-cache-path name="<i>name</i>" path="<i>path</i>" /&gt;
* </pre>
* </dt>
* <dd>
* Represents files in the root of your app's external cache area. The root path of this
@@ -203,11 +206,11 @@ import org.xmlpull.v1.XmlPullParserException;
* You must specify a child element of <code>&lt;paths&gt;</code> for each directory that contains
* files for which you want content URIs. For example, these XML elements specify two directories:
* <pre class="prettyprint">
*&lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;paths xmlns:android="http://schemas.android.com/apk/res/android"&gt;
* &lt;files-path name="my_images" path="images/"/&gt;
* &lt;files-path name="my_docs" path="docs/"/&gt;
*&lt;/paths&gt;
*</pre>
* &lt;/paths&gt;
* </pre>
* <p>
* Put the <code>&lt;paths&gt;</code> element and its children in an XML file in your project.
* For example, you can add them to a new file called <code>res/xml/file_paths.xml</code>.
@@ -219,7 +222,7 @@ import org.xmlpull.v1.XmlPullParserException;
* to <code>&#64;xml/file_paths</code> (notice that you don't specify the <code>.xml</code>
* extension). For example:
* <pre class="prettyprint">
*&lt;provider
* &lt;provider
* android:name="android.support.v4.content.FileProvider"
* android:authorities="com.mydomain.fileprovider"
* android:exported="false"
@@ -227,8 +230,8 @@ import org.xmlpull.v1.XmlPullParserException;
* &lt;meta-data
* android:name="android.support.FILE_PROVIDER_PATHS"
* android:resource="&#64;xml/file_paths" /&gt;
*&lt;/provider&gt;
*</pre>
* &lt;/provider&gt;
* </pre>
* <h3 id="GetUri">Generating the Content URI for a File</h3>
* <p>
* To share a file with another app using a content URI, your app has to generate the content URI.
@@ -245,10 +248,10 @@ import org.xmlpull.v1.XmlPullParserException;
* <code>default_image.jpg</code> in the <code>images/</code> subdirectory of your internal storage
* add the following code:
* <pre class="prettyprint">
*File imagePath = new File(Context.getFilesDir(), "images");
*File newFile = new File(imagePath, "default_image.jpg");
*Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
*</pre>
* File imagePath = new File(Context.getFilesDir(), "images");
* File newFile = new File(imagePath, "default_image.jpg");
* Uri contentUri = getUriForFile(getContext(), "com.mydomain.fileprovider", newFile);
* </pre>
* As a result of the previous snippet,
* {@link #getUriForFile(Context, String, File) getUriForFile()} returns the content URI
* <code>content://com.mydomain.fileprovider/my_images/default_image.jpg</code>.
@@ -348,37 +351,6 @@ public class FileProvider extends ContentProvider {
private PathStrategy mStrategy;
/**
* The default FileProvider implementation does not need to be initialized. If you want to
* override this method, you must provide your own subclass of FileProvider.
*/
@Override
public boolean onCreate() {
return true;
}
/**
* After the FileProvider is instantiated, this method is called to provide the system with
* information about the provider.
*
* @param context A {@link Context} for the current component.
* @param info A {@link ProviderInfo} for the new provider.
*/
@Override
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
// Sanity check our security
if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
mStrategy = getPathStrategy(context, info.authority);
}
/**
* Return a content URI for a given {@link File}. Specific temporary
* permissions for the content URI can be set with
@@ -390,168 +362,20 @@ public class FileProvider extends ContentProvider {
* <code>content</code> {@link Uri} for file paths defined in their <code>&lt;paths&gt;</code>
* meta-data element. See the Class Overview for more information.
*
* @param context A {@link Context} for the current component.
* @param context A {@link Context} for the current component.
* @param authority The authority of a {@link FileProvider} defined in a
* {@code <provider>} element in your app's manifest.
* @param file A {@link File} pointing to the filename for which you want a
* <code>content</code> {@link Uri}.
* {@code <provider>} element in your app's manifest.
* @param file A {@link File} pointing to the filename for which you want a
* <code>content</code> {@link Uri}.
* @return A content URI for the file.
* @throws IllegalArgumentException When the given {@link File} is outside
* the paths supported by the provider.
* the paths supported by the provider.
*/
public static Uri getUriForFile(Context context, String authority, File file) {
final PathStrategy strategy = getPathStrategy(context, authority);
return strategy.getUriForFile(file);
}
/**
* Use a content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
* managed by the FileProvider.
* FileProvider reports the column names defined in {@link android.provider.MediaStore.MediaColumns}:
* <ul>
* <li>{@link android.provider.MediaStore.MediaColumns#DISPLAY_NAME}</li>
* <li>{@link android.provider.MediaStore.MediaColumns#SIZE}</li>
* <li>{@link android.provider.MediaStore.MediaColumns#DATA}</li>
* </ul>
* For more information, see
* {@link ContentProvider#query(Uri, String[], String, String[], String)
* ContentProvider.query()}.
*
* @param uri A content URI returned by {@link #getUriForFile}.
* @param projection The list of columns to put into the {@link Cursor}. If null all columns are
* included.
* @param selection Selection criteria to apply. If null then all data that matches the content
* URI is returned.
* @param selectionArgs An array of {@link java.lang.String}, containing arguments to bind to
* the <i>selection</i> parameter. The <i>query</i> method scans <i>selection</i> from left to
* right and iterates through <i>selectionArgs</i>, replacing the current "?" character in
* <i>selection</i> with the value at the current position in <i>selectionArgs</i>. The
* values are bound to <i>selection</i> as {@link java.lang.String} values.
* @param sortOrder A {@link java.lang.String} containing the column name(s) on which to sort
* the resulting {@link Cursor}.
* @return A {@link Cursor} containing the results of the query.
*
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (MediaStore.MediaColumns.DISPLAY_NAME.equals(col)) {
cols[i] = MediaStore.MediaColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (MediaStore.MediaColumns.SIZE.equals(col)) {
cols[i] = MediaStore.MediaColumns.SIZE;
values[i++] = file.length();
} else if (MediaStore.MediaColumns.DATA.equals(col)) {
cols[i] = MediaStore.MediaColumns.DATA;
values[i++] = file.getPath();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
/**
* Returns the MIME type of a content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
*
* @param uri A content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @return If the associated file has an extension, the MIME type associated with that
* extension; otherwise <code>application/octet-stream</code>.
*/
@Override
public String getType(Uri uri) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int lastDot = file.getName().lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file.getName().substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
/**
* By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external inserts");
}
/**
* By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates");
}
/**
* Deletes the file associated with the specified content URI, as
* returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
* method does <b>not</b> throw an {@link java.io.IOException}; you must check its return value.
*
* @param uri A content URI for a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param selection Ignored. Set to {@code null}.
* @param selectionArgs Ignored. Set to {@code null}.
* @return 1 if the delete succeeds; otherwise, 0.
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0;
}
/**
* By default, FileProvider automatically returns the
* {@link ParcelFileDescriptor} for a file associated with a <code>content://</code>
* {@link Uri}. To get the {@link ParcelFileDescriptor}, call
* {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
* ContentResolver.openFileDescriptor}.
*
* To override this method, you must provide your own subclass of FileProvider.
*
* @param uri A content URI associated with a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
* write access, or "rwt" for read and write access that truncates any existing file.
* @return A new {@link ParcelFileDescriptor} with which you can access the file.
*/
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
/**
* Return {@link PathStrategy} for given authority, either by parsing or
* returning from cache.
@@ -633,6 +457,234 @@ public class FileProvider extends ContentProvider {
return strat;
}
/**
* Copied from ContentResolver.java
*/
private static int modeToMode(String mode) {
int modeBits;
if ("r".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("w".equals(mode) || "wt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else if ("wa".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
} else if ("rw".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
} else if ("rwt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
return modeBits;
}
private static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (segment != null) {
cur = new File(cur, segment);
}
}
return cur;
}
private static String[] copyOf(String[] original, int newLength) {
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static Object[] copyOf(Object[] original, int newLength) {
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
/**
* The default FileProvider implementation does not need to be initialized. If you want to
* override this method, you must provide your own subclass of FileProvider.
*/
@Override
public boolean onCreate() {
return true;
}
/**
* After the FileProvider is instantiated, this method is called to provide the system with
* information about the provider.
*
* @param context A {@link Context} for the current component.
* @param info A {@link ProviderInfo} for the new provider.
*/
@Override
public void attachInfo(Context context, ProviderInfo info) {
super.attachInfo(context, info);
// Sanity check our security
if (info.exported) {
throw new SecurityException("Provider must not be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grant uri permissions");
}
mStrategy = getPathStrategy(context, info.authority);
}
/**
* Use a content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()} to get information about a file
* managed by the FileProvider.
* FileProvider reports the column names defined in {@link android.provider.MediaStore.MediaColumns}:
* <ul>
* <li>{@link android.provider.MediaStore.MediaColumns#DISPLAY_NAME}</li>
* <li>{@link android.provider.MediaStore.MediaColumns#SIZE}</li>
* <li>{@link android.provider.MediaStore.MediaColumns#DATA}</li>
* </ul>
* For more information, see
* {@link ContentProvider#query(Uri, String[], String, String[], String)
* ContentProvider.query()}.
*
* @param uri A content URI returned by {@link #getUriForFile}.
* @param projection The list of columns to put into the {@link Cursor}. If null all columns are
* included.
* @param selection Selection criteria to apply. If null then all data that matches the content
* URI is returned.
* @param selectionArgs An array of {@link java.lang.String}, containing arguments to bind to
* the <i>selection</i> parameter. The <i>query</i> method scans <i>selection</i> from left to
* right and iterates through <i>selectionArgs</i>, replacing the current "?" character in
* <i>selection</i> with the value at the current position in <i>selectionArgs</i>. The
* values are bound to <i>selection</i> as {@link java.lang.String} values.
* @param sortOrder A {@link java.lang.String} containing the column name(s) on which to sort
* the resulting {@link Cursor}.
* @return A {@link Cursor} containing the results of the query.
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
if (projection == null) {
projection = COLUMNS;
}
String[] cols = new String[projection.length];
Object[] values = new Object[projection.length];
int i = 0;
for (String col : projection) {
if (MediaStore.MediaColumns.DISPLAY_NAME.equals(col)) {
cols[i] = MediaStore.MediaColumns.DISPLAY_NAME;
values[i++] = file.getName();
} else if (MediaStore.MediaColumns.SIZE.equals(col)) {
cols[i] = MediaStore.MediaColumns.SIZE;
values[i++] = file.length();
} else if (MediaStore.MediaColumns.DATA.equals(col)) {
cols[i] = MediaStore.MediaColumns.DATA;
values[i++] = file.getPath();
}
}
cols = copyOf(cols, i);
values = copyOf(values, i);
final MatrixCursor cursor = new MatrixCursor(cols, 1);
cursor.addRow(values);
return cursor;
}
/**
* Returns the MIME type of a content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
*
* @param uri A content URI returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @return If the associated file has an extension, the MIME type associated with that
* extension; otherwise <code>application/octet-stream</code>.
*/
@Override
public String getType(Uri uri) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int lastDot = file.getName().lastIndexOf('.');
if (lastDot >= 0) {
final String extension = file.getName().substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
/**
* By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("No external inserts");
}
/**
* By default, this method throws an {@link java.lang.UnsupportedOperationException}. You must
* subclass FileProvider if you want to provide different functionality.
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("No external updates");
}
/**
* Deletes the file associated with the specified content URI, as
* returned by {@link #getUriForFile(Context, String, File) getUriForFile()}. Notice that this
* method does <b>not</b> throw an {@link java.io.IOException}; you must check its return value.
*
* @param uri A content URI for a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param selection Ignored. Set to {@code null}.
* @param selectionArgs Ignored. Set to {@code null}.
* @return 1 if the delete succeeds; otherwise, 0.
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
return file.delete() ? 1 : 0;
}
/**
* By default, FileProvider automatically returns the
* {@link ParcelFileDescriptor} for a file associated with a <code>content://</code>
* {@link Uri}. To get the {@link ParcelFileDescriptor}, call
* {@link android.content.ContentResolver#openFileDescriptor(Uri, String)
* ContentResolver.openFileDescriptor}.
* <p>
* To override this method, you must provide your own subclass of FileProvider.
*
* @param uri A content URI associated with a file, as returned by
* {@link #getUriForFile(Context, String, File) getUriForFile()}.
* @param mode Access mode for the file. May be "r" for read-only access, "rw" for read and
* write access, or "rwt" for read and write access that truncates any existing file.
* @return A new {@link ParcelFileDescriptor} with which you can access the file.
*/
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
// ContentProvider has already checked granted permissions
final File file = mStrategy.getFileForUri(uri);
final int fileMode = modeToMode(mode);
return ParcelFileDescriptor.open(file, fileMode);
}
/**
* Strategy for mapping between {@link File} and {@link Uri}.
* <p>
@@ -650,12 +702,12 @@ public class FileProvider extends ContentProvider {
/**
* Return a {@link Uri} that represents the given {@link File}.
*/
public Uri getUriForFile(File file);
Uri getUriForFile(File file);
/**
* Return a {@link File} that represents the given {@link Uri}.
*/
public File getFileForUri(Uri uri);
File getFileForUri(Uri uri);
}
/**
@@ -761,54 +813,4 @@ public class FileProvider extends ContentProvider {
return file;
}
}
/**
* Copied from ContentResolver.java
*/
private static int modeToMode(String mode) {
int modeBits;
if ("r".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_ONLY;
} else if ("w".equals(mode) || "wt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else if ("wa".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_WRITE_ONLY
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_APPEND;
} else if ("rw".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE;
} else if ("rwt".equals(mode)) {
modeBits = ParcelFileDescriptor.MODE_READ_WRITE
| ParcelFileDescriptor.MODE_CREATE
| ParcelFileDescriptor.MODE_TRUNCATE;
} else {
throw new IllegalArgumentException("Invalid mode: " + mode);
}
return modeBits;
}
private static File buildPath(File base, String... segments) {
File cur = base;
for (String segment : segments) {
if (segment != null) {
cur = new File(cur, segment);
}
}
return cur;
}
private static String[] copyOf(String[] original, int newLength) {
final String[] result = new String[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
private static Object[] copyOf(Object[] original, int newLength) {
final Object[] result = new Object[newLength];
System.arraycopy(original, 0, result, 0, newLength);
return result;
}
}
@@ -22,7 +22,9 @@ package com.hippo.content;
import android.app.Activity;
import android.os.Bundle;
import com.hippo.scene.SceneApplication;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
@@ -31,60 +33,65 @@ import java.util.List;
public abstract class RecordingApplication extends SceneApplication {
private List<WeakReference<Activity>> list = new LinkedList<>();
private List<WeakReference<Activity>> list = new LinkedList<>();
@Override
public void onCreate() {
super.onCreate();
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
list.add(new java.lang.ref.WeakReference<>(activity));
}
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
list.add(new java.lang.ref.WeakReference<>(activity));
}
@Override
public void onActivityDestroyed(Activity activity) {
Iterator<WeakReference<Activity>> iterator = list.iterator();
while (iterator.hasNext()) {
WeakReference<Activity> reference = iterator.next();
Activity a = reference.get();
// Remove current activity and null
if (a == null || a == activity) {
iterator.remove();
}
@Override
public void onActivityDestroyed(Activity activity) {
Iterator<WeakReference<Activity>> iterator = list.iterator();
while (iterator.hasNext()) {
WeakReference<Activity> reference = iterator.next();
Activity a = reference.get();
// Remove current activity and null
if (a == null || a == activity) {
iterator.remove();
}
}
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
});
}
public void recreate() {
// Copy list
ArrayList<Activity> listCopy = new ArrayList<>(list.size());
for (WeakReference<Activity> reference : list) {
Activity activity = reference.get();
if (activity == null) continue;
listCopy.add(activity);
}
}
@Override
public void onActivityStarted(Activity activity) {}
@Override
public void onActivityResumed(Activity activity) {}
@Override
public void onActivityPaused(Activity activity) {}
@Override
public void onActivityStopped(Activity activity) {}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
});
}
public void recreate() {
// Copy list
ArrayList<Activity> listCopy = new ArrayList<>(list.size());
for (WeakReference<Activity> reference: list) {
Activity activity = reference.get();
if (activity == null) continue;
listCopy.add(activity);
// Finish all activities
for (int i = listCopy.size() - 1; i >= 0; --i) {
listCopy.get(i).recreate();
}
}
// Finish all activities
for (int i = listCopy.size() - 1; i >= 0; --i) {
listCopy.get(i).recreate();
}
}
}
@@ -23,6 +23,7 @@ package com.hippo.database;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -30,101 +31,101 @@ import java.util.Map;
public class MSQLiteBuilder {
public static final String COLUMN_ID = "_id";
public static final String COLUMN_ID = "_id";
private static final Map<Class, String> JAVA_TYPE_TO_SQLITE_TYPE = new HashMap<>();
private static final Map<Class, String> JAVA_TYPE_TO_SQLITE_TYPE = new HashMap<>();
static {
JAVA_TYPE_TO_SQLITE_TYPE.put(boolean.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(byte.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(short.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(int.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(long.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(float.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(double.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT");
}
private static String javaTypeToSQLiteType(Class clazz) {
String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz);
if (type == null) {
throw new IllegalStateException("Unknown type: " + clazz);
static {
JAVA_TYPE_TO_SQLITE_TYPE.put(boolean.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(byte.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(short.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(int.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(long.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(float.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(double.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT");
}
return type;
}
private int version = 0;
private List<String> statements;
private SparseArray<List<String>> statementsMap = new SparseArray<>();
private int version = 0;
private List<String> statements;
private SparseArray<List<String>> statementsMap = new SparseArray<>();
/**
* Bump database version.
*/
public MSQLiteBuilder version(int version) {
if (version <= this.version) {
throw new IllegalStateException("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".");
private static String javaTypeToSQLiteType(Class clazz) {
String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz);
if (type == null) {
throw new IllegalStateException("Unknown type: " + clazz);
}
return type;
}
this.version = version;
this.statements = new ArrayList<>();
this.statementsMap.put(version, this.statements);
return this;
}
/**
* Creates a table with int {@link #COLUMN_ID} primary key.
*/
public MSQLiteBuilder createTable(String table) {
return createTable(table, COLUMN_ID, int.class);
}
/**
* Creates a table.
*/
public MSQLiteBuilder createTable(String table, String column, Class clazz) {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);");
}
/**
* Drops a table.
*/
public MSQLiteBuilder dropTable(String table) {
return statement("DROP TABLE " + table + ";");
}
/**
* Inserts a column to the table.
*/
public MSQLiteBuilder insertColumn(String table, String column, Class clazz) {
return statement("ALTER TABLE " + table + " ADD COLUMN " + column + " " + javaTypeToSQLiteType(clazz) + ";");
}
/**
* Add a statement.
*/
public MSQLiteBuilder statement(String statement) {
if (version == 0 || statements == null) {
throw new IllegalStateException("Call version() first!");
/**
* Bump database version.
*/
public MSQLiteBuilder version(int version) {
if (version <= this.version) {
throw new IllegalStateException("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".");
}
this.version = version;
this.statements = new ArrayList<>();
this.statementsMap.put(version, this.statements);
return this;
}
statements.add(statement);
return this;
}
/**
* Build a SQLiteOpenHelper from it.
*/
public SQLiteOpenHelper build(Context context, String name, int version) {
return new MSQLiteOpenHelper(context, name, version, this);
}
List<String> getStatements(int oldVersion, int newVersion) {
List<String> result = new ArrayList<>();
for (int i = oldVersion + 1; i <= newVersion; ++i) {
List<String> list = statementsMap.get(i);
if (list != null) {
result.addAll(list);
}
/**
* Creates a table with int {@link #COLUMN_ID} primary key.
*/
public MSQLiteBuilder createTable(String table) {
return createTable(table, COLUMN_ID, int.class);
}
/**
* Creates a table.
*/
public MSQLiteBuilder createTable(String table, String column, Class clazz) {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);");
}
/**
* Drops a table.
*/
public MSQLiteBuilder dropTable(String table) {
return statement("DROP TABLE " + table + ";");
}
/**
* Inserts a column to the table.
*/
public MSQLiteBuilder insertColumn(String table, String column, Class clazz) {
return statement("ALTER TABLE " + table + " ADD COLUMN " + column + " " + javaTypeToSQLiteType(clazz) + ";");
}
/**
* Add a statement.
*/
public MSQLiteBuilder statement(String statement) {
if (version == 0 || statements == null) {
throw new IllegalStateException("Call version() first!");
}
statements.add(statement);
return this;
}
/**
* Build a SQLiteOpenHelper from it.
*/
public SQLiteOpenHelper build(Context context, String name, int version) {
return new MSQLiteOpenHelper(context, name, version, this);
}
List<String> getStatements(int oldVersion, int newVersion) {
List<String> result = new ArrayList<>();
for (int i = oldVersion + 1; i <= newVersion; ++i) {
List<String> list = statementsMap.get(i);
if (list != null) {
result.addAll(list);
}
}
return result;
}
return result;
}
}
@@ -23,34 +23,35 @@ package com.hippo.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.hippo.util.SqlUtils;
class MSQLiteOpenHelper extends SQLiteOpenHelper {
private final int version;
private final MSQLiteBuilder builder;
private final int version;
private final MSQLiteBuilder builder;
public MSQLiteOpenHelper(Context context, String name, int version, MSQLiteBuilder builder) {
super(context, name, null, version);
this.version = version;
this.builder = builder;
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (String command : builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command);
public MSQLiteOpenHelper(Context context, String name, int version, MSQLiteBuilder builder) {
super(context, name, null, version);
this.version = version;
this.builder = builder;
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
SqlUtils.dropAllTable(db);
onCreate(db);
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (String command : builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
SqlUtils.dropAllTable(db);
onCreate(db);
}
}
@@ -28,21 +28,18 @@ import com.hippo.yorozuya.MathUtils;
public class BatteryDrawable extends Drawable {
public static final int WARN_LIMIT = 15;
@SuppressWarnings("unused")
private static final String TAG = BatteryDrawable.class.getSimpleName();
public static final int WARN_LIMIT = 15;
private int mColor = Color.WHITE;
private int mWarningColor = 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 mColor = Color.WHITE;
private int mWarningColor = Color.RED;
private int mElect = -1;
private int mStart;
private int mStop;
@@ -18,7 +18,9 @@ package com.hippo.drawable;
import android.graphics.Bitmap;
import android.util.Log;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedHashSet;
@@ -23,158 +23,159 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.DrawableCompat;
public class DrawableWrapper extends Drawable implements Drawable.Callback {
private Drawable mDrawable;
private Drawable mDrawable;
public DrawableWrapper(Drawable drawable) {
this.setWrappedDrawable(drawable);
}
public void draw(@NonNull Canvas canvas) {
this.mDrawable.draw(canvas);
}
protected void onBoundsChange(Rect bounds) {
this.mDrawable.setBounds(bounds);
}
public void setChangingConfigurations(int configs) {
this.mDrawable.setChangingConfigurations(configs);
}
public int getChangingConfigurations() {
return this.mDrawable.getChangingConfigurations();
}
public void setDither(boolean dither) {
this.mDrawable.setDither(dither);
}
public void setFilterBitmap(boolean filter) {
this.mDrawable.setFilterBitmap(filter);
}
public void setAlpha(int alpha) {
this.mDrawable.setAlpha(alpha);
}
public void setColorFilter(ColorFilter cf) {
this.mDrawable.setColorFilter(cf);
}
public boolean isStateful() {
return this.mDrawable.isStateful();
}
public boolean setState(@NonNull int[] stateSet) {
return this.mDrawable.setState(stateSet);
}
@NonNull
public int[] getState() {
return this.mDrawable.getState();
}
public void jumpToCurrentState() {
this.mDrawable.jumpToCurrentState();
}
@NonNull
public Drawable getCurrent() {
return this.mDrawable.getCurrent();
}
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);
}
public int getOpacity() {
return this.mDrawable.getOpacity();
}
public Region getTransparentRegion() {
return this.mDrawable.getTransparentRegion();
}
public int getIntrinsicWidth() {
return this.mDrawable.getIntrinsicWidth();
}
public int getIntrinsicHeight() {
return this.mDrawable.getIntrinsicHeight();
}
public int getMinimumWidth() {
return this.mDrawable.getMinimumWidth();
}
public int getMinimumHeight() {
return this.mDrawable.getMinimumHeight();
}
public boolean getPadding(@NonNull Rect padding) {
return this.mDrawable.getPadding(padding);
}
public void invalidateDrawable(@NonNull Drawable who) {
this.invalidateSelf();
}
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
this.scheduleSelf(what, when);
}
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
this.unscheduleSelf(what);
}
protected boolean onLevelChange(int level) {
return this.mDrawable.setLevel(level);
}
public void setAutoMirrored(boolean mirrored) {
DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);
}
public boolean isAutoMirrored() {
return DrawableCompat.isAutoMirrored(this.mDrawable);
}
public void setTint(int tint) {
DrawableCompat.setTint(this.mDrawable, tint);
}
public void setTintList(ColorStateList tint) {
DrawableCompat.setTintList(this.mDrawable, tint);
}
public void setTintMode(@NonNull PorterDuff.Mode tintMode) {
DrawableCompat.setTintMode(this.mDrawable, tintMode);
}
public void setHotspot(float x, float y) {
DrawableCompat.setHotspot(this.mDrawable, x, y);
}
public void setHotspotBounds(int left, int top, int right, int bottom) {
DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);
}
public Drawable getWrappedDrawable() {
return this.mDrawable;
}
public void setWrappedDrawable(Drawable drawable) {
if (this.mDrawable != null) {
this.mDrawable.setCallback(null);
public DrawableWrapper(Drawable drawable) {
this.setWrappedDrawable(drawable);
}
this.mDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
public void draw(@NonNull Canvas canvas) {
this.mDrawable.draw(canvas);
}
protected void onBoundsChange(Rect bounds) {
this.mDrawable.setBounds(bounds);
}
public int getChangingConfigurations() {
return this.mDrawable.getChangingConfigurations();
}
public void setChangingConfigurations(int configs) {
this.mDrawable.setChangingConfigurations(configs);
}
public void setDither(boolean dither) {
this.mDrawable.setDither(dither);
}
public void setFilterBitmap(boolean filter) {
this.mDrawable.setFilterBitmap(filter);
}
public void setAlpha(int alpha) {
this.mDrawable.setAlpha(alpha);
}
public void setColorFilter(ColorFilter cf) {
this.mDrawable.setColorFilter(cf);
}
public boolean isStateful() {
return this.mDrawable.isStateful();
}
public boolean setState(@NonNull int[] stateSet) {
return this.mDrawable.setState(stateSet);
}
@NonNull
public int[] getState() {
return this.mDrawable.getState();
}
public void jumpToCurrentState() {
this.mDrawable.jumpToCurrentState();
}
@NonNull
public Drawable getCurrent() {
return this.mDrawable.getCurrent();
}
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart) || this.mDrawable.setVisible(visible, restart);
}
public int getOpacity() {
return this.mDrawable.getOpacity();
}
public Region getTransparentRegion() {
return this.mDrawable.getTransparentRegion();
}
public int getIntrinsicWidth() {
return this.mDrawable.getIntrinsicWidth();
}
public int getIntrinsicHeight() {
return this.mDrawable.getIntrinsicHeight();
}
public int getMinimumWidth() {
return this.mDrawable.getMinimumWidth();
}
public int getMinimumHeight() {
return this.mDrawable.getMinimumHeight();
}
public boolean getPadding(@NonNull Rect padding) {
return this.mDrawable.getPadding(padding);
}
public void invalidateDrawable(@NonNull Drawable who) {
this.invalidateSelf();
}
public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
this.scheduleSelf(what, when);
}
public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
this.unscheduleSelf(what);
}
protected boolean onLevelChange(int level) {
return this.mDrawable.setLevel(level);
}
public boolean isAutoMirrored() {
return DrawableCompat.isAutoMirrored(this.mDrawable);
}
public void setAutoMirrored(boolean mirrored) {
DrawableCompat.setAutoMirrored(this.mDrawable, mirrored);
}
public void setTint(int tint) {
DrawableCompat.setTint(this.mDrawable, tint);
}
public void setTintList(ColorStateList tint) {
DrawableCompat.setTintList(this.mDrawable, tint);
}
public void setTintMode(@NonNull PorterDuff.Mode tintMode) {
DrawableCompat.setTintMode(this.mDrawable, tintMode);
}
public void setHotspot(float x, float y) {
DrawableCompat.setHotspot(this.mDrawable, x, y);
}
public void setHotspotBounds(int left, int top, int right, int bottom) {
DrawableCompat.setHotspotBounds(this.mDrawable, left, top, right, bottom);
}
public Drawable getWrappedDrawable() {
return this.mDrawable;
}
public void setWrappedDrawable(Drawable drawable) {
if (this.mDrawable != null) {
this.mDrawable.setCallback(null);
}
this.mDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
}
}
}
}
@@ -27,7 +27,9 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.annotation.ColorInt;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.MathUtils;
@@ -36,10 +38,9 @@ import com.hippo.yorozuya.MathUtils;
*/
public class DrawerArrowDrawable extends Drawable {
private final Paint mPaint = new Paint();
// The angle in degrees that the arrow head is inclined at.
private static final float ARROW_HEAD_ANGLE = (float) Math.toRadians(45);
private final Paint mPaint = new Paint();
private final float mBarThickness;
// The length of top and bottom bars when they merge into an arrow
private final float mTopBottomArrowSize;
@@ -56,12 +57,12 @@ public class DrawerArrowDrawable extends Drawable {
private final Path mPath = new Path();
// The reported intrinsic size of the drawable.
private final int mSize;
// the amount that overlaps w/ bar size when rotation is max
private final float mMaxCutForBarSize;
// Whether we should mirror animation when animation is reversed.
private boolean mVerticalMirror = false;
// The interpolated version of the original progress
private float mProgress;
// the amount that overlaps w/ bar size when rotation is max
private final float mMaxCutForBarSize;
/**
* @param context used to get the configuration for the drawable from
@@ -18,7 +18,9 @@ package com.hippo.drawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.annotation.NonNull;
import com.hippo.conaco.Conaco;
import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.Unikery;
@@ -31,11 +33,9 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
ObservedTextView.OnWindowAttachListener {
private static final String TAG = UnikeryDrawable.class.getSimpleName();
private int mTaskId = Unikery.INVALID_ID;
private final ObservedTextView mTextView;
private final Conaco<ImageBitmap> mConaco;
private int mTaskId = Unikery.INVALID_ID;
private String mUrl;
public UnikeryDrawable(ObservedTextView textView, Conaco<ImageBitmap> conaco) {
@@ -91,13 +91,13 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
}
@Override
public void setTaskId(int id) {
mTaskId = id;
public int getTaskId() {
return mTaskId;
}
@Override
public int getTaskId() {
return mTaskId;
public void setTaskId(int id) {
mTaskId = id;
}
@Override
@@ -107,16 +107,20 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
}
@Override
public void onMiss(int source) {}
public void onMiss(int source) {
}
@Override
public void onRequest() {}
public void onRequest() {
}
@Override
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {}
public void onProgress(long singleReceivedSize, long receivedSize, long totalSize) {
}
@Override
public void onWait() {}
public void onWait() {
}
@Override
public boolean onGetValue(@NonNull ImageBitmap value, int source) {
@@ -26,14 +26,14 @@ public class WrapDrawable extends Drawable {
private Drawable mDrawable;
public void setDrawable(Drawable drawable) {
mDrawable = drawable;
}
public Drawable getDrawable() {
return mDrawable;
}
public void setDrawable(Drawable drawable) {
mDrawable = drawable;
}
public void updateBounds() {
setBounds(0, 0, getIntrinsicWidth(), getIntrinsicHeight());
}
@@ -63,15 +63,6 @@ public class WrapDrawable extends Drawable {
}
}
@Override
public void setChangingConfigurations(int configs) {
super.setChangingConfigurations(configs);
if (mDrawable != null) {
mDrawable.setChangingConfigurations(configs);
}
}
@Override
public int getChangingConfigurations() {
if (mDrawable != null) {
@@ -81,6 +72,15 @@ public class WrapDrawable extends Drawable {
}
}
@Override
public void setChangingConfigurations(int configs) {
super.setChangingConfigurations(configs);
if (mDrawable != null) {
mDrawable.setChangingConfigurations(configs);
}
}
@Override
public void setDither(boolean dither) {
super.setDither(dither);
@@ -1,63 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.hippo.scene.SceneFragment;
import java.util.Locale;
public final class Analytics {
private static final String DEVICE_LANGUAGE = "device_language";
private static FirebaseAnalytics analytics;
private Analytics() {}
public static void start(Context context) {
analytics = FirebaseAnalytics.getInstance(context);
analytics.setUserId(Settings.getUserID());
Locale locale = Locale.getDefault();
String language = locale.getLanguage();
if (TextUtils.isEmpty(language)) {
language = "none";
}
String country = locale.getCountry();
if (!TextUtils.isEmpty(country)) {
language = language + "-" + country;
}
language = language.toLowerCase();
analytics.setUserProperty(DEVICE_LANGUAGE, language);
}
public static boolean isEnabled() {
return analytics != null && Settings.getEnableAnalytics();
}
public static void onSceneView(SceneFragment scene) {
if (isEnabled()) {
Bundle bundle = new Bundle();
bundle.putString("scene_simple_class", scene.getClass().getSimpleName());
bundle.putString("scene_class", scene.getClass().getName());
analytics.logEvent("scene_view", bundle);
}
}
}
@@ -18,15 +18,19 @@ package com.hippo.ehviewer;
import android.content.Context;
import android.os.Environment;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class AppConfig {
@@ -147,11 +151,11 @@ public class AppConfig {
String message = e.getMessage();
String body = e.getBody();
if (null != message) {
os.write(message.getBytes("utf-8"));
os.write(message.getBytes(StandardCharsets.UTF_8));
os.write('\n');
}
if (null != body) {
os.write(body.getBytes("utf-8"));
os.write(body.getBytes(StandardCharsets.UTF_8));
}
os.flush();
} catch (IOException e1) {
+179 -109
View File
@@ -22,135 +22,205 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Debug;
import androidx.annotation.NonNull;
import com.hippo.scene.StageActivity;
import com.hippo.util.PackageUtils;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.OSUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public final class Crash {
private Crash() {}
@NonNull
private static String avoidNull(String str) {
return null != str ? str : "null";
}
private static void collectInfo(Context context, FileWriter fw) throws IOException {
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = String.valueOf(pi.versionCode);
fw.write("======== PackageInfo ========\r\n");
fw.write("PackageName=");fw.write(pi.packageName);fw.write("\r\n");
fw.write("VersionName=");fw.write(versionName);fw.write("\r\n");
fw.write("VersionCode=");fw.write(versionCode);fw.write("\r\n");
String signature = PackageUtils.getSignature(context, pi.packageName);
fw.write("Signature=");fw.write(null != signature ? signature : "null");fw.write("\r\n");
fw.write("\r\n");
}
} catch (PackageManager.NameNotFoundException e) {
fw.write("======== PackageInfo ========\r\n");
fw.write("Can't get package information\r\n");
fw.write("\r\n");
private Crash() {
}
// Runtime
String topActivityClazzName = "null";
String topSceneClazzName = "null";
try {
Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity();
if (null != topActivity) {
topActivityClazzName = topActivity.getClass().getName();
if (topActivity instanceof StageActivity) {
Class<?> clazz = ((StageActivity) topActivity).getTopSceneClass();
if (clazz != null) {
topSceneClazzName = clazz.getName();
}
@NonNull
private static String avoidNull(String str) {
return null != str ? str : "null";
}
private static void collectInfo(Context context, FileWriter fw) throws IOException {
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
if (pi != null) {
String versionName = pi.versionName == null ? "null" : pi.versionName;
String versionCode = String.valueOf(pi.versionCode);
fw.write("======== PackageInfo ========\r\n");
fw.write("PackageName=");
fw.write(pi.packageName);
fw.write("\r\n");
fw.write("VersionName=");
fw.write(versionName);
fw.write("\r\n");
fw.write("VersionCode=");
fw.write(versionCode);
fw.write("\r\n");
String signature = PackageUtils.getSignature(context, pi.packageName);
fw.write("Signature=");
fw.write(null != signature ? signature : "null");
fw.write("\r\n");
fw.write("\r\n");
}
} catch (PackageManager.NameNotFoundException e) {
fw.write("======== PackageInfo ========\r\n");
fw.write("Can't get package information\r\n");
fw.write("\r\n");
}
}
} catch (Throwable e) {
// Ignore
}
fw.write("======== Runtime ========\r\n");
fw.write("TopActivity=");fw.write(avoidNull(topActivityClazzName));fw.write("\r\n");
fw.write("TopScene=");fw.write(avoidNull(topSceneClazzName));fw.write("\r\n");
fw.write("\r\n");
// Device info
fw.write("======== DeviceInfo ========\r\n");
fw.write("BOARD=");fw.write(Build.BOARD);fw.write("\r\n");
fw.write("BOOTLOADER=");fw.write(Build.BOOTLOADER);fw.write("\r\n");
fw.write("CPU_ABI=");fw.write(Build.CPU_ABI);fw.write("\r\n");
fw.write("CPU_ABI2=");fw.write(Build.CPU_ABI2);fw.write("\r\n");
fw.write("DEVICE=");fw.write(Build.DEVICE);fw.write("\r\n");
fw.write("DISPLAY=");fw.write(Build.DISPLAY);fw.write("\r\n");
fw.write("FINGERPRINT=");fw.write(Build.FINGERPRINT);fw.write("\r\n");
fw.write("HARDWARE=");fw.write(Build.HARDWARE);fw.write("\r\n");
fw.write("HOST=");fw.write(Build.HOST);fw.write("\r\n");
fw.write("ID=");fw.write(Build.ID);fw.write("\r\n");
fw.write("MANUFACTURER=");fw.write(Build.MANUFACTURER);fw.write("\r\n");
fw.write("MODEL=");fw.write(Build.MODEL);fw.write("\r\n");
fw.write("PRODUCT=");fw.write(Build.PRODUCT);fw.write("\r\n");
fw.write("RADIO=");fw.write(Build.getRadioVersion());fw.write("\r\n");
fw.write("SERIAL=");fw.write(Build.SERIAL);fw.write("\r\n");
fw.write("TAGS=");fw.write(Build.TAGS);fw.write("\r\n");
fw.write("TYPE=");fw.write(Build.TYPE);fw.write("\r\n");
fw.write("USER=");fw.write(Build.USER);fw.write("\r\n");
fw.write("CODENAME=");fw.write(Build.VERSION.CODENAME);fw.write("\r\n");
fw.write("INCREMENTAL=");fw.write(Build.VERSION.INCREMENTAL);fw.write("\r\n");
fw.write("RELEASE=");fw.write(Build.VERSION.RELEASE);fw.write("\r\n");
fw.write("SDK=");fw.write(Integer.toString(Build.VERSION.SDK_INT));fw.write("\r\n");
fw.write("MEMORY=");fw.write(
FileUtils.humanReadableByteCount(OSUtils.getAppAllocatedMemory(), false));fw.write("\r\n");
fw.write("MEMORY_NATIVE=");fw.write(FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));fw.write("\r\n");
fw.write("MEMORY_MAX=");fw.write(FileUtils.humanReadableByteCount(OSUtils.getAppMaxMemory(), false));fw.write("\r\n");
fw.write("MEMORY_TOTAL=");fw.write(FileUtils.humanReadableByteCount(OSUtils.getTotalMemory(), false));fw.write("\r\n");
fw.write("\r\n");
}
// Runtime
String topActivityClazzName = "null";
String topSceneClazzName = "null";
try {
Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity();
if (null != topActivity) {
topActivityClazzName = topActivity.getClass().getName();
if (topActivity instanceof StageActivity) {
Class<?> clazz = ((StageActivity) topActivity).getTopSceneClass();
if (clazz != null) {
topSceneClazzName = clazz.getName();
}
}
}
} catch (Throwable e) {
// Ignore
}
fw.write("======== Runtime ========\r\n");
fw.write("TopActivity=");
fw.write(avoidNull(topActivityClazzName));
fw.write("\r\n");
fw.write("TopScene=");
fw.write(avoidNull(topSceneClazzName));
fw.write("\r\n");
fw.write("\r\n");
private static void getThrowableInfo(Throwable t, FileWriter fw) {
PrintWriter printWriter = new PrintWriter(fw);
t.printStackTrace(printWriter);
Throwable cause = t.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
}
public static void saveCrashLog(Context context, Throwable t) {
File dir = AppConfig.getExternalCrashDir();
if (dir == null) {
return;
// Device info
fw.write("======== DeviceInfo ========\r\n");
fw.write("BOARD=");
fw.write(Build.BOARD);
fw.write("\r\n");
fw.write("BOOTLOADER=");
fw.write(Build.BOOTLOADER);
fw.write("\r\n");
fw.write("CPU_ABI=");
fw.write(Build.CPU_ABI);
fw.write("\r\n");
fw.write("CPU_ABI2=");
fw.write(Build.CPU_ABI2);
fw.write("\r\n");
fw.write("DEVICE=");
fw.write(Build.DEVICE);
fw.write("\r\n");
fw.write("DISPLAY=");
fw.write(Build.DISPLAY);
fw.write("\r\n");
fw.write("FINGERPRINT=");
fw.write(Build.FINGERPRINT);
fw.write("\r\n");
fw.write("HARDWARE=");
fw.write(Build.HARDWARE);
fw.write("\r\n");
fw.write("HOST=");
fw.write(Build.HOST);
fw.write("\r\n");
fw.write("ID=");
fw.write(Build.ID);
fw.write("\r\n");
fw.write("MANUFACTURER=");
fw.write(Build.MANUFACTURER);
fw.write("\r\n");
fw.write("MODEL=");
fw.write(Build.MODEL);
fw.write("\r\n");
fw.write("PRODUCT=");
fw.write(Build.PRODUCT);
fw.write("\r\n");
fw.write("RADIO=");
fw.write(Build.getRadioVersion());
fw.write("\r\n");
fw.write("SERIAL=");
fw.write(Build.SERIAL);
fw.write("\r\n");
fw.write("TAGS=");
fw.write(Build.TAGS);
fw.write("\r\n");
fw.write("TYPE=");
fw.write(Build.TYPE);
fw.write("\r\n");
fw.write("USER=");
fw.write(Build.USER);
fw.write("\r\n");
fw.write("CODENAME=");
fw.write(Build.VERSION.CODENAME);
fw.write("\r\n");
fw.write("INCREMENTAL=");
fw.write(Build.VERSION.INCREMENTAL);
fw.write("\r\n");
fw.write("RELEASE=");
fw.write(Build.VERSION.RELEASE);
fw.write("\r\n");
fw.write("SDK=");
fw.write(Integer.toString(Build.VERSION.SDK_INT));
fw.write("\r\n");
fw.write("MEMORY=");
fw.write(
FileUtils.humanReadableByteCount(OSUtils.getAppAllocatedMemory(), false));
fw.write("\r\n");
fw.write("MEMORY_NATIVE=");
fw.write(FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));
fw.write("\r\n");
fw.write("MEMORY_MAX=");
fw.write(FileUtils.humanReadableByteCount(OSUtils.getAppMaxMemory(), false));
fw.write("\r\n");
fw.write("MEMORY_TOTAL=");
fw.write(FileUtils.humanReadableByteCount(OSUtils.getTotalMemory(), false));
fw.write("\r\n");
fw.write("\r\n");
}
String nowString = ReadableTime.getFilenamableTime(System.currentTimeMillis());
String fileName = "crash-" + nowString + ".log";
File file = new File(dir, fileName);
private static void getThrowableInfo(Throwable t, FileWriter fw) {
PrintWriter printWriter = new PrintWriter(fw);
t.printStackTrace(printWriter);
Throwable cause = t.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
}
FileWriter fw = null;
try {
fw = new FileWriter(file);
fw.write("TIME=");fw.write(nowString);fw.write("\r\n");
fw.write("\r\n");
collectInfo(context, fw);
fw.write("======== CrashInfo ========\r\n");
getThrowableInfo(t, fw);
fw.write("\r\n");
fw.flush();
} catch (Exception e) {
file.delete();
} finally {
IOUtils.closeQuietly(fw);
public static void saveCrashLog(Context context, Throwable t) {
File dir = AppConfig.getExternalCrashDir();
if (dir == null) {
return;
}
String nowString = ReadableTime.getFilenamableTime(System.currentTimeMillis());
String fileName = "crash-" + nowString + ".log";
File file = new File(dir, fileName);
FileWriter fw = null;
try {
fw = new FileWriter(file);
fw.write("TIME=");
fw.write(nowString);
fw.write("\r\n");
fw.write("\r\n");
collectInfo(context, fw);
fw.write("======== CrashInfo ========\r\n");
getThrowableInfo(t, fw);
fw.write("\r\n");
fw.flush();
} catch (Exception e) {
file.delete();
} finally {
IOUtils.closeQuietly(fw);
}
}
}
}
@@ -28,9 +28,11 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Debug;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.LruCache;
import com.getkeepsafe.relinker.ReLinker;
import com.hippo.a7zip.A7Zip;
import com.hippo.a7zip.A7ZipExtractLite;
@@ -58,21 +60,21 @@ import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.OSUtils;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
public class EhApplication extends RecordingApplication {
public static final boolean BETA = false;
private static final String TAG = EhApplication.class.getSimpleName();
private static final String KEY_GLOBAL_STUFF_NEXT_ID = "global_stuff_next_id";
public static final boolean BETA = false;
private static final boolean DEBUG_CONACO = false;
private static final boolean DEBUG_PRINT_NATIVE_MEMORY = false;
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
@@ -82,6 +84,7 @@ public class EhApplication extends RecordingApplication {
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
private final HashMap<Integer, Object> mGlobalStuffMap = new HashMap<>();
private final List<Activity> mActivityList = new ArrayList<>();
private EhCookieStore mEhCookieStore;
private EhClient mEhClient;
private EhProxySelector mEhProxySelector;
@@ -93,182 +96,12 @@ public class EhApplication extends RecordingApplication {
private DownloadManager mDownloadManager;
private Hosts mHosts;
private FavouriteStatusRouter mFavouriteStatusRouter;
private final List<Activity> mActivityList = new ArrayList<>();
private boolean initialized = false;
public static EhApplication getInstance() {
return instance;
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
instance = this;
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
try {
// Always save crash file if onCreate() is not done
if (!initialized || Settings.getSaveCrashLog()) {
Crash.saveCrashLog(instance, e);
}
} catch (Throwable ignored) { }
if (handler != null) {
handler.uncaughtException(t, e);
}
});
super.onCreate();
GetText.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
Html.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
EhEngine.initialize();
BitmapUtils.initialize(this);
Image.initialize(this);
A7Zip.loadLibrary(A7ZipExtractLite.LIBRARY, libname -> ReLinker.loadLibrary(EhApplication.this, libname));
if (EhDB.needMerge()) {
EhDB.mergeOldDB(this);
}
if (Settings.getEnableAnalytics()) {
Analytics.start(this);
}
// Do io tasks in new thread
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
// Check no media file
try {
UniFile downloadLocation = Settings.getDownloadLocation();
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation);
} else {
CommonOperations.ensureNoMediaFile(downloadLocation);
}
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
// Clear temp files
try {
clearTempDir();
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
return null;
}
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
// Check app update
update();
// Update version code
try {
PackageInfo pi= getPackageManager().getPackageInfo(getPackageName(), 0);
Settings.putVersionCode(pi.versionCode);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
debugPrint();
}
initialized = true;
}
private void clearTempDir() {
File dir = AppConfig.getTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
dir = AppConfig.getExternalTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
// Add .nomedia to external temp dir
CommonOperations.ensureNoMediaFile(UniFile.fromFile(AppConfig.getExternalTempDir()));
}
private void update() {
int version = Settings.getVersionCode();
if (version < 52) {
Settings.putGuideGallery(true);
}
}
public void clearMemoryCache() {
if (null != mConaco) {
mConaco.getBeerBelly().clearMemory();
}
if (null != mGalleryDetailCache) {
mGalleryDetailCache.evictAll();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
clearMemoryCache();
}
}
private void debugPrint() {
new Runnable() {
@Override
public void run() {
if (DEBUG_PRINT_NATIVE_MEMORY) {
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
Debug.getNativeHeapAllocatedSize(), false));
}
if (DEBUG_PRINT_IMAGE_COUNT) {
Log.i(TAG, "Image count: " + Image.getImageCount());
}
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
}
}.run();
}
public int putGlobalStuff(@NonNull Object o) {
int id = mIdGenerator.nextId();
mGlobalStuffMap.put(id, o);
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
return id;
}
public boolean containGlobalStuff(int id) {
return mGlobalStuffMap.containsKey(id);
}
public Object getGlobalStuff(int id) {
return mGlobalStuffMap.get(id);
}
public Object removeGlobalStuff(int id) {
return mGlobalStuffMap.remove(id);
}
public boolean removeGlobalStuff(Object o) {
return mGlobalStuffMap.values().removeAll(Collections.singleton(o));
}
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhCookieStore == null) {
@@ -410,6 +243,170 @@ public class EhApplication extends RecordingApplication {
return "ehviewersu$gmail.com".replace('$', '@');
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
instance = this;
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
try {
// Always save crash file if onCreate() is not done
if (!initialized || Settings.getSaveCrashLog()) {
Crash.saveCrashLog(instance, e);
}
} catch (Throwable ignored) {
}
if (handler != null) {
handler.uncaughtException(t, e);
}
});
super.onCreate();
GetText.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
Html.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
EhEngine.initialize();
BitmapUtils.initialize(this);
Image.initialize(this);
A7Zip.loadLibrary(A7ZipExtractLite.LIBRARY, libname -> ReLinker.loadLibrary(EhApplication.this, libname));
if (EhDB.needMerge()) {
EhDB.mergeOldDB(this);
}
// Do io tasks in new thread
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
// Check no media file
try {
UniFile downloadLocation = Settings.getDownloadLocation();
if (Settings.getMediaScan()) {
CommonOperations.removeNoMediaFile(downloadLocation);
} else {
CommonOperations.ensureNoMediaFile(downloadLocation);
}
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
// Clear temp files
try {
clearTempDir();
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
return null;
}
}.executeOnExecutor(IoThreadPoolExecutor.getInstance());
// Check app update
update();
// Update version code
try {
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
Settings.putVersionCode(pi.versionCode);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
mIdGenerator.setNextId(Settings.getInt(KEY_GLOBAL_STUFF_NEXT_ID, 0));
if (DEBUG_PRINT_NATIVE_MEMORY || DEBUG_PRINT_IMAGE_COUNT) {
debugPrint();
}
initialized = true;
}
private void clearTempDir() {
File dir = AppConfig.getTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
dir = AppConfig.getExternalTempDir();
if (null != dir) {
FileUtils.deleteContent(dir);
}
// Add .nomedia to external temp dir
CommonOperations.ensureNoMediaFile(UniFile.fromFile(AppConfig.getExternalTempDir()));
}
private void update() {
int version = Settings.getVersionCode();
if (version < 52) {
Settings.putGuideGallery(true);
}
}
public void clearMemoryCache() {
if (null != mConaco) {
mConaco.getBeerBelly().clearMemory();
}
if (null != mGalleryDetailCache) {
mGalleryDetailCache.evictAll();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
clearMemoryCache();
}
}
private void debugPrint() {
new Runnable() {
@Override
public void run() {
if (DEBUG_PRINT_NATIVE_MEMORY) {
Log.i(TAG, "Native memory: " + FileUtils.humanReadableByteCount(
Debug.getNativeHeapAllocatedSize(), false));
}
if (DEBUG_PRINT_IMAGE_COUNT) {
Log.i(TAG, "Image count: " + Image.getImageCount());
}
SimpleHandler.getInstance().postDelayed(this, DEBUG_PRINT_INTERVAL);
}
}.run();
}
public int putGlobalStuff(@NonNull Object o) {
int id = mIdGenerator.nextId();
mGlobalStuffMap.put(id, o);
Settings.putInt(KEY_GLOBAL_STUFF_NEXT_ID, mIdGenerator.nextId());
return id;
}
public boolean containGlobalStuff(int id) {
return mGlobalStuffMap.containsKey(id);
}
public Object getGlobalStuff(int id) {
return mGlobalStuffMap.get(id);
}
public Object removeGlobalStuff(int id) {
return mGlobalStuffMap.remove(id);
}
public boolean removeGlobalStuff(Object o) {
return mGlobalStuffMap.values().removeAll(Collections.singleton(o));
}
public void registerActivity(Activity activity) {
mActivityList.add(activity);
}
+91 -79
View File
@@ -21,8 +21,10 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.dao.DaoMaster;
@@ -47,9 +49,7 @@ import com.hippo.util.SqlUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.ObjectUtils;
import com.hippo.yorozuya.collect.SparseJLArray;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.query.CloseableListIterator;
import de.greenrobot.dao.query.LazyList;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -59,6 +59,10 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.query.CloseableListIterator;
import de.greenrobot.dao.query.LazyList;
public class EhDB {
private static final String TAG = EhDB.class.getSimpleName();
@@ -70,34 +74,16 @@ public class EhDB {
private static boolean sHasOldDB;
private static boolean sNewDB;
private static class DBOpenHelper extends DaoMaster.OpenHelper {
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onCreate(SQLiteDatabase db) {
super.onCreate(db);
sNewDB = true;
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
upgradeDB(db, oldVersion);
}
}
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
switch (oldVersion) {
case 1: // 1 to 2, add FILTER
FilterDao.createTable(db, true);
case 2: // 2 to 3, add ENABLE column to table FILTER
db.execSQL("CREATE TABLE " + "\"FILTER2\" (" +
"\"_id\" INTEGER PRIMARY KEY ," +
"\"MODE\" INTEGER NOT NULL ," +
"\"TEXT\" TEXT," +
"\"ENABLE\" INTEGER);");
"\"_id\" INTEGER PRIMARY KEY ," +
"\"MODE\" INTEGER NOT NULL ," +
"\"TEXT\" TEXT," +
"\"ENABLE\" INTEGER);");
db.execSQL("INSERT INTO \"FILTER2\" (" +
"_id, MODE, TEXT, ENABLE)" +
"SELECT _id, MODE, TEXT, 1 FROM FILTER;");
@@ -105,48 +91,24 @@ public class EhDB {
db.execSQL("ALTER TABLE FILTER2 RENAME TO FILTER");
case 3: // 3 to 4, add PAGE_FROM and PAGE_TO column to QUICK_SEARCH
db.execSQL("CREATE TABLE " + "\"QUICK_SEARCH2\" (" +
"\"_id\" INTEGER PRIMARY KEY ," +
"\"NAME\" TEXT," +
"\"MODE\" INTEGER NOT NULL ," +
"\"CATEGORY\" INTEGER NOT NULL ," +
"\"KEYWORD\" TEXT," +
"\"ADVANCE_SEARCH\" INTEGER NOT NULL ," +
"\"MIN_RATING\" INTEGER NOT NULL ," +
"\"PAGE_FROM\" INTEGER NOT NULL ," +
"\"PAGE_TO\" INTEGER NOT NULL ," +
"\"TIME\" INTEGER NOT NULL );");
"\"_id\" INTEGER PRIMARY KEY ," +
"\"NAME\" TEXT," +
"\"MODE\" INTEGER NOT NULL ," +
"\"CATEGORY\" INTEGER NOT NULL ," +
"\"KEYWORD\" TEXT," +
"\"ADVANCE_SEARCH\" INTEGER NOT NULL ," +
"\"MIN_RATING\" INTEGER NOT NULL ," +
"\"PAGE_FROM\" INTEGER NOT NULL ," +
"\"PAGE_TO\" INTEGER NOT NULL ," +
"\"TIME\" INTEGER NOT NULL );");
db.execSQL("INSERT INTO \"QUICK_SEARCH2\" (" +
"_id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, PAGE_FROM, PAGE_TO, TIME)" +
"SELECT _id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, -1, -1, TIME FROM QUICK_SEARCH;");
"_id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, PAGE_FROM, PAGE_TO, TIME)" +
"SELECT _id, NAME, MODE, CATEGORY, KEYWORD, ADVANCE_SEARCH, MIN_RATING, -1, -1, TIME FROM QUICK_SEARCH;");
db.execSQL("DROP TABLE QUICK_SEARCH");
db.execSQL("ALTER TABLE QUICK_SEARCH2 RENAME TO QUICK_SEARCH");
}
}
private static class OldDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "data";
private static final int VERSION = 4;
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";
public OldDBHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public static void initialize(Context context) {
sHasOldDB = context.getDatabasePath("data").exists();
@@ -362,7 +324,7 @@ public class EhDB {
DownloadsDao dao = sDaoSession.getDownloadsDao();
List<DownloadInfo> list = dao.queryBuilder().orderDesc(DownloadsDao.Properties.Time).list();
// Fix state
for (DownloadInfo info: list) {
for (DownloadInfo info : list) {
if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) {
info.state = DownloadInfo.STATE_NONE;
}
@@ -491,7 +453,7 @@ public class EhDB {
}
public static synchronized List<GalleryInfo> searchLocalFavorites(String query) {
query = SqlUtils.sqlEscapeString("%" + query+ "%");
query = SqlUtils.sqlEscapeString("%" + query + "%");
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
List<LocalFavoriteInfo> list = dao.queryBuilder().orderDesc(LocalFavoritesDao.Properties.Time)
.where(LocalFavoritesDao.Properties.Title.like(query)).list();
@@ -506,7 +468,7 @@ public class EhDB {
public static synchronized void removeLocalFavorites(long[] gidArray) {
LocalFavoritesDao dao = sDaoSession.getLocalFavoritesDao();
for (long gid: gidArray) {
for (long gid : gidArray) {
dao.deleteByKey(gid);
}
}
@@ -531,7 +493,7 @@ public class EhDB {
}
public static synchronized void putLocalFavorites(List<GalleryInfo> galleryInfoList) {
for (GalleryInfo gi: galleryInfoList) {
for (GalleryInfo gi : galleryInfoList) {
putLocalFavorites(gi);
}
}
@@ -607,7 +569,7 @@ public class EhDB {
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
HistoryDao dao = sDaoSession.getHistoryDao();
for (HistoryInfo info: historyInfoList) {
for (HistoryInfo info : historyInfoList) {
if (null == dao.load(info.gid)) {
dao.insert(info);
}
@@ -670,14 +632,22 @@ public class EhDB {
try (SQLiteDatabase db = helper.getWritableDatabase()) {
DaoMaster daoMaster = new DaoMaster(db);
DaoSession exportSession = daoMaster.newSession();
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao())) return false;
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao())) return false;
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao())) return false;
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao())) return false;
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao())) return false;
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao())) return false;
if (!copyDao(sDaoSession.getBookmarksBao(), exportSession.getBookmarksBao())) return false;
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao())) return false;
if (!copyDao(sDaoSession.getDownloadsDao(), exportSession.getDownloadsDao()))
return false;
if (!copyDao(sDaoSession.getDownloadLabelDao(), exportSession.getDownloadLabelDao()))
return false;
if (!copyDao(sDaoSession.getDownloadDirnameDao(), exportSession.getDownloadDirnameDao()))
return false;
if (!copyDao(sDaoSession.getHistoryDao(), exportSession.getHistoryDao()))
return false;
if (!copyDao(sDaoSession.getQuickSearchDao(), exportSession.getQuickSearchDao()))
return false;
if (!copyDao(sDaoSession.getLocalFavoritesDao(), exportSession.getLocalFavoritesDao()))
return false;
if (!copyDao(sDaoSession.getBookmarksBao(), exportSession.getBookmarksBao()))
return false;
if (!copyDao(sDaoSession.getFilterDao(), exportSession.getFilterDao()))
return false;
}
// Copy export db to data dir
@@ -737,7 +707,7 @@ public class EhDB {
// Download dirname
List<DownloadDirname> downloadDirnameList = session.getDownloadDirnameDao().queryBuilder().list();
for (DownloadDirname dirname: downloadDirnameList) {
for (DownloadDirname dirname : downloadDirnameList) {
putDownloadDirname(dirname.getGid(), dirname.getDirname());
}
@@ -748,9 +718,9 @@ public class EhDB {
// QuickSearch
List<QuickSearch> quickSearchList = session.getQuickSearchDao().queryBuilder().list();
List<QuickSearch> currentQuickSearchList = sDaoSession.getQuickSearchDao().queryBuilder().list();
for (QuickSearch quickSearch: quickSearchList) {
for (QuickSearch quickSearch : quickSearchList) {
String name = quickSearch.name;
for (QuickSearch q: currentQuickSearchList) {
for (QuickSearch q : currentQuickSearchList) {
if (ObjectUtils.equal(q.name, name)) {
// The same name
name = null;
@@ -765,7 +735,7 @@ public class EhDB {
// LocalFavorites
List<LocalFavoriteInfo> localFavoriteInfoList = session.getLocalFavoritesDao().queryBuilder().list();
for (LocalFavoriteInfo info: localFavoriteInfoList) {
for (LocalFavoriteInfo info : localFavoriteInfoList) {
putLocalFavorites(info);
}
@@ -775,7 +745,7 @@ public class EhDB {
// Filter
List<Filter> filterList = session.getFilterDao().queryBuilder().list();
List<Filter> currentFilterList = sDaoSession.getFilterDao().queryBuilder().list();
for (Filter filter: filterList) {
for (Filter filter : filterList) {
if (!currentFilterList.contains(filter)) {
addFilter(filter);
}
@@ -788,4 +758,46 @@ public class EhDB {
return context.getString(R.string.cant_read_the_file);
}
}
private static class DBOpenHelper extends DaoMaster.OpenHelper {
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onCreate(SQLiteDatabase db) {
super.onCreate(db);
sNewDB = true;
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
upgradeDB(db, oldVersion);
}
}
private static class OldDBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "data";
private static final int VERSION = 4;
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";
public OldDBHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
@@ -17,8 +17,10 @@
package com.hippo.ehviewer;
import android.text.TextUtils;
import com.hippo.network.InetValidator;
import com.hippo.util.ExceptionUtils;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -31,76 +33,78 @@ import java.util.List;
public class EhProxySelector extends ProxySelector {
public static final int TYPE_DIRECT = 0;
public static final int TYPE_SYSTEM = 1;
public static final int TYPE_HTTP = 2;
public static final int TYPE_SOCKS = 3;
public static final int TYPE_DIRECT = 0;
public static final int TYPE_SYSTEM = 1;
public static final int TYPE_HTTP = 2;
public static final int TYPE_SOCKS = 3;
private ProxySelector delegation;
private ProxySelector alternative;
private ProxySelector delegation;
private ProxySelector alternative;
EhProxySelector() {
alternative = ProxySelector.getDefault();
if (alternative == null) {
alternative = new NullProxySelector();
}
updateProxy();
}
public void updateProxy() {
switch (Settings.getProxyType()) {
case TYPE_DIRECT:
delegation = new NullProxySelector();
break;
default:
case TYPE_SYSTEM:
delegation = alternative;
break;
case TYPE_HTTP:
case TYPE_SOCKS:
delegation = null;
break;
}
}
@Override
public List<Proxy> select(URI uri) {
int type = Settings.getProxyType();
if (type == TYPE_HTTP || type == TYPE_SOCKS) {
try {
String ip = Settings.getProxyIp();
int port = Settings.getProxyPort();
if (!TextUtils.isEmpty(ip) && InetValidator.isValidInetPort(port)) {
InetAddress inetAddress = InetAddress.getByName(ip);
SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
return Collections.singletonList(new Proxy(type == TYPE_HTTP ? Proxy.Type.HTTP : Proxy.Type.SOCKS, socketAddress));
EhProxySelector() {
alternative = ProxySelector.getDefault();
if (alternative == null) {
alternative = new NullProxySelector();
}
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
updateProxy();
}
if (delegation != null) {
return delegation.select(uri);
public void updateProxy() {
switch (Settings.getProxyType()) {
case TYPE_DIRECT:
delegation = new NullProxySelector();
break;
default:
case TYPE_SYSTEM:
delegation = alternative;
break;
case TYPE_HTTP:
case TYPE_SOCKS:
delegation = null;
break;
}
}
return alternative.select(uri);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (delegation != null) {
delegation.select(uri);
}
}
private static class NullProxySelector extends ProxySelector {
@Override
public List<Proxy> select(URI uri) {
return Collections.singletonList(Proxy.NO_PROXY);
int type = Settings.getProxyType();
if (type == TYPE_HTTP || type == TYPE_SOCKS) {
try {
String ip = Settings.getProxyIp();
int port = Settings.getProxyPort();
if (!TextUtils.isEmpty(ip) && InetValidator.isValidInetPort(port)) {
InetAddress inetAddress = InetAddress.getByName(ip);
SocketAddress socketAddress = new InetSocketAddress(inetAddress, port);
return Collections.singletonList(new Proxy(type == TYPE_HTTP ? Proxy.Type.HTTP : Proxy.Type.SOCKS, socketAddress));
}
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
}
}
if (delegation != null) {
return delegation.select(uri);
}
return alternative.select(uri);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { }
}
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (delegation != null) {
delegation.select(uri);
}
}
private static class NullProxySelector extends ProxySelector {
@Override
public List<Proxy> select(URI uri) {
return Collections.singletonList(Proxy.NO_PROXY);
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
}
}
}
@@ -17,8 +17,10 @@
package com.hippo.ehviewer;
import android.annotation.SuppressLint;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.yorozuya.IntIdGenerator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -26,47 +28,47 @@ import java.util.Map;
public class FavouriteStatusRouter {
private static final String KEY_DATA_MAP_NEXT_ID = "data_map_next_id";
private static final String KEY_DATA_MAP_NEXT_ID = "data_map_next_id";
private final IntIdGenerator idGenerator = new IntIdGenerator(Settings.getInt(KEY_DATA_MAP_NEXT_ID, 0));
@SuppressLint("UseSparseArrays")
private final HashMap<Integer, Map<Long, GalleryInfo>> maps = new HashMap<>();
private final IntIdGenerator idGenerator = new IntIdGenerator(Settings.getInt(KEY_DATA_MAP_NEXT_ID, 0));
@SuppressLint("UseSparseArrays")
private final HashMap<Integer, Map<Long, GalleryInfo>> maps = new HashMap<>();
private List<Listener> listeners = new ArrayList<>();
private List<Listener> listeners = new ArrayList<>();
public int saveDataMap(Map<Long, GalleryInfo> map) {
int id = idGenerator.nextId();
maps.put(id, map);
Settings.putInt(KEY_DATA_MAP_NEXT_ID, idGenerator.nextId());
return id;
}
public Map<Long, GalleryInfo> restoreDataMap(int id) {
return maps.remove(id);
}
public void modifyFavourites(long gid, int slot) {
for (Map<Long, GalleryInfo> map : maps.values()) {
GalleryInfo info = map.get(gid);
if (info != null) {
info.favoriteSlot = slot;
}
public int saveDataMap(Map<Long, GalleryInfo> map) {
int id = idGenerator.nextId();
maps.put(id, map);
Settings.putInt(KEY_DATA_MAP_NEXT_ID, idGenerator.nextId());
return id;
}
for (Listener listener : listeners) {
listener.onModifyFavourites(gid, slot);
public Map<Long, GalleryInfo> restoreDataMap(int id) {
return maps.remove(id);
}
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void modifyFavourites(long gid, int slot) {
for (Map<Long, GalleryInfo> map : maps.values()) {
GalleryInfo info = map.get(gid);
if (info != null) {
info.favoriteSlot = slot;
}
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
for (Listener listener : listeners) {
listener.onModifyFavourites(gid, slot);
}
}
public interface Listener {
void onModifyFavourites(long gid, int slot);
}
public void addListener(Listener listener) {
listeners.add(listener);
}
public void removeListener(Listener listener) {
listeners.remove(listener);
}
public interface Listener {
void onModifyFavourites(long gid, int slot);
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
+292 -294
View File
@@ -26,9 +26,12 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.hippo.database.MSQLiteBuilder;
import com.hippo.util.SqlUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
@@ -36,315 +39,310 @@ import java.util.List;
public class Hosts {
private static final int VERSION_1 = 1;
private static final String TABLE_HOSTS = "HOSTS";
private static final String COLUMN_HOST = "HOST";
private static final String COLUMN_IP = "IP";
private static final int VERSION_1 = 1;
private static final String TABLE_HOSTS = "HOSTS";
private static final String COLUMN_HOST = "HOST";
private static final String COLUMN_IP = "IP";
private static final int DB_VERSION = VERSION_1;
private static final int DB_VERSION = VERSION_1;
private final SQLiteOpenHelper helper;
private final SQLiteDatabase db;
private final SQLiteOpenHelper helper;
private final SQLiteDatabase db;
public Hosts(Context context, String name) {
helper = new MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_HOSTS)
.insertColumn(TABLE_HOSTS, COLUMN_HOST, String.class)
.insertColumn(TABLE_HOSTS, COLUMN_IP, String.class)
.build(context, name, DB_VERSION);
db = helper.getWritableDatabase();
}
/**
* Gets a InetAddress with the host.
*/
@Nullable
public InetAddress get(String host) {
if (!isValidHost(host)) {
return null;
public Hosts(Context context, String name) {
helper = new MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_HOSTS)
.insertColumn(TABLE_HOSTS, COLUMN_HOST, String.class)
.insertColumn(TABLE_HOSTS, COLUMN_IP, String.class)
.build(context, name, DB_VERSION);
db = helper.getWritableDatabase();
}
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[] {host});
try {
if (cursor.moveToNext()) {
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
return toInetAddress(host, ip);
} else {
return null;
}
} finally {
cursor.close();
}
}
@Nullable
public static InetAddress toInetAddress(String host, String ip) {
if (!isValidHost(host)) {
return null;
}
private boolean contains(String host) {
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[] {host});
try {
return cursor.moveToNext();
} finally {
cursor.close();
}
}
/**
* Puts the host-ip pair into this hosts.
*/
public boolean put(String host, String ip) {
if (!isValidHost(host) || !isValidIp(ip)) {
return false;
}
ContentValues values = new ContentValues();
values.put(COLUMN_HOST, host);
values.put(COLUMN_IP, ip);
if (contains(host)) {
db.update(TABLE_HOSTS, values, COLUMN_HOST + " = ?", new String[] { host });
} else {
db.insert(TABLE_HOSTS, null, values);
}
return true;
}
/**
* Puts delete the entry with the host.
*/
public void delete(String host) {
db.delete(TABLE_HOSTS, COLUMN_HOST + " = ?", new String[] { host });
}
/**
* Get all data from this host.
*/
public List<Pair<String, String>> getAll() {
List<Pair<String, String>> result = new ArrayList<>();
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + ";", null);
try {
while (cursor.moveToNext()) {
String host = SqlUtils.getString(cursor, COLUMN_HOST, null);
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
if (ip == null) {
return null;
}
InetAddress inetAddress = toInetAddress(host, ip);
if (inetAddress == null) {
continue;
byte[] bytes = parseV4(ip);
if (bytes == null) {
bytes = parseV6(ip);
}
result.add(new Pair<>(host, ip));
}
} finally {
cursor.close();
}
return result;
}
@Nullable
public static InetAddress toInetAddress(String host, String ip) {
if (!isValidHost(host)) {
return null;
}
if (ip == null) {
return null;
}
byte[] bytes = parseV4(ip);
if (bytes == null) {
bytes = parseV6(ip);
}
if (bytes == null) {
return null;
}
try {
return InetAddress.getByAddress(host, bytes);
} catch (UnknownHostException e) {
return null;
}
}
/**
* Returns true if the host is valid.
*/
public static boolean isValidHost(String host) {
if (host == null) {
return false;
}
if (host.length() > 253) {
return false;
}
int labelLength = 0;
for (int i = 0, n = host.length(); i < n; i++) {
char ch = host.charAt(i);
if (ch == '.') {
if (labelLength < 1 || labelLength > 63) {
return false;
if (bytes == null) {
return null;
}
labelLength = 0;
} else {
labelLength++;
}
if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '-' && ch != '.') {
return false;
}
}
if (labelLength < 1 || labelLength > 63) {
return false;
}
return true;
}
/**
* Returns true if the ip is valid.
*/
public static boolean isValidIp(String ip) {
return ip != null && (parseV4(ip) != null || parseV6(ip) != null);
}
// org.xbill.DNS.Address.parseV4
@Nullable
private static byte[] parseV4(String s) {
int numDigits;
int currentOctet;
byte [] values = new byte[4];
int currentValue;
int length = s.length();
currentOctet = 0;
currentValue = 0;
numDigits = 0;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
/* Can't have more than 3 digits per octet. */
if (numDigits == 3)
return null;
/* Octets shouldn't start with 0, unless they are 0. */
if (numDigits > 0 && currentValue == 0)
return null;
numDigits++;
currentValue *= 10;
currentValue += (c - '0');
/* 255 is the maximum value for an octet. */
if (currentValue > 255)
return null;
} else if (c == '.') {
/* Can't have more than 3 dots. */
if (currentOctet == 3)
return null;
/* Two consecutive dots are bad. */
if (numDigits == 0)
return null;
values[currentOctet++] = (byte) currentValue;
currentValue = 0;
numDigits = 0;
} else
return null;
}
/* Must have 4 octets. */
if (currentOctet != 3)
return null;
/* The fourth octet can't be empty. */
if (numDigits == 0)
return null;
values[currentOctet] = (byte) currentValue;
return values;
}
// org.xbill.DNS.Address.parseV6
@Nullable
private static byte[] parseV6(String s) {
int range = -1;
byte [] data = new byte[16];
String [] tokens = s.split(":", -1);
int first = 0;
int last = tokens.length - 1;
if (tokens[0].length() == 0) {
// If the first two tokens are empty, it means the string
// started with ::, which is fine. If only the first is
// empty, the string started with :, which is bad.
if (last - first > 0 && tokens[1].length() == 0)
first++;
else
return null;
}
if (tokens[last].length() == 0) {
// If the last two tokens are empty, it means the string
// ended with ::, which is fine. If only the last is
// empty, the string ended with :, which is bad.
if (last - first > 0 && tokens[last - 1].length() == 0)
last--;
else
return null;
}
if (last - first + 1 > 8)
return null;
int i, j;
for (i = first, j = 0; i <= last; i++) {
if (tokens[i].length() == 0) {
if (range >= 0)
return null;
range = j;
continue;
}
if (tokens[i].indexOf('.') >= 0) {
// An IPv4 address must be the last component
if (i < last)
return null;
// There can't have been more than 6 components.
if (i > 6)
return null;
byte [] v4addr = parseV4(tokens[i]);
if (v4addr == null)
return null;
for (int k = 0; k < 4; k++)
data[j++] = v4addr[k];
break;
}
try {
for (int k = 0; k < tokens[i].length(); k++) {
char c = tokens[i].charAt(k);
if (Character.digit(c, 16) < 0)
try {
return InetAddress.getByAddress(host, bytes);
} catch (UnknownHostException e) {
return null;
}
int x = Integer.parseInt(tokens[i], 16);
if (x > 0xFFFF || x < 0)
return null;
data[j++] = (byte)(x >>> 8);
data[j++] = (byte)(x & 0xFF);
}
catch (NumberFormatException e) {
return null;
}
}
if (j < 16 && range < 0)
return null;
/**
* Returns true if the host is valid.
*/
public static boolean isValidHost(String host) {
if (host == null) {
return false;
}
if (range >= 0) {
int empty = 16 - j;
System.arraycopy(data, range, data, range + empty, j - range);
for (i = range; i < range + empty; i++)
data[i] = 0;
if (host.length() > 253) {
return false;
}
int labelLength = 0;
for (int i = 0, n = host.length(); i < n; i++) {
char ch = host.charAt(i);
if (ch == '.') {
if (labelLength < 1 || labelLength > 63) {
return false;
}
labelLength = 0;
} else {
labelLength++;
}
if ((ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '-' && ch != '.') {
return false;
}
}
return labelLength >= 1 && labelLength <= 63;
}
return data;
}
/**
* Returns true if the ip is valid.
*/
public static boolean isValidIp(String ip) {
return ip != null && (parseV4(ip) != null || parseV6(ip) != null);
}
// org.xbill.DNS.Address.parseV4
@Nullable
private static byte[] parseV4(String s) {
int numDigits;
int currentOctet;
byte[] values = new byte[4];
int currentValue;
int length = s.length();
currentOctet = 0;
currentValue = 0;
numDigits = 0;
for (int i = 0; i < length; i++) {
char c = s.charAt(i);
if (c >= '0' && c <= '9') {
/* Can't have more than 3 digits per octet. */
if (numDigits == 3)
return null;
/* Octets shouldn't start with 0, unless they are 0. */
if (numDigits > 0 && currentValue == 0)
return null;
numDigits++;
currentValue *= 10;
currentValue += (c - '0');
/* 255 is the maximum value for an octet. */
if (currentValue > 255)
return null;
} else if (c == '.') {
/* Can't have more than 3 dots. */
if (currentOctet == 3)
return null;
/* Two consecutive dots are bad. */
if (numDigits == 0)
return null;
values[currentOctet++] = (byte) currentValue;
currentValue = 0;
numDigits = 0;
} else
return null;
}
/* Must have 4 octets. */
if (currentOctet != 3)
return null;
/* The fourth octet can't be empty. */
if (numDigits == 0)
return null;
values[currentOctet] = (byte) currentValue;
return values;
}
// org.xbill.DNS.Address.parseV6
@Nullable
private static byte[] parseV6(String s) {
int range = -1;
byte[] data = new byte[16];
String[] tokens = s.split(":", -1);
int first = 0;
int last = tokens.length - 1;
if (tokens[0].length() == 0) {
// If the first two tokens are empty, it means the string
// started with ::, which is fine. If only the first is
// empty, the string started with :, which is bad.
if (last - first > 0 && tokens[1].length() == 0)
first++;
else
return null;
}
if (tokens[last].length() == 0) {
// If the last two tokens are empty, it means the string
// ended with ::, which is fine. If only the last is
// empty, the string ended with :, which is bad.
if (last - first > 0 && tokens[last - 1].length() == 0)
last--;
else
return null;
}
if (last - first + 1 > 8)
return null;
int i, j;
for (i = first, j = 0; i <= last; i++) {
if (tokens[i].length() == 0) {
if (range >= 0)
return null;
range = j;
continue;
}
if (tokens[i].indexOf('.') >= 0) {
// An IPv4 address must be the last component
if (i < last)
return null;
// There can't have been more than 6 components.
if (i > 6)
return null;
byte[] v4addr = parseV4(tokens[i]);
if (v4addr == null)
return null;
for (int k = 0; k < 4; k++)
data[j++] = v4addr[k];
break;
}
try {
for (int k = 0; k < tokens[i].length(); k++) {
char c = tokens[i].charAt(k);
if (Character.digit(c, 16) < 0)
return null;
}
int x = Integer.parseInt(tokens[i], 16);
if (x > 0xFFFF || x < 0)
return null;
data[j++] = (byte) (x >>> 8);
data[j++] = (byte) (x & 0xFF);
} catch (NumberFormatException e) {
return null;
}
}
if (j < 16 && range < 0)
return null;
if (range >= 0) {
int empty = 16 - j;
System.arraycopy(data, range, data, range + empty, j - range);
for (i = range; i < range + empty; i++)
data[i] = 0;
}
return data;
}
/**
* Gets a InetAddress with the host.
*/
@Nullable
public InetAddress get(String host) {
if (!isValidHost(host)) {
return null;
}
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host});
try {
if (cursor.moveToNext()) {
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
return toInetAddress(host, ip);
} else {
return null;
}
} finally {
cursor.close();
}
}
private boolean contains(String host) {
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + " WHERE " + COLUMN_HOST + " = ?;", new String[]{host});
try {
return cursor.moveToNext();
} finally {
cursor.close();
}
}
/**
* Puts the host-ip pair into this hosts.
*/
public boolean put(String host, String ip) {
if (!isValidHost(host) || !isValidIp(ip)) {
return false;
}
ContentValues values = new ContentValues();
values.put(COLUMN_HOST, host);
values.put(COLUMN_IP, ip);
if (contains(host)) {
db.update(TABLE_HOSTS, values, COLUMN_HOST + " = ?", new String[]{host});
} else {
db.insert(TABLE_HOSTS, null, values);
}
return true;
}
/**
* Puts delete the entry with the host.
*/
public void delete(String host) {
db.delete(TABLE_HOSTS, COLUMN_HOST + " = ?", new String[]{host});
}
/**
* Get all data from this host.
*/
public List<Pair<String, String>> getAll() {
List<Pair<String, String>> result = new ArrayList<>();
Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_HOSTS + ";", null);
try {
while (cursor.moveToNext()) {
String host = SqlUtils.getString(cursor, COLUMN_HOST, null);
String ip = SqlUtils.getString(cursor, COLUMN_IP, null);
InetAddress inetAddress = toInetAddress(host, ip);
if (inetAddress == null) {
continue;
}
result.add(new Pair<>(host, ip));
}
} finally {
cursor.close();
}
return result;
}
}
@@ -18,9 +18,11 @@ package com.hippo.ehviewer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.conaco.ValueHelper;
import com.hippo.image.ImageBitmap;
import com.hippo.streampipe.InputStreamPipe;
import java.io.IOException;
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
+214 -284
View File
@@ -21,9 +21,11 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.FavListUrlBuilder;
@@ -36,13 +38,222 @@ import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
public class Settings {
private static final String TAG = Settings.class.getSimpleName();
/********************
****** Eh
********************/
public static final String KEY_THEME = "theme";
public static final int THEME_LIGHT = 0;
public static final int THEME_DARK = 1;
public static final int THEME_BLACK = 2;
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
public static final String KEY_GALLERY_SITE = "gallery_site";
public static final String KEY_LIST_MODE = "list_mode";
public static final String KEY_DETAIL_SIZE = "detail_size";
public static final String KEY_THUMB_SIZE = "thumb_size";
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
/********************
****** Privacy and Security
********************/
public static final String KEY_SEC_SECURITY = "enable_secure";
public static final boolean VALUE_SEC_SECURITY = false;
/********************
****** Download
********************/
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
public static final String KEY_MEDIA_SCAN = "media_scan";
public static final String KEY_IMAGE_RESOLUTION = "image_size";
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
/********************
****** Advanced
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
public static final String KEY_SECURITY = "security";
public static final String DEFAULT_SECURITY = "";
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
public static final int DEFAULT_READ_CACHE_SIZE = 160;
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts";
public static final String KEY_APP_LANGUAGE = "app_language";
private static final String TAG = Settings.class.getSimpleName();
private static final String KEY_VERSION_CODE = "version_code";
private static final int DEFAULT_VERSION_CODE = 0;
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
private static final String KEY_SHOW_WARNING = "show_warning";
private static final boolean DEFAULT_SHOW_WARNING = true;
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
private static final boolean DEFAULT_NEED_SIGN_IN = true;
private static final String KEY_SELECT_SITE = "select_site";
private static final boolean DEFAULT_SELECT_SITE = true;
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
private static final int DEFAULT_THEME = THEME_LIGHT;
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
private static final int DEFAULT_GALLERY_SITE = 1;
private static final String KEY_LAUNCH_PAGE = "launch_page";
private static final int DEFAULT_LAUNCH_PAGE = 0;
private static final int DEFAULT_LIST_MODE = 0;
private static final int DEFAULT_DETAIL_SIZE = 0;
private static final int DEFAULT_THUMB_SIZE = 1;
private static final int DEFAULT_THUMB_RESOLUTION = 0;
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
private static final boolean DEFAULT_FIX_THUMB_URL = false;
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
private static final boolean DEFAULT_SHOW_GALLERY_PAGES = false;
private static final boolean DEFAULT_SHOW_TAG_TRANSLATIONS = false;
private static final int DEFAULT_EXCLUDED_TAG_NAMESPACES = 0;
private static final String DEFAULT_EXCLUDED_LANGUAGES = null;
private static final String KEY_CELLULAR_NETWORK_WARNING = "cellular_network_warning";
private static final boolean DEFAULT_CELLULAR_NETWORK_WARNING = false;
/********************
****** Read
********************/
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private static final int DEFAULT_SCREEN_ROTATION = 0;
private static final String KEY_READING_DIRECTION = "reading_direction";
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
private static final String KEY_PAGE_SCALING = "page_scaling";
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
private static final String KEY_START_POSITION = "start_position";
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
private static final boolean DEFAULT_SHOW_CLOCK = true;
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
private static final boolean DEFAULT_SHOW_PROGRESS = true;
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
private static final boolean DEFAULT_SHOW_BATTERY = true;
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
private static final boolean VALUE_READING_FULLSCREEN = true;
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
private static final boolean DEFAULT_MEDIA_SCAN = false;
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
private static final String KEY_HAS_DEFAULT_DOWNLOAD_LABEL = "has_default_download_label";
private static final boolean DEFAULT_HAS_DOWNLOAD_LABEL = false;
private static final String KEY_DEFAULT_DOWNLOAD_LABEL = "default_download_label";
private static final String DEFAULT_DOWNLOAD_LABEL = null;
private static final String KEY_MULTI_THREAD_DOWNLOAD = "download_thread";
private static final int DEFAULT_MULTI_THREAD_DOWNLOAD = 3;
private static final String KEY_PRELOAD_IMAGE = "preload_image";
private static final int DEFAULT_PRELOAD_IMAGE = 5;
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
/********************
****** Favorites
********************/
private static final String KEY_FAV_CAT_0 = "fav_cat_0";
private static final String KEY_FAV_CAT_1 = "fav_cat_1";
private static final String KEY_FAV_CAT_2 = "fav_cat_2";
private static final String KEY_FAV_CAT_3 = "fav_cat_3";
private static final String KEY_FAV_CAT_4 = "fav_cat_4";
private static final String KEY_FAV_CAT_5 = "fav_cat_5";
private static final String KEY_FAV_CAT_6 = "fav_cat_6";
private static final String KEY_FAV_CAT_7 = "fav_cat_7";
private static final String KEY_FAV_CAT_8 = "fav_cat_8";
private static final String KEY_FAV_CAT_9 = "fav_cat_9";
private static final String DEFAULT_FAV_CAT_0 = "Favorites 0";
private static final String DEFAULT_FAV_CAT_1 = "Favorites 1";
private static final String DEFAULT_FAV_CAT_2 = "Favorites 2";
private static final String DEFAULT_FAV_CAT_3 = "Favorites 3";
private static final String DEFAULT_FAV_CAT_4 = "Favorites 4";
private static final String DEFAULT_FAV_CAT_5 = "Favorites 5";
private static final String DEFAULT_FAV_CAT_6 = "Favorites 6";
private static final String DEFAULT_FAV_CAT_7 = "Favorites 7";
private static final String DEFAULT_FAV_CAT_8 = "Favorites 8";
private static final String DEFAULT_FAV_CAT_9 = "Favorites 9";
private static final String KEY_FAV_COUNT_0 = "fav_count_0";
private static final String KEY_FAV_COUNT_1 = "fav_count_1";
private static final String KEY_FAV_COUNT_2 = "fav_count_2";
private static final String KEY_FAV_COUNT_3 = "fav_count_3";
private static final String KEY_FAV_COUNT_4 = "fav_count_4";
private static final String KEY_FAV_COUNT_5 = "fav_count_5";
private static final String KEY_FAV_COUNT_6 = "fav_count_6";
private static final String KEY_FAV_COUNT_7 = "fav_count_7";
private static final String KEY_FAV_COUNT_8 = "fav_count_8";
private static final String KEY_FAV_COUNT_9 = "fav_count_9";
private static final String KEY_FAV_LOCAL = "fav_local";
private static final String KEY_FAV_CLOUD = "fav_cloud";
private static final int DEFAULT_FAV_COUNT = 0;
private static final String KEY_RECENT_FAV_CAT = "recent_fav_cat";
private static final int DEFAULT_RECENT_FAV_CAT = FavListUrlBuilder.FAV_CAT_ALL;
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
/********************
****** Analytics
********************/
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
private static final boolean DEFAULT_ASK_ANALYTICS = true;
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
private static final String KEY_USER_ID = "user_id";
private static final String FILENAME_USER_ID = ".user_id";
private static final int LENGTH_USER_ID = 32;
/********************
****** Update
********************/
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
private static final boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = EhApplication.BETA;
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
private static final boolean DEFAULT_SAVE_CRASH_LOG = false;
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
private static final String DEFAULT_APP_LANGUAGE = "system";
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
private static final String KEY_PROXY_IP = "proxy_ip";
private static final String DEFAULT_PROXY_IP = null;
private static final String KEY_PROXY_PORT = "proxy_port";
private static final int DEFAULT_PROXY_PORT = -1;
/********************
****** Guide
********************/
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
private static final boolean DEFAULT_GUIDE_GALLERY = true;
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
private static Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
@@ -64,7 +275,7 @@ public class Settings {
}
private static EhConfig loadEhConfig() {
EhConfig ehConfig= new EhConfig();
EhConfig ehConfig = new EhConfig();
ehConfig.imageSize = getImageResolution();
ehConfig.excludedLanguages = getExcludedLanguages();
ehConfig.defaultCategories = getDefaultCategories();
@@ -151,9 +362,6 @@ public class Settings {
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
}
private static final String KEY_VERSION_CODE = "version_code";
private static final int DEFAULT_VERSION_CODE = 0;
public static int getVersionCode() {
return getInt(KEY_VERSION_CODE, DEFAULT_VERSION_CODE);
}
@@ -162,9 +370,6 @@ public class Settings {
putInt(KEY_VERSION_CODE, value);
}
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
public static String getDisplayName() {
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
}
@@ -173,9 +378,6 @@ public class Settings {
putString(KEY_DISPLAY_NAME, value);
}
private static final String KEY_AVATAR = "avatar";
private static final String DEFAULT_AVATAR = null;
public static String getAvatar() {
return getString(KEY_AVATAR, DEFAULT_AVATAR);
}
@@ -184,9 +386,6 @@ public class Settings {
putString(KEY_AVATAR, value);
}
private static final String KEY_SHOW_WARNING = "show_warning";
private static final boolean DEFAULT_SHOW_WARNING = true;
public static boolean getShowWarning() {
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
}
@@ -195,9 +394,6 @@ public class Settings {
putBoolean(KEY_SHOW_WARNING, value);
}
private static final String KEY_REMOVE_IMAGE_FILES = "include_pic";
private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true;
public static boolean getRemoveImageFiles() {
return getBoolean(KEY_REMOVE_IMAGE_FILES, DEFAULT_REMOVE_IMAGE_FILES);
}
@@ -210,9 +406,6 @@ public class Settings {
return sEhConfig;
}
private static final String KEY_NEED_SIGN_IN = "need_sign_in";
private static final boolean DEFAULT_NEED_SIGN_IN = true;
public static boolean getNeedSignIn() {
return getBoolean(KEY_NEED_SIGN_IN, DEFAULT_NEED_SIGN_IN);
}
@@ -221,9 +414,6 @@ public class Settings {
putBoolean(KEY_NEED_SIGN_IN, value);
}
private static final String KEY_SELECT_SITE = "select_site";
private static final boolean DEFAULT_SELECT_SITE = true;
public static boolean getSelectSite() {
return getBoolean(KEY_SELECT_SITE, DEFAULT_SELECT_SITE);
}
@@ -232,9 +422,6 @@ public class Settings {
putBoolean(KEY_SELECT_SITE, value);
}
private static final String KEY_QUICK_SEARCH_TIP = "quick_search_tip";
private static final boolean DEFAULT_QUICK_SEARCH_TIP = true;
public static boolean getQuickSearchTip() {
return getBoolean(KEY_QUICK_SEARCH_TIP, DEFAULT_QUICK_SEARCH_TIP);
}
@@ -243,16 +430,6 @@ public class Settings {
putBoolean(KEY_QUICK_SEARCH_TIP, value);
}
/********************
****** Eh
********************/
public static final String KEY_THEME = "theme";
public static final int THEME_LIGHT = 0;
public static final int THEME_DARK = 1;
public static final int THEME_BLACK = 2;
private static final int DEFAULT_THEME = THEME_LIGHT;
public static int getTheme() {
return getIntFromStr(KEY_THEME, DEFAULT_THEME);
}
@@ -261,16 +438,10 @@ public class Settings {
putIntToStr(KEY_THEME, theme);
}
public static final String KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
private static final boolean DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
public static boolean getApplyNavBarThemeColor() {
return getBoolean(KEY_APPLY_NAV_BAR_THEME_COLOR, DEFAULT_APPLY_NAV_BAR_THEME_COLOR);
}
public static final String KEY_GALLERY_SITE = "gallery_site";
private static final int DEFAULT_GALLERY_SITE = 1;
public static int getGallerySite() {
return getIntFromStr(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE);
}
@@ -279,9 +450,6 @@ public class Settings {
putIntToStr(KEY_GALLERY_SITE, value);
}
private static final String KEY_LAUNCH_PAGE = "launch_page";
private static final int DEFAULT_LAUNCH_PAGE = 0;
public static String getLaunchPageGalleryListSceneAction() {
int value = getIntFromStr(KEY_LAUNCH_PAGE, DEFAULT_LAUNCH_PAGE);
switch (value) {
@@ -295,16 +463,10 @@ public class Settings {
}
}
public static final String KEY_LIST_MODE = "list_mode";
private static final int DEFAULT_LIST_MODE = 0;
public static int getListMode() {
return getIntFromStr(KEY_LIST_MODE, DEFAULT_LIST_MODE);
}
public static final String KEY_DETAIL_SIZE = "detail_size";
private static final int DEFAULT_DETAIL_SIZE = 0;
public static int getDetailSize() {
return getIntFromStr(KEY_DETAIL_SIZE, DEFAULT_DETAIL_SIZE);
}
@@ -320,9 +482,6 @@ public class Settings {
}
}
public static final String KEY_THUMB_SIZE = "thumb_size";
private static final int DEFAULT_THUMB_SIZE = 1;
public static int getThumbSize() {
return getIntFromStr(KEY_THUMB_SIZE, DEFAULT_THUMB_SIZE);
}
@@ -340,44 +499,26 @@ public class Settings {
}
}
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
private static final int DEFAULT_THUMB_RESOLUTION = 0;
public static int getThumbResolution() {
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
}
private static final String KEY_FIX_THUMB_URL = "fix_thumb_url";
private static final boolean DEFAULT_FIX_THUMB_URL = false;
public static boolean getFixThumbUrl() {
return getBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL);
}
private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title";
private static final boolean DEFAULT_SHOW_JPN_TITLE = false;
public static boolean getShowJpnTitle() {
return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE);
}
private static final String KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
private static final boolean DEFAULT_SHOW_GALLERY_PAGES = false;
public static boolean getShowGalleryPages() {
return getBoolean(KEY_SHOW_GALLERY_PAGES, DEFAULT_SHOW_GALLERY_PAGES);
}
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
private static final boolean DEFAULT_SHOW_TAG_TRANSLATIONS = false;
public static boolean getShowTagTranslations() {
return getBoolean(KEY_SHOW_TAG_TRANSLATIONS, DEFAULT_SHOW_TAG_TRANSLATIONS);
}
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
public static int getDefaultCategories() {
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
}
@@ -388,9 +529,6 @@ public class Settings {
putInt(KEY_DEFAULT_CATEGORIES, value);
}
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
private static final int DEFAULT_EXCLUDED_TAG_NAMESPACES = 0;
public static int getExcludedTagNamespaces() {
return getInt(KEY_EXCLUDED_TAG_NAMESPACES, DEFAULT_EXCLUDED_TAG_NAMESPACES);
}
@@ -401,9 +539,6 @@ public class Settings {
putInt(KEY_EXCLUDED_TAG_NAMESPACES, value);
}
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
private static final String DEFAULT_EXCLUDED_LANGUAGES = null;
public static String getExcludedLanguages() {
return getString(KEY_EXCLUDED_LANGUAGES, DEFAULT_EXCLUDED_LANGUAGES);
}
@@ -414,19 +549,10 @@ public class Settings {
putString(KEY_EXCLUDED_LANGUAGES, value);
}
private static final String KEY_CELLULAR_NETWORK_WARNING = "cellular_network_warning";
private static final boolean DEFAULT_CELLULAR_NETWORK_WARNING = false;
public static boolean getCellularNetworkWarning() {
return getBoolean(KEY_CELLULAR_NETWORK_WARNING, DEFAULT_CELLULAR_NETWORK_WARNING);
}
/********************
****** Read
********************/
private static final String KEY_SCREEN_ROTATION = "screen_rotation";
private static final int DEFAULT_SCREEN_ROTATION = 0;
public static int getScreenRotation() {
return getIntFromStr(KEY_SCREEN_ROTATION, DEFAULT_SCREEN_ROTATION);
}
@@ -435,9 +561,6 @@ public class Settings {
putIntToStr(KEY_SCREEN_ROTATION, value);
}
private static final String KEY_READING_DIRECTION = "reading_direction";
private static final int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
@GalleryView.LayoutMode
public static int getReadingDirection() {
return GalleryView.sanitizeLayoutMode(getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION));
@@ -447,9 +570,6 @@ public class Settings {
putIntToStr(KEY_READING_DIRECTION, value);
}
private static final String KEY_PAGE_SCALING = "page_scaling";
private static final int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
@GalleryView.ScaleMode
public static int getPageScaling() {
return GalleryView.sanitizeScaleMode(getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING));
@@ -459,9 +579,6 @@ public class Settings {
putIntToStr(KEY_PAGE_SCALING, value);
}
private static final String KEY_START_POSITION = "start_position";
private static final int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
@GalleryView.StartPosition
public static int getStartPosition() {
return GalleryView.sanitizeStartPosition(getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION));
@@ -471,9 +588,6 @@ public class Settings {
putIntToStr(KEY_START_POSITION, value);
}
private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on";
private static final boolean DEFAULT_KEEP_SCREEN_ON = false;
public static boolean getKeepScreenOn() {
return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON);
}
@@ -482,9 +596,6 @@ public class Settings {
putBoolean(KEY_KEEP_SCREEN_ON, value);
}
private static final String KEY_SHOW_CLOCK = "gallery_show_clock";
private static final boolean DEFAULT_SHOW_CLOCK = true;
public static boolean getShowClock() {
return getBoolean(KEY_SHOW_CLOCK, DEFAULT_SHOW_CLOCK);
}
@@ -493,9 +604,6 @@ public class Settings {
putBoolean(KEY_SHOW_CLOCK, value);
}
private static final String KEY_SHOW_PROGRESS = "gallery_show_progress";
private static final boolean DEFAULT_SHOW_PROGRESS = true;
public static boolean getShowProgress() {
return getBoolean(KEY_SHOW_PROGRESS, DEFAULT_SHOW_PROGRESS);
}
@@ -504,9 +612,6 @@ public class Settings {
putBoolean(KEY_SHOW_PROGRESS, value);
}
private static final String KEY_SHOW_BATTERY = "gallery_show_battery";
private static final boolean DEFAULT_SHOW_BATTERY = true;
public static boolean getShowBattery() {
return getBoolean(KEY_SHOW_BATTERY, DEFAULT_SHOW_BATTERY);
}
@@ -515,9 +620,6 @@ public class Settings {
putBoolean(KEY_SHOW_BATTERY, value);
}
private static final String KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
private static final boolean DEFAULT_SHOW_PAGE_INTERVAL = true;
public static boolean getShowPageInterval() {
return getBoolean(KEY_SHOW_PAGE_INTERVAL, DEFAULT_SHOW_PAGE_INTERVAL);
}
@@ -526,9 +628,6 @@ public class Settings {
putBoolean(KEY_SHOW_PAGE_INTERVAL, value);
}
private static final String KEY_VOLUME_PAGE = "volume_page";
private static final boolean DEFAULT_VOLUME_PAGE = false;
public static boolean getVolumePage() {
return getBoolean(KEY_VOLUME_PAGE, DEFAULT_VOLUME_PAGE);
}
@@ -537,9 +636,6 @@ public class Settings {
putBoolean(KEY_VOLUME_PAGE, value);
}
private static final String KEY_READING_FULLSCREEN = "reading_fullscreen";
private static final boolean VALUE_READING_FULLSCREEN = true;
public static boolean getReadingFullscreen() {
return getBoolean(KEY_READING_FULLSCREEN, VALUE_READING_FULLSCREEN);
}
@@ -548,9 +644,6 @@ public class Settings {
putBoolean(KEY_READING_FULLSCREEN, value);
}
private static final String KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
private static final boolean DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
public static boolean getCustomScreenLightness() {
return getBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, DEFAULT_CUSTOM_SCREEN_LIGHTNESS);
}
@@ -559,9 +652,6 @@ public class Settings {
putBoolean(KEY_CUSTOM_SCREEN_LIGHTNESS, value);
}
private static final String KEY_SCREEN_LIGHTNESS = "screen_lightness";
private static final int DEFAULT_SCREEN_LIGHTNESS = 50;
public static int getScreenLightness() {
return getInt(KEY_SCREEN_LIGHTNESS, DEFAULT_SCREEN_LIGHTNESS);
}
@@ -570,28 +660,14 @@ public class Settings {
putInt(KEY_SCREEN_LIGHTNESS, value);
}
/********************
****** Privacy and Security
********************/
public static final String KEY_SEC_SECURITY = "enable_secure";
public static final boolean VALUE_SEC_SECURITY = false;
public static boolean getEnabledSecurity() {
return getBoolean(KEY_SEC_SECURITY, VALUE_SEC_SECURITY);
}
public static void putEnabledSecurity(boolean value) {
putBoolean(KEY_READING_FULLSCREEN, value);
}
/********************
****** Download
********************/
public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path";
public static final String KEY_DOWNLOAD_SAVE_QUERY = "image_query";
public static final String KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
@Nullable
public static UniFile getDownloadLocation() {
UniFile dir = null;
@@ -625,16 +701,10 @@ public class Settings {
}
}
public static final String KEY_MEDIA_SCAN = "media_scan";
private static final boolean DEFAULT_MEDIA_SCAN = false;
public static boolean getMediaScan() {
return getBoolean(KEY_MEDIA_SCAN, DEFAULT_MEDIA_SCAN);
}
private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null;
public static String getRecentDownloadLabel() {
return getString(KEY_RECENT_DOWNLOAD_LABEL, DEFAULT_RECENT_DOWNLOAD_LABEL);
}
@@ -643,9 +713,6 @@ public class Settings {
putString(KEY_RECENT_DOWNLOAD_LABEL, value);
}
private static final String KEY_HAS_DEFAULT_DOWNLOAD_LABEL = "has_default_download_label";
private static final boolean DEFAULT_HAS_DOWNLOAD_LABEL = false;
public static boolean getHasDefaultDownloadLabel() {
return getBoolean(KEY_HAS_DEFAULT_DOWNLOAD_LABEL, DEFAULT_HAS_DOWNLOAD_LABEL);
}
@@ -654,9 +721,6 @@ public class Settings {
putBoolean(KEY_HAS_DEFAULT_DOWNLOAD_LABEL, hasDefaultDownloadLabel);
}
private static final String KEY_DEFAULT_DOWNLOAD_LABEL = "default_download_label";
private static final String DEFAULT_DOWNLOAD_LABEL = null;
public static String getDefaultDownloadLabel() {
return getString(KEY_DEFAULT_DOWNLOAD_LABEL, DEFAULT_DOWNLOAD_LABEL);
}
@@ -665,9 +729,6 @@ public class Settings {
putString(KEY_DEFAULT_DOWNLOAD_LABEL, value);
}
private static final String KEY_MULTI_THREAD_DOWNLOAD = "download_thread";
private static final int DEFAULT_MULTI_THREAD_DOWNLOAD = 3;
public static int getMultiThreadDownload() {
return getIntFromStr(KEY_MULTI_THREAD_DOWNLOAD, DEFAULT_MULTI_THREAD_DOWNLOAD);
}
@@ -676,9 +737,6 @@ public class Settings {
putIntToStr(KEY_MULTI_THREAD_DOWNLOAD, value);
}
private static final String KEY_PRELOAD_IMAGE = "preload_image";
private static final int DEFAULT_PRELOAD_IMAGE = 5;
public static int getPreloadImage() {
return getIntFromStr(KEY_PRELOAD_IMAGE, DEFAULT_PRELOAD_IMAGE);
}
@@ -687,9 +745,6 @@ public class Settings {
putIntToStr(KEY_PRELOAD_IMAGE, value);
}
public static final String KEY_IMAGE_RESOLUTION = "image_size";
public static final String DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
public static String getImageResolution() {
return getString(KEY_IMAGE_RESOLUTION, DEFAULT_IMAGE_RESOLUTION);
}
@@ -700,9 +755,6 @@ public class Settings {
putString(KEY_IMAGE_RESOLUTION, value);
}
private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
public static boolean getDownloadOriginImage() {
return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE);
}
@@ -711,46 +763,6 @@ public class Settings {
putBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, value);
}
/********************
****** Favorites
********************/
private static final String KEY_FAV_CAT_0 = "fav_cat_0";
private static final String KEY_FAV_CAT_1 = "fav_cat_1";
private static final String KEY_FAV_CAT_2 = "fav_cat_2";
private static final String KEY_FAV_CAT_3 = "fav_cat_3";
private static final String KEY_FAV_CAT_4 = "fav_cat_4";
private static final String KEY_FAV_CAT_5 = "fav_cat_5";
private static final String KEY_FAV_CAT_6 = "fav_cat_6";
private static final String KEY_FAV_CAT_7 = "fav_cat_7";
private static final String KEY_FAV_CAT_8 = "fav_cat_8";
private static final String KEY_FAV_CAT_9 = "fav_cat_9";
private static final String DEFAULT_FAV_CAT_0 = "Favorites 0";
private static final String DEFAULT_FAV_CAT_1 = "Favorites 1";
private static final String DEFAULT_FAV_CAT_2 = "Favorites 2";
private static final String DEFAULT_FAV_CAT_3 = "Favorites 3";
private static final String DEFAULT_FAV_CAT_4 = "Favorites 4";
private static final String DEFAULT_FAV_CAT_5 = "Favorites 5";
private static final String DEFAULT_FAV_CAT_6 = "Favorites 6";
private static final String DEFAULT_FAV_CAT_7 = "Favorites 7";
private static final String DEFAULT_FAV_CAT_8 = "Favorites 8";
private static final String DEFAULT_FAV_CAT_9 = "Favorites 9";
private static final String KEY_FAV_COUNT_0 = "fav_count_0";
private static final String KEY_FAV_COUNT_1 = "fav_count_1";
private static final String KEY_FAV_COUNT_2 = "fav_count_2";
private static final String KEY_FAV_COUNT_3 = "fav_count_3";
private static final String KEY_FAV_COUNT_4 = "fav_count_4";
private static final String KEY_FAV_COUNT_5 = "fav_count_5";
private static final String KEY_FAV_COUNT_6 = "fav_count_6";
private static final String KEY_FAV_COUNT_7 = "fav_count_7";
private static final String KEY_FAV_COUNT_8 = "fav_count_8";
private static final String KEY_FAV_COUNT_9 = "fav_count_9";
private static final String KEY_FAV_LOCAL = "fav_local";
private static final String KEY_FAV_CLOUD = "fav_cloud";
private static final int DEFAULT_FAV_COUNT = 0;
public static String[] getFavCat() {
String[] favCat = new String[10];
favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0);
@@ -829,9 +841,6 @@ public class Settings {
sSettingsPre.edit().putInt(KEY_FAV_CLOUD, count).apply();
}
private static final String KEY_RECENT_FAV_CAT = "recent_fav_cat";
private static final int DEFAULT_RECENT_FAV_CAT = FavListUrlBuilder.FAV_CAT_ALL;
public static int getRecentFavCat() {
return getInt(KEY_RECENT_FAV_CAT, DEFAULT_RECENT_FAV_CAT);
}
@@ -840,11 +849,6 @@ public class Settings {
putInt(KEY_RECENT_FAV_CAT, value);
}
// -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
private static final String KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
public static final int INVALID_DEFAULT_FAV_SLOT = -2;
private static final int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
public static int getDefaultFavSlot() {
return getInt(KEY_DEFAULT_FAV_SLOT, DEFAULT_DEFAULT_FAV_SLOT);
}
@@ -853,12 +857,6 @@ public class Settings {
putInt(KEY_DEFAULT_FAV_SLOT, value);
}
/********************
****** Analytics
********************/
private static final String KEY_ASK_ANALYTICS = "ask_analytics";
private static final boolean DEFAULT_ASK_ANALYTICS = true;
public static boolean getAskAnalytics() {
return getBoolean(KEY_ASK_ANALYTICS, DEFAULT_ASK_ANALYTICS);
}
@@ -867,9 +865,6 @@ public class Settings {
putBoolean(KEY_ASK_ANALYTICS, value);
}
public static final String KEY_ENABLE_ANALYTICS = "enable_analytics";
private static final boolean DEFAULT_ENABLE_ANALYTICS = false;
public static boolean getEnableAnalytics() {
return getBoolean(KEY_ENABLE_ANALYTICS, DEFAULT_ENABLE_ANALYTICS);
}
@@ -878,10 +873,6 @@ public class Settings {
putBoolean(KEY_ENABLE_ANALYTICS, value);
}
private static final String KEY_USER_ID = "user_id";
private static final String FILENAME_USER_ID = ".user_id";
private static final int LENGTH_USER_ID = 32;
public static String getUserID() {
boolean writeXml = false;
boolean writeFile = false;
@@ -940,14 +931,6 @@ public class Settings {
return true;
}
/********************
****** Update
********************/
private static final String KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
private static final boolean DEFAULT_BETA_UPDATE_CHANNEL = EhApplication.BETA;
private static final String KEY_SKIP_UPDATE_VERSION = "skip_update_version";
private static final int DEFAULT_SKIP_UPDATE_VERSION = 0;
public static boolean getBetaUpdateChannel() {
return getBoolean(KEY_BETA_UPDATE_CHANNEL, DEFAULT_BETA_UPDATE_CHANNEL);
}
@@ -964,12 +947,6 @@ public class Settings {
putInt(KEY_SKIP_UPDATE_VERSION, value);
}
/********************
****** Advanced
********************/
public static final String KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
private static final boolean DEFAULT_SAVE_PARSE_ERROR_BODY = EhApplication.BETA;
public static boolean getSaveParseErrorBody() {
return getBoolean(KEY_SAVE_PARSE_ERROR_BODY, DEFAULT_SAVE_PARSE_ERROR_BODY);
}
@@ -978,16 +955,10 @@ public class Settings {
putBoolean(KEY_SAVE_PARSE_ERROR_BODY, value);
}
private static final String KEY_SAVE_CRASH_LOG = "save_crash_log";
private static final boolean DEFAULT_SAVE_CRASH_LOG = false;
public static boolean getSaveCrashLog() {
return getBoolean(KEY_SAVE_CRASH_LOG, DEFAULT_SAVE_CRASH_LOG);
}
public static final String KEY_SECURITY = "security";
public static final String DEFAULT_SECURITY = "";
public static String getSecurity() {
return getString(KEY_SECURITY, DEFAULT_SECURITY);
}
@@ -996,8 +967,6 @@ public class Settings {
putString(KEY_SECURITY, value);
}
public static final String KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
public static boolean getEnableFingerprint() {
return getBoolean(KEY_ENABLE_FINGERPRINT, false);
}
@@ -1006,16 +975,10 @@ public class Settings {
putBoolean(KEY_ENABLE_FINGERPRINT, value);
}
public static final String KEY_READ_CACHE_SIZE = "read_cache_size";
public static final int DEFAULT_READ_CACHE_SIZE = 160;
public static int getReadCacheSize() {
return getIntFromStr(KEY_READ_CACHE_SIZE, DEFAULT_READ_CACHE_SIZE);
}
public static final String KEY_BUILT_IN_HOSTS = "built_in_hosts";
private static final boolean DEFAULT_BUILT_IN_HOSTS = false;
public static boolean getBuiltInHosts() {
return getBoolean(KEY_BUILT_IN_HOSTS, DEFAULT_BUILT_IN_HOSTS);
}
@@ -1024,9 +987,6 @@ public class Settings {
putBoolean(KEY_BUILT_IN_HOSTS, value);
}
public static final String KEY_APP_LANGUAGE = "app_language";
private static final String DEFAULT_APP_LANGUAGE = "system";
public static String getAppLanguage() {
return getString(KEY_APP_LANGUAGE, DEFAULT_APP_LANGUAGE);
}
@@ -1035,9 +995,6 @@ public class Settings {
putString(KEY_APP_LANGUAGE, value);
}
private static final String KEY_PROXY_TYPE = "proxy_type";
private static final int DEFAULT_PROXY_TYPE = EhProxySelector.TYPE_SYSTEM;
public static int getProxyType() {
return getInt(KEY_PROXY_TYPE, DEFAULT_PROXY_TYPE);
}
@@ -1046,9 +1003,6 @@ public class Settings {
putInt(KEY_PROXY_TYPE, value);
}
private static final String KEY_PROXY_IP = "proxy_ip";
private static final String DEFAULT_PROXY_IP = null;
public static String getProxyIp() {
return getString(KEY_PROXY_IP, DEFAULT_PROXY_IP);
}
@@ -1057,9 +1011,6 @@ public class Settings {
putString(KEY_PROXY_IP, value);
}
private static final String KEY_PROXY_PORT = "proxy_port";
private static final int DEFAULT_PROXY_PORT = -1;
public static int getProxyPort() {
return getInt(KEY_PROXY_PORT, DEFAULT_PROXY_PORT);
}
@@ -1068,12 +1019,6 @@ public class Settings {
putInt(KEY_PROXY_PORT, value);
}
/********************
****** Guide
********************/
private static final String KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
private static final boolean DEFAULT_GUIDE_QUICK_SEARCH = true;
public static boolean getGuideQuickSearch() {
return getBoolean(KEY_GUIDE_QUICK_SEARCH, DEFAULT_GUIDE_QUICK_SEARCH);
}
@@ -1082,9 +1027,6 @@ public class Settings {
putBoolean(KEY_GUIDE_QUICK_SEARCH, value);
}
private static final String KEY_GUIDE_COLLECTIONS = "guide_collections";
private static final boolean DEFAULT_GUIDE_COLLECTIONS = true;
public static boolean getGuideCollections() {
return getBoolean(KEY_GUIDE_COLLECTIONS, DEFAULT_GUIDE_COLLECTIONS);
}
@@ -1093,9 +1035,6 @@ public class Settings {
putBoolean(KEY_GUIDE_COLLECTIONS, value);
}
private static final String KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
public static boolean getGuideDownloadThumb() {
return getBoolean(KEY_GUIDE_DOWNLOAD_THUMB, DEFAULT_GUIDE_DOWNLOAD_THUMB);
}
@@ -1104,9 +1043,6 @@ public class Settings {
putBoolean(KEY_GUIDE_DOWNLOAD_THUMB, value);
}
private static final String KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
private static final boolean DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
public static boolean getGuideDownloadLabels() {
return getBoolean(KEY_GUIDE_DOWNLOAD_LABELS, DEFAULT_GUIDE_DOWNLOAD_LABELS);
}
@@ -1115,9 +1051,6 @@ public class Settings {
putBoolean(KEY_GUIDE_DOWNLOAD_LABELS, value);
}
private static final String KEY_GUIDE_GALLERY = "guide_gallery";
private static final boolean DEFAULT_GUIDE_GALLERY = true;
public static boolean getGuideGallery() {
return getBoolean(KEY_GUIDE_GALLERY, DEFAULT_GUIDE_GALLERY);
}
@@ -1126,9 +1059,6 @@ public class Settings {
putBoolean(KEY_GUIDE_GALLERY, value);
}
private static final String KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
private static final int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
public static int getClipboardTextHashCode() {
return getInt(KEY_CLIPBOARD_TEXT_HASH_CODE, DEFAULT_CLIPBOARD_TEXT_HASH_CODE);
}
@@ -22,7 +22,9 @@ import android.net.Uri;
import android.provider.Browser;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
@@ -18,15 +18,18 @@ package com.hippo.ehviewer.client;
import android.content.Context;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.exception.CancelledException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Call;
import okhttp3.OkHttpClient;
@@ -70,14 +73,22 @@ public class EhClient {
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
public class Task extends AsyncTask<Object, Void, Object> {
private final int mMethod;
private Callback mCallback;
private EhConfig mEhConfig;
private final AtomicReference<Call> mCall = new AtomicReference<>();
private final AtomicBoolean mStop = new AtomicBoolean();
private Callback mCallback;
private EhConfig mEhConfig;
public Task(int method, Callback callback, EhConfig ehConfig) {
mMethod = method;
@@ -202,13 +213,4 @@ public class EhClient {
mCall.lazySet(null);
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
}
@@ -27,180 +27,14 @@ public class EhConfig implements Cloneable {
* The Cookie key of uconfig
*/
public static final String KEY_UCONFIG = "uconfig";
/**
* The key of load images through the Hentai@Home Network
* @see #LOAD_FROM_HAH_YES
* @see #LOAD_FROM_HAH_NO
*/
private static final String KEY_LOAD_FROM_HAH = "uh";
/**
* The key of Image Size Settings
* @see #IMAGE_SIZE_AUTO
* @see #IMAGE_SIZE_780X
* @see #IMAGE_SIZE_980X
* @see #IMAGE_SIZE_1280X
* @see #IMAGE_SIZE_1600X
* @see #IMAGE_SIZE_2400X
*/
private static final String KEY_IMAGE_SIZE = "xr";
/**
* The key of scale images width
*/
private static final String KEY_SCALE_WIDTH = "rx";
/**
* The key of scale images height
*/
private static final String KEY_SCALE_HEIGHT = "ry";
/**
* The key of Gallery Name Display
* @see #GALLERY_TITLE_DEFAULT
* @see #GALLERY_TITLE_JAPANESE
*/
private static final String KEY_GALLERY_TITLE = "tl";
/**
* The key of the behavior for downloading archiver
* @see #ARCHIVER_DOWNLOAD_MAMS
* @see #ARCHIVER_DOWNLOAD_MAAS
* @see #ARCHIVER_DOWNLOAD_AAMS
* @see #ARCHIVER_DOWNLOAD_AAAS
*/
private static final String KEY_ARCHIVER_DOWNLOAD = "ar";
/**
* The key of display mode would you like to use on the front and search pages
* @see #LAYOUT_MODE_LIST
* @see #LAYOUT_MODE_THUMB
*/
private static final String KEY_LAYOUT_MODE = "dm";
/**
* The key for show popular
* @see #POPULAR_YES
* @see #POPULAR_NO
*/
private static final String KEY_POPULAR = "prn";
/**
* The key of categories would you like to view as default on the front page
*/
private static final String KEY_DEFAULT_CATEGORIES = "cats";
/**
* The key for favorites sort
* @see #FAVORITES_SORT_GALLERY_UPDATE_TIME
* @see #FAVORITES_SORT_FAVORITED_TIME
*/
private static final String KEY_FAVORITES_SORT = "fs";
/**
* The key of exclude certain namespaces from a default tag search
*/
private static final String KEY_EXCLUDED_NAMESPACES = "xns";
/**
* The key of hide galleries in certain languages from the gallery list and searches
*/
private static final String KEY_EXCLUDED_LANGUAGES = "xl";
/**
* The key of how many results would you like per page for the index/search page and torrent search pages
* @see #RESULT_COUNT_25
* @see #RESULT_COUNT_50
* @see #RESULT_COUNT_100
* @see #RESULT_COUNT_200
*/
private static final String KEY_RESULT_COUNT = "rc";
/**
* The key of mouse-over thumb
* @see #MOUSE_OVER_YES
* @see #MOUSE_OVER_NO
*/
private static final String KEY_MOUSE_OVER = "lt";
/**
* The key of preview size
* @see #PREVIEW_SIZE_NORMAL
* @see #PREVIEW_SIZE_LARGE
*/
private static final String KEY_PREVIEW_SIZE = "ts";
/**
* The key of preview row per page
* @see #PREVIEW_ROW_4
* @see #PREVIEW_ROW_10
* @see #PREVIEW_ROW_20
* @see #PREVIEW_ROW_40
*/
private static final String KEY_PREVIEW_ROW = "tr";
/**
* The key of sort order for gallery comments
* @see #COMMENTS_SORT_OLDEST_FIRST
* @see #COMMENTS_SORT_RECENT_FIRST
* @see #COMMENTS_SORT_HIGHEST_SCORE_FIRST
*/
private static final String KEY_COMMENTS_SORT = "cs";
/**
* The key of show gallery comment votes
* @see #COMMENTS_VOTES_POP
* @see #COMMENTS_VOTES_ALWAYS
*/
private static final String KEY_COMMENTS_VOTES = "sc";
/**
* The key of sort order for gallery tags
* @see #TAGS_SORT_ALPHABETICAL
* @see #TAGS_SORT_POWER
*/
private static final String KEY_TAGS_SORT = "to";
/**
* The key of show gallery page numbers
* @see #SHOW_GALLERY_INDEX_NO
* @see #SHOW_GALLERY_INDEX_YES
*/
private static final String KEY_SHOW_GALLERY_INDEX = "pn";
/**
* The key of the IP:Port of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_IP_PORT = "hp";
/**
* The key of the passkey of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_PASSKEY = "hk";
/**
* The key of enable Tag Flagging
* @see #ENABLE_TAG_FLAGGING_NO
* @see #ENABLE_TAG_FLAGGING_YES
*/
private static final String KEY_ENABLE_TAG_FLAGGING = "tf";
/**
* The key of always display the original images instead of the resampled versions
* @see #ALWAYS_ORIGINAL_NO
* @see #ALWAYS_ORIGINAL_YES
*/
private static final String KEY_ALWAYS_ORIGINAL = "oi";
/**
* The key of enable the multi-Page Viewer
* @see #MULTI_PAGE_NO
* @see #MULTI_PAGE_YES
*/
private static final String KEY_MULTI_PAGE = "qb";
/**
* The key of multi-Page Viewer Display Style
* @see #MULTI_PAGE_STYLE_C
* @see #MULTI_PAGE_STYLE_N
* @see #MULTI_PAGE_STYLE_Y
*/
private static final String KEY_MULTI_PAGE_STYLE = "ms";
/**
* The key of multi-Page Viewer Thumbnail Pane
* @see #MULTI_PAGE_THUMB_HIDE
* @see #MULTI_PAGE_THUMB_SHOW
*/
private static final String KEY_MULTI_PAGE_THUMB = "mt";
/**
* The Cookie key of lofi resolution
*/
public static final String KEY_LOFI_RESOLUTION = "xres";
/**
* The Cookie key of show warning
*/
public static final String KEY_CONTENT_WARNING = "nw";
/**
* load images through the Hentai@Home Network
*/
@@ -209,7 +43,6 @@ public class EhConfig implements Cloneable {
* do not load images through the Hentai@Home Network
*/
public static final String LOAD_FROM_HAH_NO = "n";
/**
* Image Size Auto
*/
@@ -234,16 +67,6 @@ public class EhConfig implements Cloneable {
* Image Size 2400x
*/
public static final String IMAGE_SIZE_2400X = "2400";
/**
* Default gallery title
*/
private static final String GALLERY_TITLE_DEFAULT = "r";
/**
* Japanese gallery title
*/
private static final String GALLERY_TITLE_JAPANESE = "j";
/**
* Manual Accept, Manual Start
*/
@@ -260,7 +83,6 @@ public class EhConfig implements Cloneable {
* Auto Accept, Auto Start
*/
public static final String ARCHIVER_DOWNLOAD_AAAS = "3";
/**
* List View on the front and search pages
*/
@@ -269,16 +91,6 @@ public class EhConfig implements Cloneable {
* Thumbnail View on the front and search pages
*/
public static final String LAYOUT_MODE_THUMB = "t";
/**
* Show popular
*/
private static final String POPULAR_YES = "y";
/**
* Don't show popular
*/
private static final String POPULAR_NO = "n";
public static final int MISC = 0x1;
public static final int DOUJINSHI = 0x2;
public static final int MANGA = 0x4;
@@ -290,7 +102,6 @@ public class EhConfig implements Cloneable {
public static final int NON_H = 0x100;
public static final int WESTERN = 0x200;
public static final int ALL_CATEGORY = 0x3ff;
public static final int NAMESPACES_RECLASS = 0x1;
public static final int NAMESPACES_LANGUAGE = 0x2;
public static final int NAMESPACES_PARODY = 0x4;
@@ -299,7 +110,6 @@ public class EhConfig implements Cloneable {
public static final int NAMESPACES_ARTIST = 0x20;
public static final int NAMESPACES_MALE = 0x40;
public static final int NAMESPACES_FEMALE = 0x80;
public static final String JAPANESE_ORIGINAL = "0";
public static final String JAPANESE_TRANSLATED = "1024";
public static final String JAPANESE_REWRITE = "2048";
@@ -351,16 +161,6 @@ public class EhConfig implements Cloneable {
public static final String OTHER_ORIGINAL = "255";
public static final String OTHER_TRANSLATED = "1279";
public static final String OTHER_REWRITE = "2303";
/**
* Sort favorites by last gallery update time
*/
private static final String FAVORITES_SORT_GALLERY_UPDATE_TIME = "p";
/**
* Sort favorites by favorited time
*/
private static final String FAVORITES_SORT_FAVORITED_TIME = "f";
/**
* 25 results per page for the index/search page and torrent search pages
*/
@@ -377,7 +177,6 @@ public class EhConfig implements Cloneable {
* 200 results per page for the index/search page and torrent search pages
*/
public static final String RESULT_COUNT_200 = "3";
/**
* On mouse-over
*/
@@ -386,7 +185,6 @@ public class EhConfig implements Cloneable {
* On page load
*/
public static final String MOUSE_OVER_NO = "p";
/**
* Preview normal size
*/
@@ -395,7 +193,6 @@ public class EhConfig implements Cloneable {
* Preview large size
*/
public static final String PREVIEW_SIZE_LARGE = "l";
/**
* 4 row preview per page
*/
@@ -412,7 +209,6 @@ public class EhConfig implements Cloneable {
* 40 row preview per page
*/
public static final String PREVIEW_ROW_40 = "20";
/**
* Oldest comments first
*/
@@ -425,7 +221,6 @@ public class EhConfig implements Cloneable {
* By highest score
*/
public static final String COMMENTS_SORT_HIGHEST_SCORE_FIRST = "s";
/**
* Show gallery comment votes On score hover or click
*/
@@ -434,7 +229,6 @@ public class EhConfig implements Cloneable {
* Always show gallery comment votes
*/
public static final String COMMENTS_VOTES_ALWAYS = "1";
/**
* Sort order for gallery tags alphabetically
*/
@@ -443,7 +237,6 @@ public class EhConfig implements Cloneable {
* Sort order for gallery tags by tag power
*/
public static final String TAGS_SORT_POWER = "p";
/**
* Show gallery page numbers
*/
@@ -452,7 +245,6 @@ public class EhConfig implements Cloneable {
* Do not show gallery page numbers
*/
public static final String SHOW_GALLERY_INDEX_NO = "0";
/**
* Enable Tag Flagging
*/
@@ -461,7 +253,6 @@ public class EhConfig implements Cloneable {
* Do not enable Tag Flagging
*/
public static final String ENABLE_TAG_FLAGGING_NO = "n";
/**
* Always display the original images
*/
@@ -470,7 +261,6 @@ public class EhConfig implements Cloneable {
* Do not Always display the original images
*/
public static final String ALWAYS_ORIGINAL_NO = "n";
/**
* Enable the Multi-Page Viewe
*/
@@ -479,7 +269,6 @@ public class EhConfig implements Cloneable {
* Do not enable the Multi-Page Viewe
*/
public static final String MULTI_PAGE_NO = "n";
/**
* Align left, only scale if image is larger than browser width
*/
@@ -492,7 +281,6 @@ public class EhConfig implements Cloneable {
* Align center, Always scale images to fit browser width
*/
public static final String MULTI_PAGE_STYLE_Y = "y";
/**
* Show Multi-Page Viewer Thumbnail Pane
*/
@@ -501,32 +289,232 @@ public class EhConfig implements Cloneable {
* Hide Multi-Page Viewer Thumbnail Pane
*/
public static final String MULTI_PAGE_THUMB_HIDE = "y";
/**
* 460x for lofi resolution
*/
public static final String LOFI_RESOLUTION_460X = "1";
/**
* 780X for lofi resolution
*/
public static final String LOFI_RESOLUTION_780X = "2";
/**
* 980X for lofi resolution
*/
public static final String LOFI_RESOLUTION_980X = "3";
/**
* show warning
*/
public static final String CONTENT_WARNING_SHOW = "0";
/**
* not show warning
*/
public static final String CONTENT_WARNING_NOT_SHOW = "1";
/**
* The key of load images through the Hentai@Home Network
*
* @see #LOAD_FROM_HAH_YES
* @see #LOAD_FROM_HAH_NO
*/
private static final String KEY_LOAD_FROM_HAH = "uh";
/**
* The key of Image Size Settings
*
* @see #IMAGE_SIZE_AUTO
* @see #IMAGE_SIZE_780X
* @see #IMAGE_SIZE_980X
* @see #IMAGE_SIZE_1280X
* @see #IMAGE_SIZE_1600X
* @see #IMAGE_SIZE_2400X
*/
private static final String KEY_IMAGE_SIZE = "xr";
/**
* The key of scale images width
*/
private static final String KEY_SCALE_WIDTH = "rx";
/**
* The key of scale images height
*/
private static final String KEY_SCALE_HEIGHT = "ry";
/**
* The key of Gallery Name Display
*
* @see #GALLERY_TITLE_DEFAULT
* @see #GALLERY_TITLE_JAPANESE
*/
private static final String KEY_GALLERY_TITLE = "tl";
/**
* The key of the behavior for downloading archiver
*
* @see #ARCHIVER_DOWNLOAD_MAMS
* @see #ARCHIVER_DOWNLOAD_MAAS
* @see #ARCHIVER_DOWNLOAD_AAMS
* @see #ARCHIVER_DOWNLOAD_AAAS
*/
private static final String KEY_ARCHIVER_DOWNLOAD = "ar";
/**
* The key of display mode would you like to use on the front and search pages
*
* @see #LAYOUT_MODE_LIST
* @see #LAYOUT_MODE_THUMB
*/
private static final String KEY_LAYOUT_MODE = "dm";
/**
* The key for show popular
*
* @see #POPULAR_YES
* @see #POPULAR_NO
*/
private static final String KEY_POPULAR = "prn";
/**
* The key of categories would you like to view as default on the front page
*/
private static final String KEY_DEFAULT_CATEGORIES = "cats";
/**
* The key for favorites sort
*
* @see #FAVORITES_SORT_GALLERY_UPDATE_TIME
* @see #FAVORITES_SORT_FAVORITED_TIME
*/
private static final String KEY_FAVORITES_SORT = "fs";
/**
* The key of exclude certain namespaces from a default tag search
*/
private static final String KEY_EXCLUDED_NAMESPACES = "xns";
/**
* The key of hide galleries in certain languages from the gallery list and searches
*/
private static final String KEY_EXCLUDED_LANGUAGES = "xl";
/**
* The key of how many results would you like per page for the index/search page and torrent search pages
*
* @see #RESULT_COUNT_25
* @see #RESULT_COUNT_50
* @see #RESULT_COUNT_100
* @see #RESULT_COUNT_200
*/
private static final String KEY_RESULT_COUNT = "rc";
/**
* The key of mouse-over thumb
*
* @see #MOUSE_OVER_YES
* @see #MOUSE_OVER_NO
*/
private static final String KEY_MOUSE_OVER = "lt";
/**
* The key of preview size
*
* @see #PREVIEW_SIZE_NORMAL
* @see #PREVIEW_SIZE_LARGE
*/
private static final String KEY_PREVIEW_SIZE = "ts";
/**
* The key of preview row per page
*
* @see #PREVIEW_ROW_4
* @see #PREVIEW_ROW_10
* @see #PREVIEW_ROW_20
* @see #PREVIEW_ROW_40
*/
private static final String KEY_PREVIEW_ROW = "tr";
/**
* The key of sort order for gallery comments
*
* @see #COMMENTS_SORT_OLDEST_FIRST
* @see #COMMENTS_SORT_RECENT_FIRST
* @see #COMMENTS_SORT_HIGHEST_SCORE_FIRST
*/
private static final String KEY_COMMENTS_SORT = "cs";
/**
* The key of show gallery comment votes
*
* @see #COMMENTS_VOTES_POP
* @see #COMMENTS_VOTES_ALWAYS
*/
private static final String KEY_COMMENTS_VOTES = "sc";
/**
* The key of sort order for gallery tags
*
* @see #TAGS_SORT_ALPHABETICAL
* @see #TAGS_SORT_POWER
*/
private static final String KEY_TAGS_SORT = "to";
/**
* The key of show gallery page numbers
*
* @see #SHOW_GALLERY_INDEX_NO
* @see #SHOW_GALLERY_INDEX_YES
*/
private static final String KEY_SHOW_GALLERY_INDEX = "pn";
/**
* The key of the IP:Port of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_IP_PORT = "hp";
/**
* The key of the passkey of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_PASSKEY = "hk";
/**
* The key of enable Tag Flagging
*
* @see #ENABLE_TAG_FLAGGING_NO
* @see #ENABLE_TAG_FLAGGING_YES
*/
private static final String KEY_ENABLE_TAG_FLAGGING = "tf";
/**
* The key of always display the original images instead of the resampled versions
*
* @see #ALWAYS_ORIGINAL_NO
* @see #ALWAYS_ORIGINAL_YES
*/
private static final String KEY_ALWAYS_ORIGINAL = "oi";
/**
* The key of enable the multi-Page Viewer
*
* @see #MULTI_PAGE_NO
* @see #MULTI_PAGE_YES
*/
private static final String KEY_MULTI_PAGE = "qb";
/**
* The key of multi-Page Viewer Display Style
*
* @see #MULTI_PAGE_STYLE_C
* @see #MULTI_PAGE_STYLE_N
* @see #MULTI_PAGE_STYLE_Y
*/
private static final String KEY_MULTI_PAGE_STYLE = "ms";
/**
* The key of multi-Page Viewer Thumbnail Pane
*
* @see #MULTI_PAGE_THUMB_HIDE
* @see #MULTI_PAGE_THUMB_SHOW
*/
private static final String KEY_MULTI_PAGE_THUMB = "mt";
/**
* Default gallery title
*/
private static final String GALLERY_TITLE_DEFAULT = "r";
/**
* Japanese gallery title
*/
private static final String GALLERY_TITLE_JAPANESE = "j";
/**
* Show popular
*/
private static final String POPULAR_YES = "y";
/**
* Don't show popular
*/
private static final String POPULAR_NO = "n";
/**
* Sort favorites by last gallery update time
*/
private static final String FAVORITES_SORT_GALLERY_UPDATE_TIME = "p";
/**
* Sort favorites by favorited time
*/
private static final String FAVORITES_SORT_FAVORITED_TIME = "f";
/**
* Load images through the Hentai@Home Network<br/>
* key: {@link #KEY_LOAD_FROM_HAH}<br/>
@@ -538,7 +526,7 @@ public class EhConfig implements Cloneable {
* Image Size<br/>
* key: {@link #KEY_IMAGE_SIZE}<br/>
* value: {@link #IMAGE_SIZE_AUTO}, {@link #IMAGE_SIZE_780X}, {@link #IMAGE_SIZE_980X},
* {@link #IMAGE_SIZE_1280X}, {@link #IMAGE_SIZE_1600X}, {@link #IMAGE_SIZE_2400X}
* {@link #IMAGE_SIZE_1280X}, {@link #IMAGE_SIZE_1600X}, {@link #IMAGE_SIZE_2400X}
*/
public String imageSize = IMAGE_SIZE_AUTO;
@@ -46,18 +46,8 @@ public class EhCookieStore extends CookieRepository {
super(context, "okhttp3-cookie.db");
}
public void signOut() {
clear();
}
public boolean hasSignedIn() {
HttpUrl url = HttpUrl.parse(EhUrl.HOST_E);
return contains(url, KEY_IPD_MEMBER_ID) &&
contains(url, KEY_IPD_PASS_HASH);
}
public static Cookie newCookie(Cookie cookie, String newDomain, boolean forcePersistent,
boolean forceLongLive, boolean forceNotHostOnly) {
boolean forceLongLive, boolean forceNotHostOnly) {
Cookie.Builder builder = new Cookie.Builder();
builder.name(cookie.name());
builder.value(cookie.value());
@@ -84,6 +74,16 @@ public class EhCookieStore extends CookieRepository {
return builder.build();
}
public void signOut() {
clear();
}
public boolean hasSignedIn() {
HttpUrl url = HttpUrl.parse(EhUrl.HOST_E);
return contains(url, KEY_IPD_MEMBER_ID) &&
contains(url, KEY_IPD_PASS_HASH);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
List<Cookie> cookies = super.loadForRequest(url);
@@ -93,7 +93,7 @@ public class EhCookieStore extends CookieRepository {
if (checkTips) {
List<Cookie> result = new ArrayList<>(cookies.size() + 1);
// Add all but skip some
for (Cookie cookie: cookies) {
for (Cookie cookie : cookies) {
String name = cookie.name();
if (EhConfig.KEY_CONTENT_WARNING.equals(name)) {
continue;
@@ -21,9 +21,11 @@ package com.hippo.ehviewer.client;
*/
import android.content.Context;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Hosts;
import com.hippo.ehviewer.Settings;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
@@ -31,60 +33,61 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.Dns;
public class EhDns implements Dns {
private static final Map<String, InetAddress> builtInHosts;
private static final Map<String, InetAddress> builtInHosts;
static {
Map<String, InetAddress> map = new HashMap<>();
put(map, "e-hentai.org", "104.20.26.25");
put(map, "repo.e-hentai.org", "94.100.29.73");
put(map, "forums.e-hentai.org", "94.100.18.243");
put(map, "ehgt.org", "81.171.14.118");
put(map, "ul.ehgt.org", "94.100.24.82");
put(map, "github.com", "192.30.255.112");
put(map, "raw.githubusercontent.com", "151.101.0.133");
builtInHosts = map;
}
private static void put(Map<String, InetAddress> map, String host, String ip) {
InetAddress address = Hosts.toInetAddress(host, ip);
if (address != null) {
map.put(host, address);
}
}
private final Hosts hosts;
public EhDns(Context context) {
hosts = EhApplication.getHosts(context);
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (hostname == null) throw new UnknownHostException("hostname == null");
InetAddress inetAddress = hosts.get(hostname);
if (inetAddress != null) {
return Collections.singletonList(inetAddress);
static {
Map<String, InetAddress> map = new HashMap<>();
put(map, "e-hentai.org", "104.20.26.25");
put(map, "repo.e-hentai.org", "94.100.29.73");
put(map, "forums.e-hentai.org", "94.100.18.243");
put(map, "ehgt.org", "81.171.14.118");
put(map, "ul.ehgt.org", "94.100.24.82");
put(map, "github.com", "192.30.255.112");
put(map, "raw.githubusercontent.com", "151.101.0.133");
builtInHosts = map;
}
if (Settings.getBuiltInHosts()) {
inetAddress = builtInHosts.get(hostname);
if (inetAddress != null) {
return Collections.singletonList(inetAddress);
}
private final Hosts hosts;
public EhDns(Context context) {
hosts = EhApplication.getHosts(context);
}
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (NullPointerException e) {
UnknownHostException unknownHostException =
new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
unknownHostException.initCause(e);
throw unknownHostException;
private static void put(Map<String, InetAddress> map, String host, String ip) {
InetAddress address = Hosts.toInetAddress(host, ip);
if (address != null) {
map.put(host, address);
}
}
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
if (hostname == null) throw new UnknownHostException("hostname == null");
InetAddress inetAddress = hosts.get(hostname);
if (inetAddress != null) {
return Collections.singletonList(inetAddress);
}
if (Settings.getBuiltInHosts()) {
inetAddress = builtInHosts.get(hostname);
if (inetAddress != null) {
return Collections.singletonList(inetAddress);
}
}
try {
return Arrays.asList(InetAddress.getAllByName(hostname));
} catch (NullPointerException e) {
UnknownHostException unknownHostException =
new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
unknownHostException.initCause(e);
throw unknownHostException;
}
}
}
}
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.client;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
@@ -49,11 +51,19 @@ import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.network.StatusCodeException;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.Headers;
@@ -63,23 +73,15 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
public class EhEngine {
public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private static final String TAG = EhEngine.class.getSimpleName();
private static final String SAD_PANDA_DISPOSITION = "inline; filename=\"sadpanda.jpg\"";
private static final String SAD_PANDA_TYPE = "image/gif";
private static final String SAD_PANDA_LENGTH = "9615";
private static final String KOKOMADE_URL = "https://exhentai.org/img/kokomade.jpg";
public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
private static final MediaType MEDIA_TYPE_JPEG = MediaType.parse("image/jpeg");
private static final Pattern PATTERN_NEED_HATH_CLIENT = Pattern.compile("(You must have a H@H client assigned to your account to use this feature\\.)");
@@ -91,7 +93,7 @@ public class EhEngine {
}
private static void doThrowException(Call call, int code, @Nullable Headers headers,
@Nullable String body, Throwable e) throws Throwable {
@Nullable String body, Throwable e) throws Throwable {
if (call.isCanceled()) {
throw new CancelledException();
}
@@ -131,7 +133,7 @@ public class EhEngine {
}
private static void throwException(Call call, int code, @Nullable Headers headers,
@Nullable String body, Throwable e) throws Throwable {
@Nullable String body, Throwable e) throws Throwable {
try {
doThrowException(call, code, headers, body, e);
} catch (Throwable error) {
@@ -141,7 +143,7 @@ public class EhEngine {
}
public static String signIn(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String username, String password) throws Throwable {
String username, String password) throws Throwable {
FormBody.Builder builder = new FormBody.Builder()
.add("UserName", username)
.add("PassWord", password)
@@ -232,7 +234,7 @@ public class EhEngine {
}
public static GalleryListParser.Result getGalleryList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url) throws Throwable {
String url) throws Throwable {
String referer = EhUrl.getReferer();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -266,7 +268,7 @@ public class EhEngine {
// At least, GalleryInfo contain valid gid and token
public static List<GalleryInfo> fillGalleryListByApi(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
List<GalleryInfo> galleryInfoList, String referer) throws Throwable {
List<GalleryInfo> galleryInfoList, String referer) throws Throwable {
// We can only request 25 items one time at most
final int MAX_REQUEST_SIZE = 25;
List<GalleryInfo> requestItems = new ArrayList<>(MAX_REQUEST_SIZE);
@@ -281,7 +283,7 @@ public class EhEngine {
}
private static void doFillGalleryListByApi(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
List<GalleryInfo> galleryInfoList, String referer) throws Throwable {
List<GalleryInfo> galleryInfoList, String referer) throws Throwable {
JSONObject json = new JSONObject();
json.put("method", "gdata");
JSONArray ja = new JSONArray();
@@ -324,7 +326,7 @@ public class EhEngine {
}
public static GalleryDetail getGalleryDetail(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url) throws Throwable {
String url) throws Throwable {
String referer = EhUrl.getReferer();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -382,8 +384,8 @@ public class EhEngine {
}
public static RateGalleryParser.Result rateGallery(@Nullable EhClient.Task task,
OkHttpClient okHttpClient, long apiUid, String apiKey, long gid,
String token, float rating) throws Throwable {
OkHttpClient okHttpClient, long apiUid, String apiKey, long gid,
String token, float rating) throws Throwable {
final JSONObject json = new JSONObject();
json.put("method", "rategallery");
json.put("apiuid", apiUid);
@@ -423,7 +425,7 @@ public class EhEngine {
}
public static GalleryCommentList commentGallery(@Nullable EhClient.Task task,
OkHttpClient okHttpClient, String url, String comment, String id) throws Throwable {
OkHttpClient okHttpClient, String url, String comment, String id) throws Throwable {
FormBody.Builder builder = new FormBody.Builder();
if (id == null) {
builder.add("commenttext_new", comment);
@@ -467,7 +469,7 @@ public class EhEngine {
}
public static String getGalleryToken(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long gid, String gtoken, int page) throws Throwable {
long gid, String gtoken, int page) throws Throwable {
JSONObject json = new JSONObject()
.put("method", "gtoken")
.put("pagelist", new JSONArray().put(
@@ -504,7 +506,7 @@ public class EhEngine {
}
public static FavoritesParser.Result getFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, boolean callApi) throws Throwable {
String url, boolean callApi) throws Throwable {
String referer = EhUrl.getReferer();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -538,10 +540,10 @@ public class EhEngine {
/**
* @param dstCat -1 for delete, 0 - 9 for cloud favorite, others throw Exception
* @param note max 250 characters
* @param note max 250 characters
*/
public static Void addFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long gid, String token, int dstCat, String note) throws Throwable {
long gid, String token, int dstCat, String note) throws Throwable {
String catStr;
if (dstCat == -1) {
catStr = "favdel";
@@ -588,7 +590,7 @@ public class EhEngine {
}
public static Void addFavoritesRange(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long[] gidArray, String[] tokenArray, int dstCat) throws Throwable {
long[] gidArray, String[] tokenArray, int dstCat) throws Throwable {
AssertUtils.assertEquals(gidArray.length, tokenArray.length);
for (int i = 0, n = gidArray.length; i < n; i++) {
addFavorites(task, okHttpClient, gidArray[i], tokenArray[i], dstCat, null);
@@ -597,7 +599,7 @@ public class EhEngine {
}
public static FavoritesParser.Result modifyFavorites(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, long[] gidArray, int dstCat, boolean callApi) throws Throwable {
String url, long[] gidArray, int dstCat, boolean callApi) throws Throwable {
String catStr;
if (dstCat == -1) {
catStr = "delete";
@@ -646,7 +648,7 @@ public class EhEngine {
}
public static Pair<String, String>[] getTorrentList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, long gid, String token) throws Throwable {
String url, long gid, String token) throws Throwable {
String referer = EhUrl.getGalleryDetailUrl(gid, token);
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -677,7 +679,7 @@ public class EhEngine {
}
public static Pair<String, Pair<String, String>[]> getArchiveList(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
String url, long gid, String token) throws Throwable {
String url, long gid, String token) throws Throwable {
String referer = EhUrl.getGalleryDetailUrl(gid, token);
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -708,7 +710,7 @@ public class EhEngine {
}
public static Void downloadArchive(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long gid, String token, String or, String res) throws Throwable {
long gid, String token, String or, String res) throws Throwable {
if (or == null || or.length() == 0) {
throw new EhException("Invalid form param or: " + or);
}
@@ -755,7 +757,7 @@ public class EhEngine {
}
private static ProfileParser.Result getProfileInternal(@Nullable EhClient.Task task,
OkHttpClient okHttpClient, String url, String referer) throws Throwable {
OkHttpClient okHttpClient, String url, String referer) throws Throwable {
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
Call call = okHttpClient.newCall(request);
@@ -782,7 +784,7 @@ public class EhEngine {
}
public static ProfileParser.Result getProfile(@Nullable EhClient.Task task,
OkHttpClient okHttpClient) throws Throwable {
OkHttpClient okHttpClient) throws Throwable {
String url = EhUrl.URL_FORUMS;
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, null).build();
@@ -810,7 +812,7 @@ public class EhEngine {
}
public static VoteCommentParser.Result voteComment(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
long apiUid, String apiKey, long gid, String token, long commentId, int commentVote) throws Throwable {
final JSONObject json = new JSONObject();
json.put("method", "votecomment");
json.put("apiuid", apiUid);
@@ -854,7 +856,7 @@ public class EhEngine {
* @param image Must be jpeg
*/
public static GalleryListParser.Result imageSearch(@Nullable EhClient.Task task, OkHttpClient okHttpClient,
File image, boolean uss, boolean osc, boolean se) throws Throwable {
File image, boolean uss, boolean osc, boolean se) throws Throwable {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
builder.addPart(
@@ -922,7 +924,7 @@ public class EhEngine {
}
public static GalleryPageParser.Result getGalleryPage(@Nullable EhClient.Task task,
OkHttpClient okHttpClient, String url, long gid, String token) throws Throwable {
OkHttpClient okHttpClient, String url, long gid, String token) throws Throwable {
String referer = EhUrl.getGalleryDetailUrl(gid, token);
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer).build();
@@ -950,7 +952,7 @@ public class EhEngine {
}
public static GalleryPageApiParser.Result getGalleryPageApi(@Nullable EhClient.Task task,
OkHttpClient okHttpClient, long gid, int index, String pToken, String showKey, String previousPToken) throws Throwable {
OkHttpClient okHttpClient, long gid, int index, String pToken, String showKey, String previousPToken) throws Throwable {
final JSONObject json = new JSONObject();
json.put("method", "showpage");
json.put("gid", gid);
@@ -966,8 +968,8 @@ public class EhEngine {
String origin = EhUrl.getOrigin();
Log.d(TAG, url);
Request request = new EhRequestBuilder(url, referer, origin)
.post(requestBody)
.build();
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request);
// Put call
@@ -27,27 +27,17 @@ import java.util.List;
public final class EhFilter {
private static final String TAG = EhFilter.class.getSimpleName();
public static final int MODE_TITLE = 0;
public static final int MODE_UPLOADER = 1;
public static final int MODE_TAG = 2;
public static final int MODE_TAG_NAMESPACE = 3;
private static final String TAG = EhFilter.class.getSimpleName();
private static EhFilter sInstance;
private final List<Filter> mTitleFilterList = new ArrayList<>();
private final List<Filter> mUploaderFilterList = new ArrayList<>();
private final List<Filter> mTagFilterList = new ArrayList<>();
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
private static EhFilter sInstance;
public static EhFilter getInstance() {
if (sInstance == null) {
sInstance = new EhFilter();
}
return sInstance;
}
private EhFilter() {
List<Filter> list = EhDB.getAllFilter();
for (int i = 0, n = list.size(); i < n; i++) {
@@ -75,6 +65,13 @@ public final class EhFilter {
}
}
public static EhFilter getInstance() {
if (sInstance == null) {
sInstance = new EhFilter();
}
return sInstance;
}
public List<Filter> getTitleFilterList() {
return mTitleFilterList;
}
@@ -216,11 +213,7 @@ public final class EhFilter {
!tagNamespace.equals(filterNamespace)) {
return false;
}
if (!tagName.equals(filterName)) {
return false;
}
return true;
return tagName.equals(filterName);
}
public synchronized boolean filterTag(GalleryInfo info) {
@@ -232,7 +225,7 @@ public final class EhFilter {
String[] tags = info.simpleTags;
List<Filter> filters = mTagFilterList;
if (null != tags && filters.size() > 0) {
for (String tag: tags) {
for (String tag : tags) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && matchTag(tag, filters.get(i).text)) {
return false;
@@ -267,7 +260,7 @@ public final class EhFilter {
String[] tags = info.simpleTags;
List<Filter> filters = mTagNamespaceFilterList;
if (null != tags && filters.size() > 0) {
for (String tag: tags) {
for (String tag : tags) {
for (int i = 0, n = filters.size(); i < n; i++) {
if (filters.get(i).enable && matchTagNamespace(tag, filters.get(i).text)) {
return false;
@@ -20,51 +20,49 @@ import com.hippo.ehviewer.Settings;
public class EhRequest {
EhClient.Task task;
private int mMethod;
private Object[] mArgs;
private EhClient.Callback mCallback;
private EhConfig mEhConfig;
EhClient.Task task;
private boolean mCancel = false;
public int getMethod() {
return mMethod;
}
public EhRequest setMethod(int method) {
mMethod = method;
return this;
}
public Object[] getArgs() {
return mArgs;
}
public EhRequest setArgs(Object... args) {
mArgs = args;
return this;
}
public EhClient.Callback getCallback() {
return mCallback;
}
public EhRequest setCallback(EhClient.Callback callback) {
mCallback = callback;
return this;
}
public EhConfig getEhConfig() {
return mEhConfig != null ? mEhConfig : Settings.getEhConfig();
}
public EhRequest setEhConfig(EhConfig ehConfig) {
mEhConfig = ehConfig;
return this;
}
public int getMethod() {
return mMethod;
}
public Object[] getArgs() {
return mArgs;
}
public EhClient.Callback getCallback() {
return mCallback;
}
public EhConfig getEhConfig() {
return mEhConfig != null ? mEhConfig : Settings.getEhConfig();
}
public void cancel() {
if (!mCancel) {
mCancel = true;
@@ -18,7 +18,9 @@ package com.hippo.ehviewer.client;
import android.content.Context;
import android.util.Base64;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
@@ -27,6 +29,7 @@ import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.util.TextUrl;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -39,6 +42,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
@@ -49,309 +53,307 @@ import okio.Okio;
public class EhTagDatabase {
private final String name;
private final byte[] tags;
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
private static volatile EhTagDatabase instance;
// TODO more lock for different language
private static Lock lock = new ReentrantLock();
public EhTagDatabase(String name, BufferedSource source) throws IOException {
this.name = name;
int totalBytes = source.readInt();
tags = new byte[totalBytes];
source.readFully(tags);
}
static {
NAMESPACE_TO_PREFIX.put("artist", "a:");
NAMESPACE_TO_PREFIX.put("character", "c:");
NAMESPACE_TO_PREFIX.put("female", "f:");
NAMESPACE_TO_PREFIX.put("group", "g:");
NAMESPACE_TO_PREFIX.put("language", "l:");
NAMESPACE_TO_PREFIX.put("male", "m:");
NAMESPACE_TO_PREFIX.put("misc", "");
NAMESPACE_TO_PREFIX.put("parody", "p:");
NAMESPACE_TO_PREFIX.put("reclass", "r:");
}
public String getTranslation(String tag) {
return search(tags, tag.getBytes(TextUrl.UTF_8));
}
private final String name;
private final byte[] tags;
@Nullable
private String search(byte[] tags, byte[] tag) {
int low = 0;
int high = tags.length;
while (low < high) {
int start = (low + high) / 2;
// Look for the starting '\n'
while (start > -1 && tags[start] != '\n') {
start--;
}
start++;
public EhTagDatabase(String name, BufferedSource source) throws IOException {
this.name = name;
int totalBytes = source.readInt();
tags = new byte[totalBytes];
source.readFully(tags);
}
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
int compare;
int tagIndex = 0;
int curIndex = start;
for (;;) {
int tagByte = tag[tagIndex] & 0xff;
int curByte = tags[curIndex] & 0xff;
compare = tagByte - curByte;
if (compare != 0) {
break;
@Nullable
public static EhTagDatabase getInstance(Context context) {
if (isPossible(context)) {
return instance;
} else {
instance = null;
return null;
}
}
tagIndex++;
curIndex++;
if (tagIndex == tag.length && curIndex == start + middle) {
break;
@Nullable
public static String namespaceToPrefix(String namespace) {
return NAMESPACE_TO_PREFIX.get(namespace);
}
private static String[] getMetadata(Context context) {
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
if (metadata.length == 4) {
return metadata;
} else {
return null;
}
if (tagIndex == tag.length) {
compare = -1;
break;
}
if (curIndex == start + middle) {
compare = 1;
break;
}
}
if (compare < 0) {
high = start - 1;
} else if (compare > 0) {
low = start + end + 1;
} else {
byte[] bytes = Base64.decode(tags, start + middle + 1, end - middle - 1, Base64.DEFAULT);
return new String(bytes, TextUrl.UTF_8);
}
}
return null;
}
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
static {
NAMESPACE_TO_PREFIX.put("artist", "a:");
NAMESPACE_TO_PREFIX.put("character", "c:");
NAMESPACE_TO_PREFIX.put("female", "f:");
NAMESPACE_TO_PREFIX.put("group", "g:");
NAMESPACE_TO_PREFIX.put("language", "l:");
NAMESPACE_TO_PREFIX.put("male", "m:");
NAMESPACE_TO_PREFIX.put("misc", "");
NAMESPACE_TO_PREFIX.put("parody", "p:");
NAMESPACE_TO_PREFIX.put("reclass", "r:");
}
private static volatile EhTagDatabase instance;
// TODO more lock for different language
private static Lock lock = new ReentrantLock();
@Nullable
public static EhTagDatabase getInstance(Context context) {
if (isPossible(context)) {
return instance;
} else {
instance = null;
return null;
}
}
@Nullable
public static String namespaceToPrefix(String namespace) {
return NAMESPACE_TO_PREFIX.get(namespace);
}
private static String[] getMetadata(Context context) {
String[] metadata = context.getResources().getStringArray(R.array.tag_translation_metadata);
if (metadata.length == 4) {
return metadata;
} else {
return null;
}
}
public static boolean isPossible(Context context) {
return getMetadata(context) != null;
}
@Nullable
private static byte[] getFileContent(File file, int length) {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
byte[] content = new byte[length];
source.readFully(content);
return content;
} catch (IOException e) {
return null;
}
}
@Nullable
private static byte[] getFileSha1(File file) {
try (InputStream is = new FileInputStream(file)) {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
int n;
byte[] buffer = new byte[4 * 1024];
while ((n = is.read(buffer)) != -1) {
digest.update(buffer, 0, n);
}
return digest.digest();
} catch (IOException e) {
return null;
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static boolean equals(byte[] b1, byte[] b2) {
if (b1 == null && b2 == null) {
return true;
}
if (b1 == null || b2 == null) {
return false;
}
if (b1.length != b2.length) {
return false;
public static boolean isPossible(Context context) {
return getMetadata(context) != null;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean checkData(File sha1File, File dataFile) {
byte[] s1 = getFileContent(sha1File, 20);
if (s1 == null) {
return false;
}
byte[] s2 = getFileSha1(dataFile);
if (s2 == null) {
return false;
}
return equals(s1, s2);
}
private static boolean save(OkHttpClient client, String url, File file) {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
ResponseBody body = response.body();
if (body == null) {
return false;
}
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
IOUtils.copy(is, os);
}
return true;
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
return false;
}
}
public static void update(Context context) {
String[] urls = getMetadata(context);
if (urls == null || urls.length != 4) {
// Clear tags if it's not possible
instance = null;
return;
}
String sha1Name = urls[0];
String sha1Url = urls[1];
String dataName = urls[2];
String dataUrl = urls[3];
// Clear tags if name if different
EhTagDatabase tmp = instance;
if (tmp != null && !tmp.name.equals(dataName)) {
instance = null;
}
IoThreadPoolExecutor.getInstance().execute(() -> {
if (!lock.tryLock()) {
return;
}
try {
File dir = AppConfig.getFilesDir("tag-translations");
if (dir == null) {
return;
}
// Check current sha1 and current data
File sha1File = new File(dir, sha1Name);
File dataFile = new File(dir, dataName);
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
// Read current EhTagDatabase
if (instance == null && dataFile.exists()) {
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
}
OkHttpClient client = EhApplication.getOkHttpClient(EhApplication.getInstance());
// Save new sha1
File tempSha1File = new File(dir, sha1Name + ".tmp");
if (!save(client, sha1Url, tempSha1File)) {
FileUtils.delete(tempSha1File);
return;
}
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File);
return;
}
// Save new data
File tempDataFile = new File(dir, dataName + ".tmp");
if (!save(client, dataUrl, tempDataFile)) {
FileUtils.delete(tempDataFile);
return;
}
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File);
FileUtils.delete(tempDataFile);
return;
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
tempSha1File.renameTo(sha1File);
tempDataFile.renameTo(dataFile);
// Read new EhTagDatabase
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
@Nullable
private static byte[] getFileContent(File file, int length) {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
byte[] content = new byte[length];
source.readFully(content);
return content;
} catch (IOException e) {
// Ignore
return null;
}
} finally {
lock.unlock();
}
});
}
}
@Nullable
private static byte[] getFileSha1(File file) {
try (InputStream is = new FileInputStream(file)) {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
int n;
byte[] buffer = new byte[4 * 1024];
while ((n = is.read(buffer)) != -1) {
digest.update(buffer, 0, n);
}
return digest.digest();
} catch (IOException e) {
return null;
} catch (NoSuchAlgorithmException e) {
return null;
}
}
private static boolean equals(byte[] b1, byte[] b2) {
if (b1 == null && b2 == null) {
return true;
}
if (b1 == null || b2 == null) {
return false;
}
if (b1.length != b2.length) {
return false;
}
for (int i = 0; i < b1.length; i++) {
if (b1[i] != b2[i]) {
return false;
}
}
return true;
}
private static boolean checkData(File sha1File, File dataFile) {
byte[] s1 = getFileContent(sha1File, 20);
if (s1 == null) {
return false;
}
byte[] s2 = getFileSha1(dataFile);
if (s2 == null) {
return false;
}
return equals(s1, s2);
}
private static boolean save(OkHttpClient client, String url, File file) {
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
try (Response response = call.execute()) {
if (!response.isSuccessful()) {
return false;
}
ResponseBody body = response.body();
if (body == null) {
return false;
}
try (InputStream is = body.byteStream(); OutputStream os = new FileOutputStream(file)) {
IOUtils.copy(is, os);
}
return true;
} catch (Throwable t) {
ExceptionUtils.throwIfFatal(t);
return false;
}
}
public static void update(Context context) {
String[] urls = getMetadata(context);
if (urls == null || urls.length != 4) {
// Clear tags if it's not possible
instance = null;
return;
}
String sha1Name = urls[0];
String sha1Url = urls[1];
String dataName = urls[2];
String dataUrl = urls[3];
// Clear tags if name if different
EhTagDatabase tmp = instance;
if (tmp != null && !tmp.name.equals(dataName)) {
instance = null;
}
IoThreadPoolExecutor.getInstance().execute(() -> {
if (!lock.tryLock()) {
return;
}
try {
File dir = AppConfig.getFilesDir("tag-translations");
if (dir == null) {
return;
}
// Check current sha1 and current data
File sha1File = new File(dir, sha1Name);
File dataFile = new File(dir, dataName);
if (!checkData(sha1File, dataFile)) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
// Read current EhTagDatabase
if (instance == null && dataFile.exists()) {
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
}
}
OkHttpClient client = EhApplication.getOkHttpClient(EhApplication.getInstance());
// Save new sha1
File tempSha1File = new File(dir, sha1Name + ".tmp");
if (!save(client, sha1Url, tempSha1File)) {
FileUtils.delete(tempSha1File);
return;
}
// Check new sha1 and current data
if (checkData(tempSha1File, dataFile)) {
// The data is the same
FileUtils.delete(tempSha1File);
return;
}
// Save new data
File tempDataFile = new File(dir, dataName + ".tmp");
if (!save(client, dataUrl, tempDataFile)) {
FileUtils.delete(tempDataFile);
return;
}
// Check new sha1 and new data
if (!checkData(tempSha1File, tempDataFile)) {
FileUtils.delete(tempSha1File);
FileUtils.delete(tempDataFile);
return;
}
// Replace current sha1 and current data with new sha1 and new data
FileUtils.delete(sha1File);
FileUtils.delete(dataFile);
tempSha1File.renameTo(sha1File);
tempDataFile.renameTo(dataFile);
// Read new EhTagDatabase
try (BufferedSource source = Okio.buffer(Okio.source(dataFile))) {
instance = new EhTagDatabase(dataName, source);
} catch (IOException e) {
// Ignore
}
} finally {
lock.unlock();
}
});
}
public String getTranslation(String tag) {
return search(tags, tag.getBytes(TextUrl.UTF_8));
}
@Nullable
private String search(byte[] tags, byte[] tag) {
int low = 0;
int high = tags.length;
while (low < high) {
int start = (low + high) / 2;
// Look for the starting '\n'
while (start > -1 && tags[start] != '\n') {
start--;
}
start++;
// Look for the middle '\r'.
int middle = 1;
while (tags[start + middle] != '\r') {
middle++;
}
// Look for the ending '\n'
int end = middle + 1;
while (tags[start + end] != '\n') {
end++;
}
int compare;
int tagIndex = 0;
int curIndex = start;
for (; ; ) {
int tagByte = tag[tagIndex] & 0xff;
int curByte = tags[curIndex] & 0xff;
compare = tagByte - curByte;
if (compare != 0) {
break;
}
tagIndex++;
curIndex++;
if (tagIndex == tag.length && curIndex == start + middle) {
break;
}
if (tagIndex == tag.length) {
compare = -1;
break;
}
if (curIndex == start + middle) {
compare = 1;
break;
}
}
if (compare < 0) {
high = start - 1;
} else if (compare > 0) {
low = start + end + 1;
} else {
byte[] bytes = Base64.decode(tags, start + middle + 1, end - middle - 1, Base64.DEFAULT);
return new String(bytes, TextUrl.UTF_8);
}
}
return null;
}
}
@@ -17,10 +17,13 @@
package com.hippo.ehviewer.client;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.Settings;
import com.hippo.network.UrlBuilder;
import java.util.List;
import java.util.ListIterator;
import okhttp3.HttpUrl;
public class EhUrl {
@@ -207,7 +210,7 @@ public class EhUrl {
public static String getThumbUrlPrefix() {
switch (Settings.getGallerySite()) {
default:
//case SITE_E:
//case SITE_E:
return URL_PREFIX_THUMB_E;
//case SITE_EX:
// return URL_PREFIX_THUMB_EX;
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.client;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser;
import com.hippo.ehviewer.client.parser.GalleryListUrlParser;
@@ -19,10 +19,13 @@ package com.hippo.ehviewer.client;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.data.GalleryInfo;
import java.util.regex.Pattern;
public class EhUtils {
@@ -64,20 +67,20 @@ public class EhUtils {
EhConfig.ASIAN_PORN,
EhConfig.NON_H,
EhConfig.WESTERN,
UNKNOWN };
UNKNOWN};
private static final String[][] CATEGORY_STRINGS = {
new String[] { "misc" },
new String[] { "doujinshi" },
new String[] { "manga" },
new String[] { "artistcg", "Artist CG Sets", "Artist CG" },
new String[] { "gamecg", "Game CG Sets", "Game CG" },
new String[] { "imageset", "Image Sets", "Image Set" },
new String[] { "cosplay" },
new String[] { "asianporn", "Asian Porn" },
new String[] { "non-h" },
new String[] { "western" },
new String[] { "unknown" }
new String[]{"misc"},
new String[]{"doujinshi"},
new String[]{"manga"},
new String[]{"artistcg", "Artist CG Sets", "Artist CG"},
new String[]{"gamecg", "Game CG Sets", "Game CG"},
new String[]{"imageset", "Image Sets", "Image Set"},
new String[]{"cosplay"},
new String[]{"asianporn", "Asian Porn"},
new String[]{"non-h"},
new String[]{"western"},
new String[]{"unknown"}
};
public static int getCategory(String type) {
@@ -29,36 +29,56 @@ import java.net.URLEncoder;
public class FavListUrlBuilder implements Parcelable {
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
public static final int FAV_CAT_ALL = -1;
public static final int FAV_CAT_LOCAL = -2;
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<FavListUrlBuilder>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
return new FavListUrlBuilder(source);
}
@Override
public FavListUrlBuilder[] newArray(int size) {
return new FavListUrlBuilder[size];
}
};
private static final String TAG = FavListUrlBuilder.class.getSimpleName();
private int mIndex;
private String mKeyword;
private int mFavCat = FAV_CAT_ALL;
public FavListUrlBuilder() {
}
protected FavListUrlBuilder(Parcel in) {
this.mIndex = in.readInt();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
public static boolean isValidFavCat(int favCat) {
return favCat >= 0 && favCat <= 9;
}
public void setIndex(int index) {
mIndex = index;
}
public void setKeyword(String keyword) {
mKeyword = keyword;
}
public void setFavCat(int favCat) {
mFavCat = favCat;
}
public String getKeyword() {
return mKeyword;
}
public void setKeyword(String keyword) {
mKeyword = keyword;
}
public int getFavCat() {
return mFavCat;
}
public static boolean isValidFavCat(int favCat) {
return favCat >= 0 && favCat <= 9;
public void setFavCat(int favCat) {
mFavCat = favCat;
}
public boolean isLocalFavCat() {
@@ -102,26 +122,4 @@ public class FavListUrlBuilder implements Parcelable {
dest.writeString(this.mKeyword);
dest.writeInt(this.mFavCat);
}
public FavListUrlBuilder() {
}
protected FavListUrlBuilder(Parcel in) {
this.mIndex = in.readInt();
this.mKeyword = in.readString();
this.mFavCat = in.readInt();
}
public static final Parcelable.Creator<FavListUrlBuilder> CREATOR = new Parcelable.Creator<FavListUrlBuilder>() {
@Override
public FavListUrlBuilder createFromParcel(Parcel source) {
return new FavListUrlBuilder(source);
}
@Override
public FavListUrlBuilder[] newArray(int size) {
return new FavListUrlBuilder[size];
}
};
}
@@ -18,10 +18,22 @@ package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
public class GalleryApiInfo implements Parcelable {
public static final Parcelable.Creator<GalleryApiInfo> CREATOR = new Parcelable.Creator<GalleryApiInfo>() {
@Override
public GalleryApiInfo createFromParcel(Parcel source) {
return new GalleryApiInfo(source);
}
@Override
public GalleryApiInfo[] newArray(int size) {
return new GalleryApiInfo[size];
}
};
public long gid;
public String token;
public String archiverKey;
@@ -39,6 +51,27 @@ public class GalleryApiInfo implements Parcelable {
@Nullable
public String[] tags;
public GalleryApiInfo() {
}
protected GalleryApiInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.archiverKey = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.category = in.readInt();
this.thumb = in.readString();
this.uploader = in.readString();
this.posted = in.readLong();
this.filecount = in.readInt();
this.filesize = in.readLong();
this.expunged = in.readByte() != 0;
this.rating = in.readFloat();
this.torrentcount = in.readInt();
this.tags = in.createStringArray();
}
@Override
public int describeContents() {
return 0;
@@ -62,37 +95,4 @@ public class GalleryApiInfo implements Parcelable {
dest.writeInt(this.torrentcount);
dest.writeStringArray(this.tags);
}
public GalleryApiInfo() {
}
protected GalleryApiInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.archiverKey = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.category = in.readInt();
this.thumb = in.readString();
this.uploader = in.readString();
this.posted = in.readLong();
this.filecount = in.readInt();
this.filesize = in.readLong();
this.expunged = in.readByte() != 0;
this.rating = in.readFloat();
this.torrentcount = in.readInt();
this.tags = in.createStringArray();
}
public static final Parcelable.Creator<GalleryApiInfo> CREATOR = new Parcelable.Creator<GalleryApiInfo>() {
@Override
public GalleryApiInfo createFromParcel(Parcel source) {
return new GalleryApiInfo(source);
}
@Override
public GalleryApiInfo[] newArray(int size) {
return new GalleryApiInfo[size];
}
};
}
@@ -21,6 +21,17 @@ import android.os.Parcelable;
public class GalleryComment implements Parcelable {
public static final Creator<GalleryComment> CREATOR = new Creator<GalleryComment>() {
@Override
public GalleryComment createFromParcel(Parcel source) {
return new GalleryComment(source);
}
@Override
public GalleryComment[] newArray(int size) {
return new GalleryComment[size];
}
};
// 0 for uploader comment. can't vote
public long id;
public int score;
@@ -35,6 +46,24 @@ public class GalleryComment implements Parcelable {
public String comment;
public long lastEdited;
public GalleryComment() {
}
protected GalleryComment(Parcel in) {
this.id = in.readLong();
this.score = in.readInt();
this.editable = in.readByte() != 0;
this.voteUpAble = in.readByte() != 0;
this.voteUpEd = in.readByte() != 0;
this.voteDownAble = in.readByte() != 0;
this.voteDownEd = in.readByte() != 0;
this.voteState = in.readString();
this.time = in.readLong();
this.user = in.readString();
this.comment = in.readString();
this.lastEdited = in.readLong();
}
@Override
public int describeContents() {
return 0;
@@ -55,34 +84,4 @@ public class GalleryComment implements Parcelable {
dest.writeString(this.comment);
dest.writeLong(this.lastEdited);
}
public GalleryComment() {
}
protected GalleryComment(Parcel in) {
this.id = in.readLong();
this.score = in.readInt();
this.editable = in.readByte() != 0;
this.voteUpAble = in.readByte() != 0;
this.voteUpEd = in.readByte() != 0;
this.voteDownAble = in.readByte() != 0;
this.voteDownEd = in.readByte() != 0;
this.voteState = in.readString();
this.time = in.readLong();
this.user = in.readString();
this.comment = in.readString();
this.lastEdited = in.readLong();
}
public static final Creator<GalleryComment> CREATOR = new Creator<GalleryComment>() {
@Override
public GalleryComment createFromParcel(Parcel source) {
return new GalleryComment(source);
}
@Override
public GalleryComment[] newArray(int size) {
return new GalleryComment[size];
}
};
}
@@ -18,48 +18,48 @@ package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
public class GalleryCommentList implements Parcelable {
public GalleryComment[] comments;
public boolean hasMore;
public static final Creator<GalleryCommentList> CREATOR = new Creator<GalleryCommentList>() {
@Override
public GalleryCommentList createFromParcel(Parcel in) {
return new GalleryCommentList(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public GalleryCommentList[] newArray(int size) {
return new GalleryCommentList[size];
}
};
public GalleryComment[] comments;
public boolean hasMore;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelableArray(comments, flags);
dest.writeByte(hasMore ? (byte) 1 : (byte) 0);
}
public GalleryCommentList(GalleryComment[] comments, boolean hasMore) {
this.comments = comments;
this.hasMore = hasMore;
}
protected GalleryCommentList(Parcel in) {
Parcelable[] array = in.readParcelableArray(getClass().getClassLoader());
if (array != null) {
comments = Arrays.copyOf(array, array.length, GalleryComment[].class);
} else {
comments = null;
public GalleryCommentList(GalleryComment[] comments, boolean hasMore) {
this.comments = comments;
this.hasMore = hasMore;
}
hasMore = in.readByte() != 0;
}
public static final Creator<GalleryCommentList> CREATOR = new Creator<GalleryCommentList>() {
@Override
public GalleryCommentList createFromParcel(Parcel in) {
return new GalleryCommentList(in);
protected GalleryCommentList(Parcel in) {
Parcelable[] array = in.readParcelableArray(getClass().getClassLoader());
if (array != null) {
comments = Arrays.copyOf(array, array.length, GalleryComment[].class);
} else {
comments = null;
}
hasMore = in.readByte() != 0;
}
@Override
public GalleryCommentList[] newArray(int size) {
return new GalleryCommentList[size];
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelableArray(comments, flags);
dest.writeByte(hasMore ? (byte) 1 : (byte) 0);
}
};
}
@@ -23,6 +23,17 @@ import java.util.Arrays;
public class GalleryDetail extends GalleryInfo {
public static final Creator<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
@Override
public GalleryDetail createFromParcel(Parcel source) {
return new GalleryDetail(source);
}
@Override
public GalleryDetail[] newArray(int size) {
return new GalleryDetail[size];
}
};
public long apiUid = -1L;
public String apiKey;
public int torrentCount;
@@ -41,31 +52,6 @@ public class GalleryDetail extends GalleryInfo {
public int previewPages;
public PreviewSet previewSet;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.torrentCount);
dest.writeString(this.torrentUrl);
dest.writeString(this.archiveUrl);
dest.writeString(this.parent);
dest.writeString(this.visible);
dest.writeString(this.language);
dest.writeString(this.size);
dest.writeInt(this.pages);
dest.writeInt(this.favoriteCount);
dest.writeByte(isFavorited ? (byte) 1 : (byte) 0);
dest.writeInt(this.ratingCount);
dest.writeParcelableArray(this.tags, flags);
dest.writeParcelable(this.comments, flags);
dest.writeInt(this.previewPages);
dest.writeParcelable(previewSet, flags);
}
public GalleryDetail() {
}
@@ -93,15 +79,28 @@ public class GalleryDetail extends GalleryInfo {
this.previewSet = in.readParcelable(PreviewSet.class.getClassLoader());
}
public static final Creator<GalleryDetail> CREATOR = new Creator<GalleryDetail>() {
@Override
public GalleryDetail createFromParcel(Parcel source) {
return new GalleryDetail(source);
}
@Override
public int describeContents() {
return 0;
}
@Override
public GalleryDetail[] newArray(int size) {
return new GalleryDetail[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.torrentCount);
dest.writeString(this.torrentUrl);
dest.writeString(this.archiveUrl);
dest.writeString(this.parent);
dest.writeString(this.visible);
dest.writeString(this.language);
dest.writeString(this.size);
dest.writeInt(this.pages);
dest.writeInt(this.favoriteCount);
dest.writeByte(isFavorited ? (byte) 1 : (byte) 0);
dest.writeInt(this.ratingCount);
dest.writeParcelableArray(this.tags, flags);
dest.writeParcelable(this.comments, flags);
dest.writeInt(this.previewPages);
dest.writeParcelable(previewSet, flags);
}
}
@@ -18,7 +18,9 @@ package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import java.util.regex.Pattern;
public class GalleryInfo implements Parcelable {
@@ -79,23 +81,34 @@ public class GalleryInfo implements Parcelable {
};
public static final String[] S_LANG_TAGS = {
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch",
"language:english",
"language:chinese",
"language:spanish",
"language:korean",
"language:russian",
"language:french",
"language:portuguese",
"language:thai",
"language:german",
"language:italian",
"language:vietnamese",
"language:polish",
"language:hungarian",
"language:dutch",
};
public static final Parcelable.Creator<GalleryInfo> CREATOR = new Parcelable.Creator<GalleryInfo>() {
public long gid ;
@Override
public GalleryInfo createFromParcel(Parcel source) {
return new GalleryInfo(source);
}
@Override
public GalleryInfo[] newArray(int size) {
return new GalleryInfo[size];
}
};
public long gid;
public String token;
public String title;
public String titleJpn;
@@ -108,22 +121,43 @@ public class GalleryInfo implements Parcelable {
@Nullable
public String[] simpleTags;
public int pages;
public int thumbWidth;
public int thumbHeight;
public int spanSize;
public int spanIndex;
public int spanGroupIndex;
/**
* language from title
*/
public String simpleLanguage;
public int favoriteSlot = -2;
public String favoriteName;
public GalleryInfo() {
}
protected GalleryInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.thumb = in.readString();
this.category = in.readInt();
this.posted = in.readString();
this.uploader = in.readString();
this.rating = in.readFloat();
this.rated = in.readByte() != 0;
this.simpleLanguage = in.readString();
this.simpleTags = in.createStringArray();
this.thumbWidth = in.readInt();
this.thumbHeight = in.readInt();
this.spanSize = in.readInt();
this.spanIndex = in.readInt();
this.spanGroupIndex = in.readInt();
this.favoriteSlot = in.readInt();
this.favoriteName = in.readString();
}
public final void generateSLang() {
if (simpleTags != null) {
generateSLangFromTags();
@@ -181,41 +215,4 @@ public class GalleryInfo implements Parcelable {
dest.writeInt(this.favoriteSlot);
dest.writeString(this.favoriteName);
}
public GalleryInfo() {}
protected GalleryInfo(Parcel in) {
this.gid = in.readLong();
this.token = in.readString();
this.title = in.readString();
this.titleJpn = in.readString();
this.thumb = in.readString();
this.category = in.readInt();
this.posted = in.readString();
this.uploader = in.readString();
this.rating = in.readFloat();
this.rated = in.readByte() != 0;
this.simpleLanguage = in.readString();
this.simpleTags = in.createStringArray();
this.thumbWidth = in.readInt();
this.thumbHeight = in.readInt();
this.spanSize = in.readInt();
this.spanIndex = in.readInt();
this.spanGroupIndex = in.readInt();
this.favoriteSlot = in.readInt();
this.favoriteName = in.readString();
}
public static final Parcelable.Creator<GalleryInfo> CREATOR = new Parcelable.Creator<GalleryInfo>() {
@Override
public GalleryInfo createFromParcel(Parcel source) {
return new GalleryInfo(source);
}
@Override
public GalleryInfo[] newArray(int size) {
return new GalleryInfo[size];
}
};
}
@@ -23,6 +23,17 @@ import com.hippo.widget.LoadImageView;
public class GalleryPreview implements Parcelable {
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
@Override
public GalleryPreview createFromParcel(Parcel source) {
return new GalleryPreview(source);
}
@Override
public GalleryPreview[] newArray(int size) {
return new GalleryPreview[size];
}
};
String imageKey;
String imageUrl;
String pageUrl;
@@ -32,6 +43,19 @@ public class GalleryPreview implements Parcelable {
int clipWidth = Integer.MIN_VALUE;
int clipHeight = Integer.MIN_VALUE;
public GalleryPreview() {
}
protected GalleryPreview(Parcel in) {
this.imageUrl = in.readString();
this.pageUrl = in.readString();
this.position = in.readInt();
this.offsetX = in.readInt();
this.offsetY = in.readInt();
this.clipWidth = in.readInt();
this.clipHeight = in.readInt();
}
public int getPosition() {
return position;
}
@@ -56,29 +80,4 @@ public class GalleryPreview implements Parcelable {
dest.writeInt(this.clipWidth);
dest.writeInt(this.clipHeight);
}
public GalleryPreview() {
}
protected GalleryPreview(Parcel in) {
this.imageUrl = in.readString();
this.pageUrl = in.readString();
this.position = in.readInt();
this.offsetX = in.readInt();
this.offsetY = in.readInt();
this.clipWidth = in.readInt();
this.clipHeight = in.readInt();
}
public static final Parcelable.Creator<GalleryPreview> CREATOR = new Parcelable.Creator<GalleryPreview>() {
@Override
public GalleryPreview createFromParcel(Parcel source) {
return new GalleryPreview(source);
}
@Override
public GalleryPreview[] newArray(int size) {
return new GalleryPreview[size];
}
};
}
@@ -24,8 +24,28 @@ import java.util.ArrayList;
// TODO Add url field?
public class GalleryTagGroup implements Parcelable {
public String groupName;
public static final Parcelable.Creator<GalleryTagGroup> CREATOR = new Parcelable.Creator<GalleryTagGroup>() {
@Override
public GalleryTagGroup createFromParcel(Parcel source) {
return new GalleryTagGroup(source);
}
@Override
public GalleryTagGroup[] newArray(int size) {
return new GalleryTagGroup[size];
}
};
private final ArrayList<String> mTagList;
public String groupName;
public GalleryTagGroup() {
mTagList = new ArrayList<>();
}
protected GalleryTagGroup(Parcel in) {
this.groupName = in.readString();
this.mTagList = in.createStringArrayList();
}
public void addTag(String tag) {
mTagList.add(tag);
@@ -49,25 +69,4 @@ public class GalleryTagGroup implements Parcelable {
dest.writeString(this.groupName);
dest.writeStringList(this.mTagList);
}
public GalleryTagGroup() {
mTagList = new ArrayList<>();
}
protected GalleryTagGroup(Parcel in) {
this.groupName = in.readString();
this.mTagList = in.createStringArrayList();
}
public static final Parcelable.Creator<GalleryTagGroup> CREATOR = new Parcelable.Creator<GalleryTagGroup>() {
@Override
public GalleryTagGroup createFromParcel(Parcel source) {
return new GalleryTagGroup(source);
}
@Override
public GalleryTagGroup[] newArray(int size) {
return new GalleryTagGroup[size];
}
};
}
@@ -27,10 +27,33 @@ import java.util.ArrayList;
public class LargePreviewSet extends PreviewSet {
public static final Parcelable.Creator<LargePreviewSet> CREATOR = new Parcelable.Creator<LargePreviewSet>() {
@Override
public LargePreviewSet createFromParcel(Parcel source) {
return new LargePreviewSet(source);
}
@Override
public LargePreviewSet[] newArray(int size) {
return new LargePreviewSet[size];
}
};
private final IntList mPositionList;
private final ArrayList<String> mImageUrlList;
private final ArrayList<String> mPageUrlList;
public LargePreviewSet() {
mPositionList = new IntList();
mImageUrlList = new ArrayList<>();
mPageUrlList = new ArrayList<>();
}
protected LargePreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageUrlList = in.createStringArrayList();
this.mPageUrlList = in.createStringArrayList();
}
public void addItem(int index, String imageUrl, String pageUrl) {
mPositionList.add(index);
mImageUrlList.add(imageUrl);
@@ -80,28 +103,4 @@ public class LargePreviewSet extends PreviewSet {
dest.writeStringList(this.mImageUrlList);
dest.writeStringList(this.mPageUrlList);
}
public LargePreviewSet() {
mPositionList = new IntList();
mImageUrlList = new ArrayList<>();
mPageUrlList = new ArrayList<>();
}
protected LargePreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageUrlList = in.createStringArrayList();
this.mPageUrlList = in.createStringArrayList();
}
public static final Parcelable.Creator<LargePreviewSet> CREATOR = new Parcelable.Creator<LargePreviewSet>() {
@Override
public LargePreviewSet createFromParcel(Parcel source) {
return new LargePreviewSet(source);
}
@Override
public LargePreviewSet[] newArray(int size) {
return new LargePreviewSet[size];
}
};
}
@@ -19,8 +19,10 @@ package com.hippo.ehviewer.client.data;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
@@ -29,6 +31,7 @@ import com.hippo.ehviewer.widget.AdvanceSearchTable;
import com.hippo.network.UrlBuilder;
import com.hippo.yorozuya.NumberUtils;
import com.hippo.yorozuya.StringUtils;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,10 +40,6 @@ import java.net.URLEncoder;
public class ListUrlBuilder implements Cloneable, Parcelable {
@IntDef({MODE_NORMAL, MODE_UPLOADER, MODE_TAG, MODE_WHATS_HOT, MODE_IMAGE_SEARCH, MODE_SUBSCRIPTION})
@Retention(RetentionPolicy.SOURCE)
private @interface Mode {}
// Mode
public static final int MODE_NORMAL = 0x0;
public static final int MODE_UPLOADER = 0x1;
@@ -48,10 +47,19 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
public static final int MODE_WHATS_HOT = 0x3;
public static final int MODE_IMAGE_SEARCH = 0x4;
public static final int MODE_SUBSCRIPTION = 0x5;
public static final int DEFAULT_ADVANCE = AdvanceSearchTable.SNAME | AdvanceSearchTable.STAGS;
public static final int DEFAULT_MIN_RATING = 2;
public static final Creator<ListUrlBuilder> CREATOR = new Creator<ListUrlBuilder>() {
@Override
public ListUrlBuilder createFromParcel(Parcel source) {
return new ListUrlBuilder(source);
}
@Override
public ListUrlBuilder[] newArray(int size) {
return new ListUrlBuilder[size];
}
};
@Mode
private int mMode = MODE_NORMAL;
@@ -70,6 +78,25 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
private boolean mOnlySearchCovers;
private boolean mShowExpunged;
public ListUrlBuilder() {
}
@SuppressWarnings("WrongConstant")
protected ListUrlBuilder(Parcel in) {
this.mMode = in.readInt();
this.mPageIndex = in.readInt();
this.mCategory = in.readInt();
this.mKeyword = in.readString();
this.mAdvanceSearch = in.readInt();
this.mMinRating = in.readInt();
this.mPageFrom = in.readInt();
this.mPageTo = in.readInt();
this.mImagePath = in.readString();
this.mUseSimilarityScan = in.readByte() != 0;
this.mOnlySearchCovers = in.readByte() != 0;
this.mShowExpunged = in.readByte() != 0;
}
/**
* Make this ListUrlBuilder point to homepage
*/
@@ -197,6 +224,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
/**
* Make them the same
*
* @param lub The template
*/
public void set(ListUrlBuilder lub) {
@@ -263,11 +291,7 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
if (q.pageFrom != mPageFrom) {
return false;
}
if (q.pageTo != mPageTo) {
return false;
}
return true;
return q.pageTo == mPageTo;
}
/**
@@ -497,17 +521,23 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
// Advance search
if (mAdvanceSearch != -1) {
ub.addQuery("advsearch", "1");
if((mAdvanceSearch & AdvanceSearchTable.SNAME) != 0) ub.addQuery("f_sname", "on");
if((mAdvanceSearch & AdvanceSearchTable.STAGS) != 0) ub.addQuery("f_stags", "on");
if((mAdvanceSearch & AdvanceSearchTable.SDESC) != 0) ub.addQuery("f_sdesc", "on");
if((mAdvanceSearch & AdvanceSearchTable.STORR) != 0) ub.addQuery("f_storr", "on");
if((mAdvanceSearch & AdvanceSearchTable.STO) != 0) ub.addQuery("f_sto", "on");
if((mAdvanceSearch & AdvanceSearchTable.SDT1) != 0) ub.addQuery("f_sdt1", "on");
if((mAdvanceSearch & AdvanceSearchTable.SDT2) != 0) ub.addQuery("f_sdt2", "on");
if((mAdvanceSearch & AdvanceSearchTable.SH) != 0) ub.addQuery("f_sh", "on");
if((mAdvanceSearch & AdvanceSearchTable.SFL) != 0) ub.addQuery("f_sfl", "on");
if((mAdvanceSearch & AdvanceSearchTable.SFU) != 0) ub.addQuery("f_sfu", "on");
if((mAdvanceSearch & AdvanceSearchTable.SFT) != 0) ub.addQuery("f_sft", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SNAME) != 0)
ub.addQuery("f_sname", "on");
if ((mAdvanceSearch & AdvanceSearchTable.STAGS) != 0)
ub.addQuery("f_stags", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SDESC) != 0)
ub.addQuery("f_sdesc", "on");
if ((mAdvanceSearch & AdvanceSearchTable.STORR) != 0)
ub.addQuery("f_storr", "on");
if ((mAdvanceSearch & AdvanceSearchTable.STO) != 0) ub.addQuery("f_sto", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SDT1) != 0)
ub.addQuery("f_sdt1", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SDT2) != 0)
ub.addQuery("f_sdt2", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SH) != 0) ub.addQuery("f_sh", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SFL) != 0) ub.addQuery("f_sfl", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SFU) != 0) ub.addQuery("f_sfu", "on");
if ((mAdvanceSearch & AdvanceSearchTable.SFT) != 0) ub.addQuery("f_sft", "on");
// Set min star
if (mMinRating != -1) {
ub.addQuery("f_sr", "on");
@@ -576,34 +606,8 @@ public class ListUrlBuilder implements Cloneable, Parcelable {
dest.writeByte(mShowExpunged ? (byte) 1 : (byte) 0);
}
public ListUrlBuilder() {
@IntDef({MODE_NORMAL, MODE_UPLOADER, MODE_TAG, MODE_WHATS_HOT, MODE_IMAGE_SEARCH, MODE_SUBSCRIPTION})
@Retention(RetentionPolicy.SOURCE)
private @interface Mode {
}
@SuppressWarnings("WrongConstant")
protected ListUrlBuilder(Parcel in) {
this.mMode = in.readInt();
this.mPageIndex = in.readInt();
this.mCategory = in.readInt();
this.mKeyword = in.readString();
this.mAdvanceSearch = in.readInt();
this.mMinRating = in.readInt();
this.mPageFrom = in.readInt();
this.mPageTo = in.readInt();
this.mImagePath = in.readString();
this.mUseSimilarityScan = in.readByte() != 0;
this.mOnlySearchCovers = in.readByte() != 0;
this.mShowExpunged = in.readByte() != 0;
}
public static final Creator<ListUrlBuilder> CREATOR = new Creator<ListUrlBuilder>() {
@Override
public ListUrlBuilder createFromParcel(Parcel source) {
return new ListUrlBuilder(source);
}
@Override
public ListUrlBuilder[] newArray(int size) {
return new ListUrlBuilder[size];
}
};
}
@@ -25,6 +25,17 @@ import java.util.ArrayList;
public class NormalPreviewSet extends PreviewSet {
public static final Creator<NormalPreviewSet> CREATOR = new Creator<NormalPreviewSet>() {
@Override
public NormalPreviewSet createFromParcel(Parcel source) {
return new NormalPreviewSet(source);
}
@Override
public NormalPreviewSet[] newArray(int size) {
return new NormalPreviewSet[size];
}
};
private IntList mPositionList = new IntList();
private ArrayList<String> mImageKeyList = new ArrayList<>();
private ArrayList<String> mImageUrlList = new ArrayList<>();
@@ -34,6 +45,20 @@ public class NormalPreviewSet extends PreviewSet {
private IntList mClipHeightList = new IntList();
private ArrayList<String> mPageUrlList = new ArrayList<>();
public NormalPreviewSet() {
}
protected NormalPreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageKeyList = in.createStringArrayList();
this.mImageUrlList = in.createStringArrayList();
this.mOffsetXList = in.readParcelable(IntList.class.getClassLoader());
this.mOffsetYList = in.readParcelable(IntList.class.getClassLoader());
this.mClipWidthList = in.readParcelable(IntList.class.getClassLoader());
this.mClipHeightList = in.readParcelable(IntList.class.getClassLoader());
this.mPageUrlList = in.createStringArrayList();
}
private String getImageKey(String imageUrl) {
int index = imageUrl.indexOf('/');
if (index >= 0) {
@@ -44,7 +69,7 @@ public class NormalPreviewSet extends PreviewSet {
}
public void addItem(int position, String imageUrl, int xOffset, int yOffset, int width,
int height, String pageUrl) {
int height, String pageUrl) {
mPositionList.add(position);
mImageKeyList.add(getImageKey(imageUrl));
mImageUrlList.add(imageUrl);
@@ -107,30 +132,4 @@ public class NormalPreviewSet extends PreviewSet {
dest.writeParcelable(this.mClipHeightList, flags);
dest.writeStringList(this.mPageUrlList);
}
public NormalPreviewSet() {
}
protected NormalPreviewSet(Parcel in) {
this.mPositionList = in.readParcelable(IntList.class.getClassLoader());
this.mImageKeyList = in.createStringArrayList();
this.mImageUrlList = in.createStringArrayList();
this.mOffsetXList = in.readParcelable(IntList.class.getClassLoader());
this.mOffsetYList = in.readParcelable(IntList.class.getClassLoader());
this.mClipWidthList = in.readParcelable(IntList.class.getClassLoader());
this.mClipHeightList = in.readParcelable(IntList.class.getClassLoader());
this.mPageUrlList = in.createStringArrayList();
}
public static final Creator<NormalPreviewSet> CREATOR = new Creator<NormalPreviewSet>() {
@Override
public NormalPreviewSet createFromParcel(Parcel source) {
return new NormalPreviewSet(source);
}
@Override
public NormalPreviewSet[] newArray(int size) {
return new NormalPreviewSet[size];
}
};
}
@@ -24,21 +24,15 @@ import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.JsoupUtils;
import com.hippo.yorozuya.AssertUtils;
import java.util.List;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class FavoritesParser {
import java.util.List;
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public int pages;
public int nextPage;
public List<GalleryInfo> galleryInfoList;
}
public class FavoritesParser {
public static Result parse(String body) throws Exception {
if (body.contains("This page requires you to log on.</p>")) {
@@ -77,4 +71,12 @@ public class FavoritesParser {
return re;
}
public static class Result {
public String[] catArray; // Size 10
public int[] countArray; // Size 10
public int pages;
public int nextPage;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -19,6 +19,7 @@ package com.hippo.ehviewer.client.parser;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -18,8 +18,8 @@ package com.hippo.ehviewer.client.parser;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.yorozuya.NumberUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.client.parser;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
@@ -37,6 +38,15 @@ import com.hippo.util.JsoupUtils;
import com.hippo.util.MutableBoolean;
import com.hippo.yorozuya.NumberUtils;
import com.hippo.yorozuya.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import org.jsoup.select.NodeTraversor;
import org.jsoup.select.NodeVisitor;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -47,13 +57,6 @@ import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;
import org.jsoup.select.NodeTraversor;
import org.jsoup.select.NodeVisitor;
public class GalleryDetailParser {
@@ -74,16 +77,15 @@ public class GalleryDetailParser {
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
private static final DateFormat WEB_COMMENT_DATE_FORMAT = new SimpleDateFormat("dd MMMMM yyyy, HH:mm z", Locale.US);
static {
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
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>";
static {
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public static GalleryDetail parse(String body) throws EhException {
if (body.contains(OFFENSIVE_STRING)) {
throw new OffensiveException();
@@ -491,7 +493,8 @@ public class GalleryDetailParser {
}
@Override
public void tail(Node node, int depth) { }
public void tail(Node node, int depth) {
}
}, chd);
return new GalleryCommentList(list.toArray(new GalleryComment[list.size()]), hasMore.value);
@@ -663,8 +666,8 @@ public class GalleryDetailParser {
continue;
}
String imageUrl = ParserUtils.trim(m.group(3));
int xOffset = ParserUtils.parseInt(m.group(4), 0);
int yOffset = 0;
int xOffset = ParserUtils.parseInt(m.group(4), 0);
int yOffset = 0;
int width = ParserUtils.parseInt(m.group(1), 0);
if (width <= 0) {
continue;
@@ -17,14 +17,15 @@
package com.hippo.ehviewer.client.parser;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.yorozuya.NumberUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Like http://exhentai.org/g/1234567/a1b2c3d4e5<br>
*
*/
public final class GalleryDetailUrlParser {
@@ -18,7 +18,9 @@ package com.hippo.ehviewer.client.parser;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryInfo;
@@ -27,15 +29,17 @@ import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
import com.hippo.util.JsoupUtils;
import com.hippo.yorozuya.NumberUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class GalleryListParser {
@@ -47,26 +51,19 @@ public class GalleryListParser {
private static final Pattern PATTERN_PAGES = Pattern.compile("(\\d+) page");
private static final Pattern PATTERN_NEXT_PAGE = Pattern.compile("page=(\\d+)");
private static final String[][] FAVORITE_SLOT_RGB = new String[][] {
new String[] { "0", "0", "0"},
new String[] { "240", "0", "0"},
new String[] { "240", "160", "0"},
new String[] { "208", "208", "0"},
new String[] { "0", "128", "0"},
new String[] { "144", "240", "64"},
new String[] { "64", "176", "240"},
new String[] { "0", "0", "240"},
new String[] { "80", "0", "128"},
new String[] { "224", "128", "224"},
private static final String[][] FAVORITE_SLOT_RGB = new String[][]{
new String[]{"0", "0", "0"},
new String[]{"240", "0", "0"},
new String[]{"240", "160", "0"},
new String[]{"208", "208", "0"},
new String[]{"0", "128", "0"},
new String[]{"144", "240", "64"},
new String[]{"64", "176", "240"},
new String[]{"0", "0", "240"},
new String[]{"80", "0", "128"},
new String[]{"224", "128", "224"},
};
public static class Result {
public int pages;
public int nextPage;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
private static int parsePages(Document d, String body) throws ParseException {
try {
Elements es = d.getElementsByClass("ptt").first().child(0).child(0).children();
@@ -357,4 +354,11 @@ public class GalleryListParser {
return result;
}
public static class Result {
public int pages;
public int nextPage;
public boolean noWatchedTags;
public List<GalleryInfo> galleryInfoList;
}
}
@@ -17,13 +17,16 @@
package com.hippo.ehviewer.client.parser;
import android.text.TextUtils;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.yorozuya.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GalleryPageApiParser {
private static final Pattern PATTERN_IMAGE_URL = Pattern.compile("<img[^>]*src=\"([^\"]+)\" style");
@@ -17,8 +17,10 @@
package com.hippo.ehviewer.client.parser;
import android.text.TextUtils;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.yorozuya.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -17,8 +17,10 @@
package com.hippo.ehviewer.client.parser;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.yorozuya.NumberUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -18,18 +18,19 @@ package com.hippo.ehviewer.client.parser;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.util.ExceptionUtils;
import org.json.JSONObject;
public class GalleryTokenApiParser {
/**
* {
* "tokenlist": [
* {
* "gid":618395,
* "token":"0439fa3666"
* }
* ]
* "tokenlist": [
* {
* "gid":618395,
* "token":"0439fa3666"
* }
* ]
* }
*/
public static String parse(String body) throws Exception {
@@ -18,9 +18,11 @@ package com.hippo.ehviewer.client.parser;
import android.text.TextUtils;
import android.util.Log;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -23,22 +23,21 @@ import org.json.JSONObject;
public class RateGalleryParser {
public static class Result {
public float rating;
public int ratingCount;
}
public static Result parse(String body) throws Exception {
try {
JSONObject jsonObject = new JSONObject(body);
Result result = new Result();
result.rating = (float)jsonObject.getDouble("rating_avg");
result.rating = (float) jsonObject.getDouble("rating_avg");
result.ratingCount = jsonObject.getInt("rating_cnt");
return result;
} catch (JSONException e) {
Exception exception = new ParseException("Can't parse rate gallery", body);
exception.initCause(e);
Exception exception = new ParseException("Can't parse rate gallery", body, e);
throw exception;
}
}
public static class Result {
public float rating;
public int ratingCount;
}
}
@@ -21,13 +21,6 @@ import org.json.JSONObject;
public class VoteCommentParser {
public static class Result {
public long id;
public int score;
public int vote;
public int expectVote;
}
// {"comment_id":1253922,"comment_score":-19,"comment_vote":0}
public static Result parse(String body, int vote) throws JSONException {
Result result = new Result();
@@ -38,4 +31,11 @@ public class VoteCommentParser {
result.expectVote = vote;
return result;
}
public static class Result {
public long id;
public int score;
public int vote;
public int expectVote;
}
}
@@ -20,8 +20,10 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.DownloadInfo;
@@ -39,6 +41,7 @@ import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.collect.LongList;
import com.hippo.yorozuya.collect.SparseIJArray;
import com.hippo.yorozuya.collect.SparseJLArray;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -52,9 +55,13 @@ import java.util.Map;
public class DownloadManager implements SpiderQueen.OnSpiderListener {
private static final String TAG = DownloadManager.class.getSimpleName();
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = new Comparator<DownloadInfo>() {
@Override
public int compare(DownloadInfo lhs, DownloadInfo rhs) {
return lhs.time - rhs.time > 0 ? -1 : 1;
}
};
private final Context mContext;
// All download info list
private final LinkedList<DownloadInfo> mAllInfoList;
// All download info map
@@ -67,20 +74,16 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
private final LinkedList<DownloadInfo> mDefaultInfoList;
// Store download info wait to start
private final LinkedList<DownloadInfo> mWaitList;
private final SpeedReminder mSpeedReminder;
private final List<DownloadInfoListener> mDownloadInfoListeners;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
@Nullable
private DownloadListener mDownloadListener;
private final List<DownloadInfoListener> mDownloadInfoListeners;
@Nullable
private DownloadInfo mCurrentTask;
@Nullable
private SpiderQueen mCurrentSpider;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(5);
public DownloadManager(Context context) {
mContext = context;
@@ -145,7 +148,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
return false;
}
for (DownloadLabel raw: mLabelList) {
for (DownloadLabel raw : mLabelList) {
if (label.equals(raw.getLabel())) {
return true;
}
@@ -230,7 +233,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Notify state update
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -256,7 +259,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Notify state update
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -289,7 +292,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
EhDB.putDownloadInfo(info);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onAdd(info, list, list.size() - 1);
}
// Make sure download is running
@@ -326,7 +329,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
if (update) {
// Notify Listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateAll();
}
// Ensure download
@@ -339,7 +342,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Start all STATE_NONE and STATE_FAILED item
LinkedList<DownloadInfo> allInfoList = mAllInfoList;
LinkedList<DownloadInfo> waitList = mWaitList;
for (DownloadInfo info: allInfoList) {
for (DownloadInfo info : allInfoList) {
if (info.state == DownloadInfo.STATE_NONE || info.state == DownloadInfo.STATE_FAILED) {
update = true;
// Set state DownloadInfo.STATE_WAIT
@@ -353,7 +356,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
if (update) {
// Notify Listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateAll();
}
// Ensure download
@@ -362,7 +365,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
public void addDownload(List<DownloadInfo> downloadInfoList) {
for (DownloadInfo info: downloadInfoList) {
for (DownloadInfo info : downloadInfoList) {
if (containDownloadInfo(info.gid)) {
// Contain
return;
@@ -401,13 +404,13 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
Collections.sort(mAllInfoList, DATE_DESC_COMPARATOR);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onReload();
}
}
public void addDownloadLabel(List<DownloadLabel> downloadLabelList) {
for (DownloadLabel label: downloadLabelList) {
for (DownloadLabel label : downloadLabelList) {
String labelString = label.getLabel();
if (!containLabel(labelString)) {
mMap.put(labelString, new LinkedList<DownloadInfo>());
@@ -444,19 +447,18 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
EhDB.putDownloadInfo(info);
// Notify
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onAdd(info, list, list.size() - 1);
}
}
public void stopDownload(long gid) {
DownloadInfo info = stopDownloadInternal(gid);
if (info != null) {
// Update listener
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -471,7 +473,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Update listener
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -484,7 +486,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
stopRangeDownloadInternal(gidList);
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateAll();
}
@@ -505,7 +507,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
stopCurrentDownloadInternal();
// Notify mDownloadInfoListener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateAll();
}
}
@@ -528,7 +530,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
if (index >= 0) {
list.remove(info);
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onRemove(info, list, index);
}
}
@@ -565,7 +567,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Update listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onReload();
}
@@ -626,7 +628,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
return stopCurrentDownloadInternal();
}
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext();) {
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext(); ) {
DownloadInfo info = iterator.next();
if (info.gid == gid) {
// Remove from wait list
@@ -686,7 +688,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Check all in wait list
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext();) {
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext(); ) {
DownloadInfo info = iterator.next();
if (gidList.contains(info.gid)) {
// Remove from wait list
@@ -715,7 +717,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
return;
}
for (DownloadInfo info: list) {
for (DownloadInfo info : list) {
if (ObjectUtils.equal(info.label, label)) {
continue;
}
@@ -735,7 +737,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
EhDB.putDownloadInfo(info);
}
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onReload();
}
}
@@ -748,7 +750,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
mLabelList.add(EhDB.addDownloadLabel(label));
mMap.put(label, new LinkedList<DownloadInfo>());
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateLabels();
}
}
@@ -758,7 +760,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
mLabelList.add(toPosition, item);
EhDB.moveDownloadLabel(fromPosition, toPosition);
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdateLabels();
}
}
@@ -766,7 +768,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
public void renameLabel(@NonNull String from, @NonNull String to) {
// Find in label list
boolean found = false;
for (DownloadLabel raw: mLabelList) {
for (DownloadLabel raw : mLabelList) {
if (from.equals(raw.getLabel())) {
found = true;
raw.setLabel(to);
@@ -785,7 +787,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Update info label
for (DownloadInfo info: list) {
for (DownloadInfo info : list) {
info.label = to;
// Update in DB
EhDB.putDownloadInfo(info);
@@ -794,7 +796,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
mMap.put(to, list);
// Notify listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onRenameLabel(from, to);
}
}
@@ -802,7 +804,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
public void deleteLabel(@NonNull String label) {
// Find in label list and remove
boolean found = false;
for (Iterator<DownloadLabel> iterator = mLabelList.iterator(); iterator.hasNext();) {
for (Iterator<DownloadLabel> iterator = mLabelList.iterator(); iterator.hasNext(); ) {
DownloadLabel raw = iterator.next();
if (label.equals(raw.getLabel())) {
found = true;
@@ -821,7 +823,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
// Update info label
for (DownloadInfo info: list) {
for (DownloadInfo info : list) {
info.label = null;
// Update in DB
EhDB.putDownloadInfo(info);
@@ -832,7 +834,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
Collections.sort(mDefaultInfoList, DATE_DESC_COMPARATOR);
// Notify listener
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onChange();
}
}
@@ -911,6 +913,80 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Ignore
}
public interface DownloadInfoListener {
/**
* Add the special info to the special position
*/
void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
/**
* The special info is changed
*/
void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list);
/**
* Maybe all data is changed, but size is the same
*/
void onUpdateAll();
/**
* Maybe all data is changed, maybe list is changed
*/
void onReload();
/**
* The list is gone, use default list please
*/
void onChange();
/**
* Rename label
*/
void onRenameLabel(String from, String to);
/**
* Remove the special info from the special position
*/
void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
void onUpdateLabels();
}
public interface DownloadListener {
/**
* Get 509 error
*/
void onGet509();
/**
* Start download
*/
void onStart(DownloadInfo info);
/**
* Update download speed
*/
void onDownload(DownloadInfo info);
/**
* Update page downloaded
*/
void onGetPage(DownloadInfo info);
/**
* Download done
*/
void onFinish(DownloadInfo info);
/**
* Download done
*/
void onCancel(DownloadInfo info);
}
private class NotifyTask implements Runnable {
public static final int TYPE_ON_GET_PAGES = 0;
@@ -985,7 +1061,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
info.total = mPages;
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -1016,7 +1092,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -1034,7 +1110,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
info.total = mTotal;
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -1078,7 +1154,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -1092,16 +1168,13 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
class SpeedReminder implements Runnable {
private boolean mStop = true;
private long mBytesRead;
private long oldSpeed = -1;
private final SparseIJArray mContentLengthMap = new SparseIJArray();
private final SparseIJArray mReceivedSizeMap = new SparseIJArray();
private boolean mStop = true;
private long mBytesRead;
private long oldSpeed = -1;
public void start() {
if (mStop) {
@@ -1174,7 +1247,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
List<DownloadInfo> list = getInfoListForLabel(info.label);
if (list != null) {
for (DownloadInfoListener l: mDownloadInfoListeners) {
for (DownloadInfoListener l : mDownloadInfoListeners) {
l.onUpdate(info, list);
}
}
@@ -1187,84 +1260,4 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
}
}
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = new Comparator<DownloadInfo>() {
@Override
public int compare(DownloadInfo lhs, DownloadInfo rhs) {
return lhs.time - rhs.time > 0 ? -1 : 1;
}
};
public interface DownloadInfoListener {
/**
* Add the special info to the special position
*/
void onAdd(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
/**
* The special info is changed
*/
void onUpdate(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list);
/**
* Maybe all data is changed, but size is the same
*/
void onUpdateAll();
/**
* Maybe all data is changed, maybe list is changed
*/
void onReload();
/**
* The list is gone, use default list please
*/
void onChange();
/**
* Rename label
*/
void onRenameLabel(String from, String to);
/**
* Remove the special info from the special position
*/
void onRemove(@NonNull DownloadInfo info, @NonNull List<DownloadInfo> list, int position);
void onUpdateLabels();
}
public interface DownloadListener {
/**
* Get 509 error
*/
void onGet509();
/**
* Start download
*/
void onStart(DownloadInfo info);
/**
* Update download speed
*/
void onDownload(DownloadInfo info);
/**
* Update page downloaded
*/
void onGetPage(DownloadInfo info);
/**
* Download done
*/
void onFinish(DownloadInfo info);
/**
* Download done
*/
void onCancel(DownloadInfo info);
}
}
@@ -28,9 +28,11 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhUtils;
@@ -45,6 +47,7 @@ import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.collect.LongList;
import com.hippo.yorozuya.collect.SparseJBArray;
import com.hippo.yorozuya.collect.SparseJLArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -70,7 +73,11 @@ public class DownloadService extends Service implements DownloadManager.Download
private static final int ID_DOWNLOADING = 1;
private static final int ID_DOWNLOADED = 2;
private static final int ID_509 = 3;
private final static SparseJBArray sItemStateArray = new SparseJBArray();
private final static SparseJLArray<String> sItemTitleArray = new SparseJLArray<>();
private static int sFailedCount;
private static int sFinishedCount;
private static int sDownloadedCount;
@Nullable
private NotificationManager mNotifyManager;
@Nullable
@@ -81,15 +88,6 @@ public class DownloadService extends Service implements DownloadManager.Download
private NotificationDelay mDownloadingDelay;
private NotificationDelay mDownloadedDelay;
private NotificationDelay m509Delay;
private final static SparseJBArray sItemStateArray = new SparseJBArray();
private final static SparseJLArray<String> sItemTitleArray = new SparseJLArray<>();
private static int sFailedCount;
private static int sFinishedCount;
private static int sDownloadedCount;
private String CHANNEL_ID;
public static void clear() {
@@ -104,9 +102,9 @@ public class DownloadService extends Service implements DownloadManager.Download
public void onCreate() {
super.onCreate();
CHANNEL_ID = getPackageName()+".download";
CHANNEL_ID = getPackageName() + ".download";
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mNotifyManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, getString(R.string.download_service),
NotificationManager.IMPORTANCE_LOW));
}
@@ -433,7 +431,7 @@ public class DownloadService extends Service implements DownloadManager.Download
continue;
}
style.addLine(getString(fin ? R.string.stat_download_done_line_succeeded :
R.string.stat_download_done_line_failed, title));
R.string.stat_download_done_line_failed, title));
}
} else {
style = null;
@@ -473,29 +471,21 @@ public class DownloadService extends Service implements DownloadManager.Download
// Avoid frequent notification
private static class NotificationDelay implements Runnable {
@IntDef({OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND})
@Retention(RetentionPolicy.SOURCE)
private @interface Ops {}
private static final int OPS_NOTIFY = 0;
private static final int OPS_CANCEL = 1;
private static final int OPS_START_FOREGROUND = 2;
private static final long DELAY = 1000; // 1s
private Service mService;
private final NotificationManager mNotifyManager;
private final NotificationCompat.Builder mBuilder;
private final int mId;
private Service mService;
private long mLastTime;
private boolean mPosted;
// false for show, true for cancel
@Ops
private int mOps;
public NotificationDelay(Service service, NotificationManager notifyManager,
NotificationCompat.Builder builder, int id) {
NotificationCompat.Builder builder, int id) {
mService = service;
mNotifyManager = notifyManager;
mBuilder = builder;
@@ -577,5 +567,10 @@ public class DownloadService extends Service implements DownloadManager.Download
break;
}
}
@IntDef({OPS_NOTIFY, OPS_CANCEL, OPS_START_FOREGROUND})
@Retention(RetentionPolicy.SOURCE)
private @interface Ops {
}
}
}
@@ -23,6 +23,7 @@ import com.hippo.a7zip.PropID;
import com.hippo.a7zip.PropType;
import com.hippo.a7zip.SequentialOutStream;
import com.hippo.unifile.UniRandomAccessFile;
import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
@@ -31,126 +32,126 @@ import java.util.List;
class A7ZipArchive implements Closeable {
private InArchive archive;
private A7ZipArchive(InArchive archive) {
this.archive = archive;
}
@Override
public void close() {
archive.close();
}
private static boolean isSupportedFilename(String name) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
if (name.endsWith(extension)) {
return true;
}
}
return false;
}
List<A7ZipArchiveEntry> getArchiveEntries() {
List<A7ZipArchiveEntry> entries = new ArrayList<>();
for (int i = 0, n = archive.getNumberOfEntries(); i < n; i++) {
if (!archive.getEntryBooleanProperty(i, PropID.ENCRYPTED)
&& !archive.getEntryBooleanProperty(i, PropID.IS_DIR)
&& !archive.getEntryBooleanProperty(i, PropID.IS_VOLUME)
&& !archive.getEntryBooleanProperty(i, PropID.SOLID)) {
String path = archive.getEntryPath(i);
if (isSupportedFilename(path.toLowerCase())) {
entries.add(new A7ZipArchiveEntry(archive, i, path));
}
}
}
return entries;
}
static A7ZipArchive create(UniRandomAccessFile file) throws ArchiveException {
InStream store = new UniRandomAccessFileInStream(file);
InArchive archive = InArchive.open(store);
if ((archive.getArchivePropertyType(PropID.ENCRYPTED) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.ENCRYPTED))
|| (archive.getArchivePropertyType(PropID.SOLID) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.SOLID))
|| (archive.getArchivePropertyType(PropID.IS_VOLUME) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.IS_VOLUME))) {
throw new ArchiveException("Unsupported archive");
}
return new A7ZipArchive(archive);
}
static class A7ZipArchiveEntry {
private InArchive archive;
private int index;
private String path;
private A7ZipArchiveEntry(InArchive archive, int index, String path) {
this.archive = archive;
this.index = index;
this.path = path;
private A7ZipArchive(InArchive archive) {
this.archive = archive;
}
String getPath() {
return path;
private static boolean isSupportedFilename(String name) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
if (name.endsWith(extension)) {
return true;
}
}
return false;
}
void extract(OutputStream os) throws ArchiveException {
archive.extractEntry(index, new OutputStreamSequentialOutStream(os));
}
}
private static class UniRandomAccessFileInStream implements InStream {
private UniRandomAccessFile file;
public UniRandomAccessFileInStream(UniRandomAccessFile file) {
this.file = file;
static A7ZipArchive create(UniRandomAccessFile file) throws ArchiveException {
InStream store = new UniRandomAccessFileInStream(file);
InArchive archive = InArchive.open(store);
if ((archive.getArchivePropertyType(PropID.ENCRYPTED) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.ENCRYPTED))
|| (archive.getArchivePropertyType(PropID.SOLID) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.SOLID))
|| (archive.getArchivePropertyType(PropID.IS_VOLUME) == PropType.BOOL && archive.getArchiveBooleanProperty(PropID.IS_VOLUME))) {
throw new ArchiveException("Unsupported archive");
}
return new A7ZipArchive(archive);
}
@Override
public void seek(long pos) throws IOException {
file.seek(pos);
public void close() {
archive.close();
}
@Override
public long tell() throws IOException {
return file.getFilePointer();
List<A7ZipArchiveEntry> getArchiveEntries() {
List<A7ZipArchiveEntry> entries = new ArrayList<>();
for (int i = 0, n = archive.getNumberOfEntries(); i < n; i++) {
if (!archive.getEntryBooleanProperty(i, PropID.ENCRYPTED)
&& !archive.getEntryBooleanProperty(i, PropID.IS_DIR)
&& !archive.getEntryBooleanProperty(i, PropID.IS_VOLUME)
&& !archive.getEntryBooleanProperty(i, PropID.SOLID)) {
String path = archive.getEntryPath(i);
if (isSupportedFilename(path.toLowerCase())) {
entries.add(new A7ZipArchiveEntry(archive, i, path));
}
}
}
return entries;
}
@Override
public long size() throws IOException {
return file.length();
static class A7ZipArchiveEntry {
private InArchive archive;
private int index;
private String path;
private A7ZipArchiveEntry(InArchive archive, int index, String path) {
this.archive = archive;
this.index = index;
this.path = path;
}
String getPath() {
return path;
}
void extract(OutputStream os) throws ArchiveException {
archive.extractEntry(index, new OutputStreamSequentialOutStream(os));
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return file.read(b, off, len);
private static class UniRandomAccessFileInStream implements InStream {
private UniRandomAccessFile file;
public UniRandomAccessFileInStream(UniRandomAccessFile file) {
this.file = file;
}
@Override
public void seek(long pos) throws IOException {
file.seek(pos);
}
@Override
public long tell() throws IOException {
return file.getFilePointer();
}
@Override
public long size() throws IOException {
return file.length();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return file.read(b, off, len);
}
@Override
public void close() throws IOException {
file.close();
}
}
@Override
public void close() throws IOException {
file.close();
private static class OutputStreamSequentialOutStream implements SequentialOutStream {
private OutputStream stream;
public OutputStreamSequentialOutStream(OutputStream stream) {
this.stream = stream;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b, off, len);
}
@Override
public void close() throws IOException {
stream.close();
}
}
}
private static class OutputStreamSequentialOutStream implements SequentialOutStream {
private OutputStream stream;
public OutputStreamSequentialOutStream(OutputStream stream) {
this.stream = stream;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
stream.write(b, off, len);
}
@Override
public void close() throws IOException {
stream.close();
}
}
}
@@ -19,8 +19,10 @@ package com.hippo.ehviewer.gallery;
import android.content.Context;
import android.net.Uri;
import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.a7zip.ArchiveException;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
@@ -30,6 +32,7 @@ import com.hippo.unifile.UniFile;
import com.hippo.unifile.UniRandomAccessFile;
import com.hippo.util.NaturalComparator;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@@ -43,239 +46,235 @@ import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static Comparator<A7ZipArchive.A7ZipArchiveEntry> naturalComparator = new Comparator<A7ZipArchive.A7ZipArchiveEntry>() {
private NaturalComparator comparator = new NaturalComparator();
private final UniFile file;
private Thread archiveThread;
private Thread decodeThread;
private volatile int size = STATE_WAIT;
private String error;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
public ArchiveGalleryProvider(Context context, Uri uri) {
file = UniFile.fromUri(context, uri);
}
@Override
public void start() {
super.start();
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
new ArchiveTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
archiveThread.start();
decodeThread = new PriorityThread(
new DecodeTask(), "DecodeTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
decodeThread.start();
}
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
}
if (decodeThread != null) {
decodeThread.interrupt();
decodeThread = null;
}
}
@Override
public int size() {
return size;
}
@Override
protected void onRequest(int index) {
boolean inDecodeTask;
synchronized (streams) {
inDecodeTask = streams.keySet().contains(index) || index == decodingIndex.get();
}
synchronized (requests) {
boolean inArchiveTask = requests.contains(index) || index == extractingIndex.get();
if (!inArchiveTask && !inDecodeTask) {
requests.add(index);
requests.notify();
}
}
notifyPageWait(index);
}
@Override
protected void onForceRequest(int index) {
onRequest(index);
}
@Override
protected void onCancelRequest(int index) {
synchronized (requests) {
requests.remove(Integer.valueOf(index));
}
}
@Override
public String getError() {
return error;
}
@NonNull
@Override
public String getImageFilename(int index) {
// TODO
return Integer.toString(index);
}
@Override
public boolean save(int index, @NonNull UniFile file) {
// TODO
return false;
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
// TODO
return null;
}
private class ArchiveTask implements Runnable {
@Override
public void run() {
UniRandomAccessFile uraf = null;
if (file != null) {
try {
uraf = file.createRandomAccessFile("r");
} catch (IOException e) {
e.printStackTrace();
@Override
public int compare(A7ZipArchive.A7ZipArchiveEntry o1, A7ZipArchive.A7ZipArchiveEntry o2) {
return comparator.compare(o1.getPath(), o2.getPath());
}
}
if (uraf == null) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
notifyDataChanged();
return;
}
};
private final UniFile file;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private Thread archiveThread;
private Thread decodeThread;
private volatile int size = STATE_WAIT;
private String error;
A7ZipArchive archive = null;
try {
archive = A7ZipArchive.create(uraf);
} catch (ArchiveException e) {
e.printStackTrace();
}
if (archive == null) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_invalid_archive);
notifyDataChanged();
return;
}
public ArchiveGalleryProvider(Context context, Uri uri) {
file = UniFile.fromUri(context, uri);
}
List<A7ZipArchive.A7ZipArchiveEntry> entries = archive.getArchiveEntries();
Collections.sort(entries, naturalComparator);
@Override
public void start() {
super.start();
// Update size and notify changed
size = entries.size();
notifyDataChanged();
int id = sIdGenerator.incrementAndGet();
archiveThread = new PriorityThread(
new ArchiveTask(), "ArchiveTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
archiveThread.start();
decodeThread = new PriorityThread(
new DecodeTask(), "DecodeTask" + '-' + id, Process.THREAD_PRIORITY_BACKGROUND);
decodeThread.start();
}
@Override
public void stop() {
super.stop();
if (archiveThread != null) {
archiveThread.interrupt();
archiveThread = null;
}
if (decodeThread != null) {
decodeThread.interrupt();
decodeThread = null;
}
}
@Override
public int size() {
return size;
}
@Override
protected void onRequest(int index) {
boolean inDecodeTask;
synchronized (streams) {
inDecodeTask = streams.containsKey(index) || index == decodingIndex.get();
}
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
if (requests.isEmpty()) {
try {
requests.wait();
} catch (InterruptedException e) {
// Interrupted
break;
boolean inArchiveTask = requests.contains(index) || index == extractingIndex.get();
if (!inArchiveTask && !inDecodeTask) {
requests.add(index);
requests.notify();
}
continue;
}
index = requests.pop();
extractingIndex.lazySet(index);
}
// Check index valid
if (index < 0 || index >= entries.size()) {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
continue;
}
Pipe pipe = new Pipe(4 * 1024);
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
streams.put(index, pipe.getInputStream());
streams.notify();
}
try {
entries.get(index).extract(pipe.getOutputStream());
} catch (ArchiveException e) {
e.printStackTrace();
} finally {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
}
}
notifyPageWait(index);
}
}
private class DecodeTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
InputStream stream;
synchronized (streams) {
if (streams.isEmpty()) {
try {
streams.wait();
} catch (InterruptedException e) {
// Interrupted
break;
protected void onForceRequest(int index) {
onRequest(index);
}
@Override
protected void onCancelRequest(int index) {
synchronized (requests) {
requests.remove(Integer.valueOf(index));
}
}
@Override
public String getError() {
return error;
}
@NonNull
@Override
public String getImageFilename(int index) {
// TODO
return Integer.toString(index);
}
@Override
public boolean save(int index, @NonNull UniFile file) {
// TODO
return false;
}
@Nullable
@Override
public UniFile save(int index, @NonNull UniFile dir, @NonNull String filename) {
// TODO
return null;
}
private class ArchiveTask implements Runnable {
@Override
public void run() {
UniRandomAccessFile uraf = null;
if (file != null) {
try {
uraf = file.createRandomAccessFile("r");
} catch (IOException e) {
e.printStackTrace();
}
}
if (uraf == null) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
notifyDataChanged();
return;
}
continue;
}
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, InputStream> entry = iterator.next();
iterator.remove();
index = entry.getKey();
stream = entry.getValue();
decodingIndex.lazySet(index);
A7ZipArchive archive = null;
try {
archive = A7ZipArchive.create(uraf);
} catch (ArchiveException e) {
e.printStackTrace();
}
if (archive == null) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_invalid_archive);
notifyDataChanged();
return;
}
List<A7ZipArchive.A7ZipArchiveEntry> entries = archive.getArchiveEntries();
Collections.sort(entries, naturalComparator);
// Update size and notify changed
size = entries.size();
notifyDataChanged();
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
if (requests.isEmpty()) {
try {
requests.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
}
index = requests.pop();
extractingIndex.lazySet(index);
}
// Check index valid
if (index < 0 || index >= entries.size()) {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
notifyPageFailed(index, GetText.getString(R.string.error_out_of_range));
continue;
}
Pipe pipe = new Pipe(4 * 1024);
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
streams.put(index, pipe.getInputStream());
streams.notify();
}
try {
entries.get(index).extract(pipe.getOutputStream());
} catch (ArchiveException e) {
e.printStackTrace();
} finally {
extractingIndex.lazySet(GalleryPageView.INVALID_INDEX);
}
}
}
}
try {
Image image = Image.decode(stream, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
}
} finally {
decodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
private class DecodeTask implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
InputStream stream;
synchronized (streams) {
if (streams.isEmpty()) {
try {
streams.wait();
} catch (InterruptedException e) {
// Interrupted
break;
}
continue;
}
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, InputStream> entry = iterator.next();
iterator.remove();
index = entry.getKey();
stream = entry.getValue();
decodingIndex.lazySet(index);
}
try {
Image image = Image.decode(stream, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
}
} finally {
decodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
}
}
}
}
}
}
private static Comparator<A7ZipArchive.A7ZipArchiveEntry> naturalComparator = new Comparator<A7ZipArchive.A7ZipArchiveEntry>() {
private NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(A7ZipArchive.A7ZipArchiveEntry o1, A7ZipArchive.A7ZipArchiveEntry o2) {
return comparator.compare(o1.getPath(), o2.getPath());
}
};
}
@@ -18,8 +18,10 @@ package com.hippo.ehviewer.gallery;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.glgallery.GalleryPageView;
@@ -31,6 +33,7 @@ import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -44,7 +47,16 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
private static final String TAG = DirGalleryProvider.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static FilenameFilter imageFilter =
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
private static Comparator<UniFile> naturalComparator = new Comparator<UniFile>() {
private NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(UniFile o1, UniFile o2) {
return comparator.compare(o1.getName(), o2.getName());
}
};
private final UniFile mDir;
private final Stack<Integer> mRequests = new Stack<>();
private final AtomicInteger mDecodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
@@ -242,15 +254,4 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
Log.i(TAG, "ImageDecoder end");
}
private static FilenameFilter imageFilter =
(dir, name) -> StringUtils.endsWith(name.toLowerCase(), SUPPORT_IMAGE_EXTENSIONS);
private static Comparator<UniFile> naturalComparator = new Comparator<UniFile>() {
private NaturalComparator comparator = new NaturalComparator();
@Override
public int compare(UniFile o1, UniFile o2) {
return comparator.compare(o1.getName(), o2.getName());
}
};
}
@@ -17,14 +17,17 @@
package com.hippo.ehviewer.gallery;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.SimpleHandler;
import java.util.Locale;
public class EhGalleryProvider extends GalleryProvider2 implements SpiderQueen.OnSpiderListener {
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.glgallery.GalleryProvider;
import com.hippo.unifile.UniFile;
@@ -35,7 +36,8 @@ public abstract class GalleryProvider2 extends GalleryProvider {
return 0;
}
public void putStartPage(int page) {}
public void putStartPage(int page) {
}
/**
* @return without extension
@@ -17,146 +17,147 @@
package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class Pipe {
private final int capacity;
private final byte[] buffer;
private final int capacity;
private final byte[] buffer;
private int head = 0;
private int tail = 0;
private boolean full = false;
private int head = 0;
private int tail = 0;
private boolean full = false;
private boolean inClosed = false;
private boolean outClosed = false;
private boolean inClosed = false;
private boolean outClosed = false;
private InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[1];
if (read(bytes, 0, 1) != -1) {
return bytes[0];
} else {
return -1;
private InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[1];
if (read(bytes, 0, 1) != -1) {
return bytes[0];
} else {
return -1;
}
}
}
}
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
for (;;) {
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (len == 0) {
return 0;
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
for (; ; ) {
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (len == 0) {
return 0;
}
if (head == tail && !full) {
if (outClosed) {
// No bytes available and the OutputStream is closed. So it's the end.
return -1;
} else {
// Wait for OutputStream write bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
if (head == tail && !full) {
if (outClosed) {
// No bytes available and the OutputStream is closed. So it's the end.
return -1;
} else {
// Wait for OutputStream write bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
}
} else {
int read = Math.min(len, (head < tail ? tail : capacity) - head);
System.arraycopy(buffer, head, b, off, read);
head += read;
if (head == capacity) {
head = 0;
}
full = false;
Pipe.this.notifyAll();
return read;
}
}
}
} else {
int read = Math.min(len, (head < tail ? tail : capacity) - head);
System.arraycopy(buffer, head, b, off, read);
head += read;
if (head == capacity) {
head = 0;
}
full = false;
Pipe.this.notifyAll();
return read;
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
inClosed = true;
Pipe.this.notifyAll();
}
}
};
private OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[] { (byte) b};
write(bytes, 0, 1);
}
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (outClosed) {
throw new IOException("The OutputStream is closed");
}
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
@Override
public void close() {
synchronized (Pipe.this) {
inClosed = true;
Pipe.this.notifyAll();
}
} else {
int write = Math.min(len, (head <= tail ? capacity : head) - tail);
System.arraycopy(b, off, buffer, tail, write);
off += write;
len -= write;
tail += write;
if (tail == capacity) {
tail = 0;
}
if (head == tail) {
full = true;
}
Pipe.this.notifyAll();
}
}
}
};
private OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[]{(byte) b};
write(bytes, 0, 1);
}
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (outClosed) {
throw new IOException("The OutputStream is closed");
}
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
} else {
int write = Math.min(len, (head <= tail ? capacity : head) - tail);
System.arraycopy(b, off, buffer, tail, write);
off += write;
len -= write;
tail += write;
if (tail == capacity) {
tail = 0;
}
if (head == tail) {
full = true;
}
Pipe.this.notifyAll();
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
outClosed = true;
Pipe.this.notifyAll();
}
}
};
Pipe(int capacity) {
this.capacity = capacity;
this.buffer = new byte[capacity];
}
@Override
public void close() {
synchronized (Pipe.this) {
outClosed = true;
Pipe.this.notifyAll();
}
InputStream getInputStream() {
return inputStream;
}
};
Pipe(int capacity) {
this.capacity = capacity;
this.buffer = new byte[capacity];
}
InputStream getInputStream() {
return inputStream;
}
OutputStream getOutputStream() {
return outputStream;
}
OutputStream getOutputStream() {
return outputStream;
}
}
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
@@ -91,7 +93,7 @@ public class CleanRedundancyPreference extends TaskPreference {
}
int count = 0;
for (UniFile f: files) {
for (UniFile f : files) {
if (clearFile(f)) {
++count;
}
@@ -110,7 +112,7 @@ public class CleanRedundancyPreference extends TaskPreference {
}
Toast.makeText(mApplication, 0 == count ?
mApplication.getString(R.string.settings_download_clean_redundancy_no_redundancy):
mApplication.getString(R.string.settings_download_clean_redundancy_no_redundancy) :
mApplication.getString(R.string.settings_download_clean_redundancy_done, count), Toast.LENGTH_SHORT).show();
super.onPostExecute(o);
}
@@ -18,7 +18,9 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
import com.hippo.preference.MessagePreference;
@@ -36,7 +38,7 @@ public class ClearDownloadPathCachePreference extends MessagePreference {
}
public ClearDownloadPathCachePreference(Context context,
AttributeSet attrs, int defStyleAttr) {
AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
@@ -18,8 +18,10 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.widget.CategoryTable;
@@ -20,7 +20,9 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.preference.DialogPreference;
@@ -29,8 +31,6 @@ import com.hippo.yorozuya.ViewUtils;
public class ExcludedTagNamespacesPreference extends DialogPreference {
private View mTableLayout;
private static final int[] EXCLUDED_TAG_GROUP_RES_ID = {
R.id.tag_group_reclass,
R.id.tag_group_language,
@@ -41,7 +41,6 @@ public class ExcludedTagNamespacesPreference extends DialogPreference {
R.id.tag_group_male,
R.id.tag_group_female,
};
private static final int[] EXCLUDED_TAG_GROUP_ID = {
0x1,
0x2,
@@ -52,6 +51,7 @@ public class ExcludedTagNamespacesPreference extends DialogPreference {
0x40,
0x80
};
private View mTableLayout;
public ExcludedTagNamespacesPreference(Context context) {
super(context);
@@ -19,60 +19,63 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.util.ReadableTime;
import java.io.File;
public class ExportDataPreference extends TaskPreference {
public ExportDataPreference(Context context) {
super(context);
}
public ExportDataPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExportDataPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@NonNull
@Override
protected Task onCreateTask() {
return new ExportDataTask(getContext());
}
private static class ExportDataTask extends Task {
public ExportDataTask(@NonNull Context context) {
super(context);
public ExportDataPreference(Context context) {
super(context);
}
public ExportDataPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExportDataPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@NonNull
@Override
protected Object doInBackground(Void... voids) {
File dir = AppConfig.getExternalDataDir();
if (dir != null) {
File file = new File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".db");
if (EhDB.exportDB(getApplication(), file)) {
return file;
protected Task onCreateTask() {
return new ExportDataTask(getContext());
}
private static class ExportDataTask extends Task {
public ExportDataTask(@NonNull Context context) {
super(context);
}
}
return null;
}
@Override
protected void onPostExecute(Object o) {
Toast.makeText(getApplication(),
(o instanceof File)
? GetText.getString(R.string.settings_advanced_export_data_to, ((File) o).getPath())
: GetText.getString(R.string.settings_advanced_export_data_failed),
Toast.LENGTH_SHORT).show();
super.onPostExecute(o);
@Override
protected Object doInBackground(Void... voids) {
File dir = AppConfig.getExternalDataDir();
if (dir != null) {
File file = new File(dir, ReadableTime.getFilenamableTime(System.currentTimeMillis()) + ".db");
if (EhDB.exportDB(getApplication(), file)) {
return file;
}
}
return null;
}
@Override
protected void onPostExecute(Object o) {
Toast.makeText(getApplication(),
(o instanceof File)
? GetText.getString(R.string.settings_advanced_export_data_to, ((File) o).getPath())
: GetText.getString(R.string.settings_advanced_export_data_failed),
Toast.LENGTH_SHORT).show();
super.onPostExecute(o);
}
}
}
}
@@ -21,15 +21,19 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.preference.MessagePreference;
import com.hippo.text.Html;
import java.util.LinkedList;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
@@ -25,7 +25,9 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.Spinner;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhProxySelector;
@@ -71,20 +73,20 @@ public class ProxyPreference extends DialogPreference implements View.OnClickLis
private void updateSummary(int type, String ip, int port) {
if ((type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS)
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port)) ) {
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))) {
type = EhProxySelector.TYPE_SYSTEM;
}
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(context, type),
ip,
port));
getProxyTypeText(context, type),
ip,
port));
} else {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(context, type)));
getProxyTypeText(context, type)));
}
}
@@ -19,10 +19,12 @@ package com.hippo.ehviewer.preference;
import android.app.Activity;
import android.content.Context;
import android.os.Parcel;
import android.preference.Preference;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.preference.Preference;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -36,11 +38,13 @@ import com.hippo.ehviewer.spider.SpiderQueen;
import com.hippo.unifile.UniFile;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import okhttp3.OkHttpClient;
public class RestoreDownloadPreference extends TaskPreference {
@@ -123,7 +127,7 @@ public class RestoreDownloadPreference extends TaskPreference {
return null;
}
for (UniFile file: files) {
for (UniFile file : files) {
RestoreItem restoreItem = getRestoreItem(file);
if (null != restoreItem) {
restoreItemList.add(restoreItem);
@@ -184,27 +188,6 @@ public class RestoreDownloadPreference extends TaskPreference {
private static class RestoreItem extends GalleryInfo {
public String dirname;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(this.dirname);
}
public RestoreItem() {
}
protected RestoreItem(Parcel in) {
super(in);
this.dirname = in.readString();
}
public static final Creator<RestoreItem> CREATOR = new Creator<RestoreItem>() {
@Override
public RestoreItem createFromParcel(Parcel source) {
@@ -216,5 +199,25 @@ public class RestoreDownloadPreference extends TaskPreference {
return new RestoreItem[size];
}
};
public String dirname;
public RestoreItem() {
}
protected RestoreItem(Parcel in) {
super(in);
this.dirname = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(this.dirname);
}
}
}
@@ -19,54 +19,58 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.preference.PreferenceViewHolder;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.preference.ActivityPreference;
import okhttp3.HttpUrl;
public class SignInRequiredActivityPreference extends ActivityPreference {
private View view;
private View view;
public SignInRequiredActivityPreference(Context context) {
super(context);
}
public SignInRequiredActivityPreference(Context context) {
super(context);
}
public SignInRequiredActivityPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SignInRequiredActivityPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SignInRequiredActivityPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SignInRequiredActivityPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected View onCreateView(ViewGroup parent) {
return view = super.onCreateView(parent);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
view = holder.itemView;
super.onBindViewHolder(holder);
}
@Override
protected void onClick() {
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
@Override
protected void onClick() {
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
if (store.contains(e, EhCookieStore.KEY_IPD_MEMBER_ID) ||
store.contains(e, EhCookieStore.KEY_IPD_PASS_HASH) ||
store.contains(ex, EhCookieStore.KEY_IPD_MEMBER_ID) ||
store.contains(ex, EhCookieStore.KEY_IPD_PASS_HASH)) {
super.onClick();
} else {
if (view != null) {
Snackbar.make(view, R.string.error_please_login_first, 3000).show();
} else {
Toast.makeText(getContext(), R.string.error_please_login_first, Toast.LENGTH_LONG).show();
}
if (store.contains(e, EhCookieStore.KEY_IPD_MEMBER_ID) ||
store.contains(e, EhCookieStore.KEY_IPD_PASS_HASH) ||
store.contains(ex, EhCookieStore.KEY_IPD_MEMBER_ID) ||
store.contains(ex, EhCookieStore.KEY_IPD_PASS_HASH)) {
super.onClick();
} else {
if (view != null) {
Snackbar.make(view, R.string.error_please_login_first, 3000).show();
} else {
Toast.makeText(getContext(), R.string.error_please_login_first, Toast.LENGTH_LONG).show();
}
}
}
}
}
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.preference.MessagePreference;
@@ -22,11 +22,13 @@ import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.customview.view.AbsSavedState;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.preference.DialogPreference;
@@ -81,15 +83,6 @@ public abstract class TaskPreference extends DialogPreference {
@NonNull
protected abstract Task onCreateTask();
@Override
public void onActivityDestroy() {
super.onActivityDestroy();
if (null != mTask) {
mTask.setPreference(null);
}
}
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
@@ -125,23 +118,6 @@ public abstract class TaskPreference extends DialogPreference {
}
private static class SavedState extends AbsSavedState {
int asyncTaskId;
public SavedState(Parcel source) {
super(source, SavedState.class.getClassLoader());
asyncTaskId = source.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(asyncTaskId);
}
public SavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
@@ -154,6 +130,22 @@ public abstract class TaskPreference extends DialogPreference {
return new SavedState[size];
}
};
int asyncTaskId;
public SavedState(Parcel source) {
super(source, SavedState.class.getClassLoader());
asyncTaskId = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(asyncTaskId);
}
}
public abstract static class Task extends AsyncTask<Void, Void, Object> {
@@ -19,7 +19,7 @@ package com.hippo.ehviewer.preference;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.preference.Preference;
import androidx.preference.Preference;
import android.util.AttributeSet;
import com.hippo.ehviewer.R;
@@ -26,16 +26,16 @@ import com.hippo.ehviewer.download.DownloadService;
* Created by onlymash on 3/25/18.
*/
public class ShortcutsActivity extends Activity{
public class ShortcutsActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = null;
Intent intent = getIntent();
if (intent != null){
if (intent != null) {
action = intent.getAction();
if (action != null && (action.equals(DownloadService.ACTION_START_ALL) ||
action.equals(DownloadService.ACTION_STOP_ALL))){
action.equals(DownloadService.ACTION_STOP_ALL))) {
startService(new Intent(this, DownloadService.class).setAction(action));
}
}
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.spider;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.Settings;
@@ -37,6 +39,7 @@ import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.Utilities;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
@@ -44,33 +47,23 @@ import java.util.Locale;
public final class SpiderDen {
@Nullable
private final UniFile mDownloadDir;
private volatile int mMode = SpiderQueen.MODE_READ;
private final long mGid;
@Nullable
private static SimpleDiskCache sCache;
@Nullable
private final UniFile mDownloadDir;
private final long mGid;
private volatile int mMode = SpiderQueen.MODE_READ;
public SpiderDen(GalleryInfo galleryInfo) {
mGid = galleryInfo.gid;
mDownloadDir = getGalleryDownloadDir(galleryInfo);
}
public static void initialize(Context context) {
sCache = new SimpleDiskCache(new File(context.getCacheDir(), "image"),
MathUtils.clamp(Settings.getReadCacheSize(), 40, 640) * 1024 * 1024);
}
private static class StartWithFilenameFilter implements FilenameFilter {
private final String mPrefix;
public StartWithFilenameFilter(String prefix) {
mPrefix = prefix;
}
@Override
public boolean accept(UniFile dir, String filename) {
return filename.startsWith(mPrefix);
}
}
public static UniFile getGalleryDownloadDir(GalleryInfo galleryInfo) {
UniFile dir = Settings.getDownloadLocation();
if (dir != null) {
@@ -116,9 +109,23 @@ public final class SpiderDen {
}
}
public SpiderDen(GalleryInfo galleryInfo) {
mGid = galleryInfo.gid;
mDownloadDir = getGalleryDownloadDir(galleryInfo);
/**
* @param extension with dot
*/
public static String generateImageFilename(int index, String extension) {
return String.format(Locale.US, "%08d%s", index + 1, extension);
}
@Nullable
private static UniFile findImageFile(UniFile dir, int index) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
String filename = generateImageFilename(index, extension);
UniFile file = dir.findFile(filename);
if (file != null) {
return file;
}
}
return null;
}
public void setMode(@SpiderQueen.Mode int mode) {
@@ -158,25 +165,6 @@ public final class SpiderDen {
return sCache.contain(key);
}
/**
* @param extension with dot
*/
public static String generateImageFilename(int index, String extension) {
return String.format(Locale.US, "%08d%s", index + 1, extension);
}
@Nullable
private static UniFile findImageFile(UniFile dir, int index) {
for (String extension : GalleryProvider2.SUPPORT_IMAGE_EXTENSIONS) {
String filename = generateImageFilename(index, extension);
UniFile file = dir.findFile(filename);
if (file != null) {
return file;
}
}
return null;
}
private boolean containInDownloadDir(int index) {
UniFile dir = getDownloadDir();
if (dir == null) {
@@ -334,7 +322,6 @@ public final class SpiderDen {
}
}
@Nullable
private InputStreamPipe openCacheInputStreamPipe(int index) {
if (sCache == null) {
@@ -378,4 +365,18 @@ public final class SpiderDen {
return null;
}
}
private static class StartWithFilenameFilter implements FilenameFilter {
private final String mPrefix;
public StartWithFilenameFilter(String prefix) {
mPrefix = prefix;
}
@Override
public boolean accept(UniFile dir, String filename) {
return filename.startsWith(mPrefix);
}
}
}
@@ -19,11 +19,14 @@ package com.hippo.ehviewer.spider;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.NumberUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -31,13 +34,10 @@ import java.io.OutputStreamWriter;
public class SpiderInfo {
static final String TOKEN_FAILED = "failed";
private static final String TAG = SpiderInfo.class.getSimpleName();
private static final String VERSION_STR = "VERSION";
private static final int VERSION = 2;
static final String TOKEN_FAILED = "failed";
public int startPage = 0;
public long gid = -1;
public String token = null;
@@ -24,10 +24,12 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.webkit.MimeTypeMap;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.GetText;
@@ -61,6 +63,7 @@ import com.hippo.yorozuya.Utilities;
import com.hippo.yorozuya.collect.SparseJLArray;
import com.hippo.yorozuya.thread.PriorityThread;
import com.hippo.yorozuya.thread.PriorityThreadFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -78,6 +81,7 @@ import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -87,38 +91,23 @@ import okhttp3.ResponseBody;
public final class SpiderQueen implements Runnable {
private static final String TAG = SpiderQueen.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final boolean DEBUG_LOG = false;
private static final boolean DEBUG_PTOKEN = true;
@IntDef({MODE_READ, MODE_DOWNLOAD})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {}
@IntDef({STATE_NONE, STATE_DOWNLOADING, STATE_FINISHED, STATE_FAILED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
public static final int MODE_READ = 0;
public static final int MODE_DOWNLOAD = 1;
public static final int STATE_NONE = 0;
public static final int STATE_DOWNLOADING = 1;
public static final int STATE_FINISHED = 2;
public static final int STATE_FAILED = 3;
public static final int DECODE_THREAD_NUM = 1;
public static final String SPIDER_INFO_FILENAME = ".ehviewer";
private static final String TAG = SpiderQueen.class.getSimpleName();
private static final AtomicInteger sIdGenerator = new AtomicInteger();
private static final boolean DEBUG_LOG = false;
private static final boolean DEBUG_PTOKEN = true;
private static final String[] URL_509_SUFFIX_ARRAY = {
"/509.gif",
"/509s.gif"
};
private static final SparseJLArray<SpiderQueen> sQueenMap = new SparseJLArray<>();
@NonNull
private final OkHttpClient mHttpClient;
@NonNull
@@ -127,54 +116,42 @@ public final class SpiderQueen implements Runnable {
private final GalleryInfo mGalleryInfo;
@NonNull
private final SpiderDen mSpiderDen;
private int mReadReference = 0;
private int mDownloadReference = 0;
// It mQueenThread is null, failed or stopped
@Nullable
private volatile Thread mQueenThread;
private final Object mQueenLock = new Object();
private final Thread[] mDecodeThreadArray = new Thread[DECODE_THREAD_NUM];
private final int[] mDecodeIndexArray = new int[DECODE_THREAD_NUM];
private final Queue<Integer> mDecodeRequestQueue = new LinkedList<>();
private final Object mWorkerLock = new Object();
private ThreadPoolExecutor mWorkerPoolExecutor;
private int mWorkerCount;
private final Object mPTokenLock = new Object();
private final AtomicReference<SpiderInfo> mSpiderInfo = new AtomicReference<>();
private final Queue<Integer> mRequestPTokenQueue = new ConcurrentLinkedQueue<>();
private final Object mPageStateLock = new Object();
private volatile int[] mPageStateArray;
// Store request page. The index may be invalid
private final Queue<Integer> mRequestPageQueue = new LinkedList<>();
// Store preload page. The index may be invalid
private final Queue<Integer> mRequestPageQueue2 = new LinkedList<>();
// Store force request page. The index may be invalid
private final Queue<Integer> mForceRequestPageQueue = new LinkedList<>();
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
private final AtomicInteger mDownloadedPages = new AtomicInteger(0);
private final AtomicInteger mFinishedPages = new AtomicInteger(0);
private AtomicReference<String> showKey = new AtomicReference<>();
private final Object showKeyLock = new Object();
// Store page error
private final ConcurrentHashMap<Integer, String> mPageErrorMap = new ConcurrentHashMap<>();
// Store page download percent
private final ConcurrentHashMap<Integer, Float> mPagePercentMap = new ConcurrentHashMap<>();
private final List<OnSpiderListener> mSpiderListeners = new ArrayList<>();
private final int mWorkerMaxCount;
private final int mPreloadNumber;
private int mReadReference = 0;
private int mDownloadReference = 0;
// It mQueenThread is null, failed or stopped
@Nullable
private volatile Thread mQueenThread;
private ThreadPoolExecutor mWorkerPoolExecutor;
private int mWorkerCount;
private volatile int[] mPageStateArray;
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
private AtomicReference<String> showKey = new AtomicReference<>();
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient(application);
@@ -194,6 +171,49 @@ public final class SpiderQueen implements Runnable {
new PriorityThreadFactory(SpiderWorker.class.getSimpleName(), Process.THREAD_PRIORITY_BACKGROUND));
}
@UiThread
public static SpiderQueen obtainSpiderQueen(@NonNull Context context,
@NonNull GalleryInfo galleryInfo, @Mode int mode) {
OSUtils.checkMainLoop();
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
EhApplication application = (EhApplication) context.getApplicationContext();
queen = new SpiderQueen(application, galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
queen.setMode(mode);
queen.start();
} else {
// Set mode
queen.setMode(mode);
}
return queen;
}
@UiThread
public static void releaseSpiderQueen(@NonNull SpiderQueen queen, @Mode int mode) {
OSUtils.checkMainLoop();
// Clear mode
queen.clearMode(mode);
if (queen.mReadReference == 0 && queen.mDownloadReference == 0) {
// Stop and remove if there is no reference
queen.stop();
sQueenMap.remove(queen.mGalleryInfo.gid);
}
}
public static boolean contain(int[] array, int value) {
for (int v : array) {
if (v == value) {
return true;
}
}
return false;
}
public void addOnSpiderListener(OnSpiderListener listener) {
synchronized (mSpiderListeners) {
mSpiderListeners.add(listener);
@@ -288,40 +308,6 @@ public final class SpiderQueen implements Runnable {
}
}
@UiThread
public static SpiderQueen obtainSpiderQueen(@NonNull Context context,
@NonNull GalleryInfo galleryInfo, @Mode int mode) {
OSUtils.checkMainLoop();
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
if (queen == null) {
EhApplication application = (EhApplication) context.getApplicationContext();
queen = new SpiderQueen(application, galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
// Set mode
queen.setMode(mode);
queen.start();
} else {
// Set mode
queen.setMode(mode);
}
return queen;
}
@UiThread
public static void releaseSpiderQueen(@NonNull SpiderQueen queen, @Mode int mode) {
OSUtils.checkMainLoop();
// Clear mode
queen.clearMode(mode);
if (queen.mReadReference == 0 && queen.mDownloadReference == 0) {
// Stop and remove if there is no reference
queen.stop();
sQueenMap.remove(queen.mGalleryInfo.gid);
}
}
private void updateMode() {
int mode;
if (mDownloadReference > 0) {
@@ -481,8 +467,7 @@ public final class SpiderQueen implements Runnable {
}
/**
* @return
* String for error<br>
* @return String for error<br>
* Float for download percent<br>
* null for wait
*/
@@ -496,7 +481,7 @@ public final class SpiderQueen implements Runnable {
// Fix state for force
if ((force && (state == STATE_FINISHED || state == STATE_FAILED)) ||
(ignoreError && state == STATE_FAILED)) {
(ignoreError && state == STATE_FAILED)) {
// Update state to none at once
updatePageState(index, STATE_NONE);
state = STATE_NONE;
@@ -563,15 +548,6 @@ public final class SpiderQueen implements Runnable {
return result;
}
public static boolean contain(int[] array, int value) {
for (int v: array) {
if (v == value) {
return true;
}
}
return false;
}
private void ensureWorkers() {
synchronized (mWorkerLock) {
if (null == mWorkerPoolExecutor) {
@@ -780,7 +756,7 @@ public final class SpiderQueen implements Runnable {
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
", previewPerPage " + spiderInfo.previewPerPage+ ", url " + url);
", previewPerPage " + spiderInfo.previewPerPage + ", url " + url);
}
Request request = new EhRequestBuilder(url, referer).build();
Response response = mHttpClient.newCall(request).execute();
@@ -1008,6 +984,41 @@ public final class SpiderQueen implements Runnable {
}
}
@IntDef({MODE_READ, MODE_DOWNLOAD})
@Retention(RetentionPolicy.SOURCE)
public @interface Mode {
}
@IntDef({STATE_NONE, STATE_DOWNLOADING, STATE_FINISHED, STATE_FAILED})
@Retention(RetentionPolicy.SOURCE)
public @interface State {
}
public interface OnSpiderListener {
void onGetPages(int pages);
void onGet509(int index);
/**
* @param contentLength -1 for unknown
*/
void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead);
void onPageSuccess(int index, int finished, int downloaded, int total);
void onPageFailure(int index, String error, int finished, int downloaded, int total);
/**
* All workers end
*/
void onFinish(int finished, int downloaded, int total);
void onGetImageSuccess(int index, Image image);
void onGetImageFailure(int index, String error);
}
private class SpiderWorker implements Runnable {
private final long mGid;
@@ -1017,7 +1028,7 @@ public final class SpiderQueen implements Runnable {
}
private String getPageUrl(long gid, int index, String pToken,
String oldPageUrl, String skipHathKey) {
String oldPageUrl, String skipHathKey) {
String pageUrl;
if (oldPageUrl != null) {
pageUrl = oldPageUrl;
@@ -1276,7 +1287,7 @@ public final class SpiderQueen implements Runnable {
isPipe.obtain();
InputStream inputStream = new BufferedInputStream(isPipe.open());
boolean isPlainTxt = true;
for (;;) {
for (; ; ) {
int b = inputStream.read();
if (b == -1) {
break;
@@ -1381,7 +1392,7 @@ public final class SpiderQueen implements Runnable {
// Check exist for not force request
if (!force && mSpiderDen.contain(index)) {
updatePageState(index , STATE_FINISHED);
updatePageState(index, STATE_FINISHED);
return true;
}
@@ -1481,7 +1492,8 @@ public final class SpiderQueen implements Runnable {
Log.i(TAG, Thread.currentThread().getName() + ": start");
}
while (mSpiderDen.isReady() && !Thread.currentThread().isInterrupted() && runInternal());
while (mSpiderDen.isReady() && !Thread.currentThread().isInterrupted() && runInternal())
;
boolean finish;
// Clear in spider worker array
@@ -1621,29 +1633,4 @@ public final class SpiderQueen implements Runnable {
mPipe.release();
}
}
public interface OnSpiderListener {
void onGetPages(int pages);
void onGet509(int index);
/**
* @param contentLength -1 for unknown
*/
void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead);
void onPageSuccess(int index, int finished, int downloaded, int total);
void onPageFailure(int index, String error, int finished, int downloaded, int total);
/**
* All workers end
*/
void onFinish(int finished, int downloaded, int total);
void onGetImageSuccess(int index, Image image);
void onGetImageFailure(int index, String error);
}
}
@@ -23,7 +23,9 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import com.hippo.app.ListCheckBoxDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
@@ -44,16 +46,19 @@ import com.hippo.util.IoThreadPoolExecutor;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.collect.LongList;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
public final class CommonOperations {
@@ -68,129 +73,8 @@ public final class CommonOperations {
}
}
private static final class UpdateTask extends AsyncTask<Void, Void, JSONObject> {
private final Activity mActivity;
private final OkHttpClient mHttpClient;
private final boolean mFeedback;
public UpdateTask(Activity activity, boolean feedback) {
mActivity = activity;
mHttpClient = EhApplication.getOkHttpClient(activity);
mFeedback = feedback;
}
private JSONObject fetchUpdateInfo(String url) throws IOException, JSONException {
Log.d(TAG, url);
Request request = new Request.Builder().url(url).build();
Response response = mHttpClient.newCall(request).execute();
return new JSONObject(response.body().string());
}
@Override
protected JSONObject doInBackground(Void... params) {
String url;
if (Settings.getBetaUpdateChannel()) {
url = "http://www.ehviewer.com/update_beta.json";
} else {
url = "http://www.ehviewer.com/update.json";
}
try {
return fetchUpdateInfo(url);
} catch (Throwable e1) {
ExceptionUtils.throwIfFatal(e1);
if (Settings.getBetaUpdateChannel()) {
url = "https://raw.githubusercontent.com/seven332/EhViewer/api/update_beta.json";
} else {
url = "https://raw.githubusercontent.com/seven332/EhViewer/api/update.json";
}
try {
return fetchUpdateInfo(url);
} catch (Throwable e2) {
ExceptionUtils.throwIfFatal(e2);
return null;
}
}
}
private void showUpToDateDialog() {
new AlertDialog.Builder(mActivity)
.setMessage(R.string.update_to_date)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void showUpdateDialog(String versionName, int versionCode, String size, CharSequence info, final String url) {
new AlertDialog.Builder(mActivity)
.setTitle(R.string.update)
.setMessage(mActivity.getString(R.string.update_plain, versionName, size, info))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UrlOpener.openUrl(mActivity, url, false);
}
})
.setNegativeButton(R.string.update_ignore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Settings.putSkipUpdateVersion(versionCode);
}
}).show();
}
private void handleResult(JSONObject jo) {
if (null == jo || mActivity.isFinishing()) {
return;
}
String versionName;
int versionCode;
String size;
CharSequence info;
String url;
try {
PackageManager pm = mActivity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mActivity.getPackageName(), PackageManager.GET_ACTIVITIES);
int currentVersionCode = pi.versionCode;
versionCode = jo.getInt("version_code");
if (currentVersionCode >= versionCode) {
// Update to date
if (mFeedback) {
showUpToDateDialog();
}
return;
}
versionName = jo.getString("version_name");
size = FileUtils.humanReadableByteCount(jo.getLong("size"), false);
info = Html.fromHtml(jo.getString("info"));
url = jo.getString("url");
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return;
}
if (mFeedback || versionCode != Settings.getSkipUpdateVersion()) {
showUpdateDialog(versionName, versionCode, size, info, url);
}
}
@Override
protected void onPostExecute(JSONObject jsonObject) {
try {
handleResult(jsonObject);
} finally {
UPDATING = false;
}
}
}
private static void doAddToFavorites(Activity activity, GalleryInfo galleryInfo,
int slot, EhClient.Callback<Void> listener) {
int slot, EhClient.Callback<Void> listener) {
if (slot == -1) {
EhDB.putLocalFavorites(galleryInfo);
listener.onSuccess(null);
@@ -207,7 +91,7 @@ public final class CommonOperations {
}
public static void addToFavorites(final Activity activity, final GalleryInfo galleryInfo,
final EhClient.Callback<Void> listener) {
final EhClient.Callback<Void> listener) {
int slot = Settings.getDefaultFavSlot();
String[] items = new String[11];
items[0] = activity.getString(R.string.local_favorites);
@@ -220,7 +104,7 @@ public final class CommonOperations {
new ListCheckBoxDialogBuilder(activity, items,
(builder, dialog, position) -> {
int slot1 = position - 1;
String newFavoriteName = (slot1 >= 0 && slot1 <= 9) ? items[slot1+1] : null;
String newFavoriteName = (slot1 >= 0 && slot1 <= 9) ? items[slot1 + 1] : null;
doAddToFavorites(activity, galleryInfo, slot1, new DelegateFavoriteCallback(listener, galleryInfo, newFavoriteName, slot1));
if (builder.isChecked()) {
Settings.putDefaultFavSlot(slot1);
@@ -235,7 +119,7 @@ public final class CommonOperations {
}
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
final EhClient.Callback<Void> listener) {
final EhClient.Callback<Void> listener) {
EhDB.removeLocalFavorites(galleryInfo.gid);
EhClient client = EhApplication.getEhClient(activity);
EhRequest request = new EhRequest();
@@ -245,40 +129,6 @@ public final class CommonOperations {
client.execute(request);
}
private static class DelegateFavoriteCallback implements EhClient.Callback<Void> {
private final EhClient.Callback<Void> delegate;
private final GalleryInfo info;
private final String newFavoriteName;
private final int slot;
DelegateFavoriteCallback(EhClient.Callback<Void> delegate, GalleryInfo info,
String newFavoriteName, int slot) {
this.delegate = delegate;
this.info = info;
this.newFavoriteName = newFavoriteName;
this.slot = slot;
}
@Override
public void onSuccess(Void result) {
info.favoriteName = newFavoriteName;
info.favoriteSlot = slot;
delegate.onSuccess(result);
EhApplication.getFavouriteStatusRouter().modifyFavourites(info.gid, slot);
}
@Override
public void onFailure(Exception e) {
delegate.onFailure(e);
}
@Override
public void onCancel() {
delegate.onCancel();
}
}
public static void startDownload(final MainActivity activity, final GalleryInfo galleryInfo, boolean forceDefault) {
startDownload(activity, Collections.singletonList(galleryInfo), forceDefault);
}
@@ -409,4 +259,159 @@ public final class CommonOperations {
noMedia.delete();
}
}
private static final class UpdateTask extends AsyncTask<Void, Void, JSONObject> {
private final Activity mActivity;
private final OkHttpClient mHttpClient;
private final boolean mFeedback;
public UpdateTask(Activity activity, boolean feedback) {
mActivity = activity;
mHttpClient = EhApplication.getOkHttpClient(activity);
mFeedback = feedback;
}
private JSONObject fetchUpdateInfo(String url) throws IOException, JSONException {
Log.d(TAG, url);
Request request = new Request.Builder().url(url).build();
Response response = mHttpClient.newCall(request).execute();
return new JSONObject(response.body().string());
}
@Override
protected JSONObject doInBackground(Void... params) {
String url;
if (Settings.getBetaUpdateChannel()) {
url = "http://www.ehviewer.com/update_beta.json";
} else {
url = "http://www.ehviewer.com/update.json";
}
try {
return fetchUpdateInfo(url);
} catch (Throwable e1) {
ExceptionUtils.throwIfFatal(e1);
if (Settings.getBetaUpdateChannel()) {
url = "https://raw.githubusercontent.com/seven332/EhViewer/api/update_beta.json";
} else {
url = "https://raw.githubusercontent.com/seven332/EhViewer/api/update.json";
}
try {
return fetchUpdateInfo(url);
} catch (Throwable e2) {
ExceptionUtils.throwIfFatal(e2);
return null;
}
}
}
private void showUpToDateDialog() {
new AlertDialog.Builder(mActivity)
.setMessage(R.string.update_to_date)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void showUpdateDialog(String versionName, int versionCode, String size, CharSequence info, final String url) {
new AlertDialog.Builder(mActivity)
.setTitle(R.string.update)
.setMessage(mActivity.getString(R.string.update_plain, versionName, size, info))
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
UrlOpener.openUrl(mActivity, url, false);
}
})
.setNegativeButton(R.string.update_ignore, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Settings.putSkipUpdateVersion(versionCode);
}
}).show();
}
private void handleResult(JSONObject jo) {
if (null == jo || mActivity.isFinishing()) {
return;
}
String versionName;
int versionCode;
String size;
CharSequence info;
String url;
try {
PackageManager pm = mActivity.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mActivity.getPackageName(), PackageManager.GET_ACTIVITIES);
int currentVersionCode = pi.versionCode;
versionCode = jo.getInt("version_code");
if (currentVersionCode >= versionCode) {
// Update to date
if (mFeedback) {
showUpToDateDialog();
}
return;
}
versionName = jo.getString("version_name");
size = FileUtils.humanReadableByteCount(jo.getLong("size"), false);
info = Html.fromHtml(jo.getString("info"));
url = jo.getString("url");
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
return;
}
if (mFeedback || versionCode != Settings.getSkipUpdateVersion()) {
showUpdateDialog(versionName, versionCode, size, info, url);
}
}
@Override
protected void onPostExecute(JSONObject jsonObject) {
try {
handleResult(jsonObject);
} finally {
UPDATING = false;
}
}
}
private static class DelegateFavoriteCallback implements EhClient.Callback<Void> {
private final EhClient.Callback<Void> delegate;
private final GalleryInfo info;
private final String newFavoriteName;
private final int slot;
DelegateFavoriteCallback(EhClient.Callback<Void> delegate, GalleryInfo info,
String newFavoriteName, int slot) {
this.delegate = delegate;
this.info = info;
this.newFavoriteName = newFavoriteName;
this.slot = slot;
}
@Override
public void onSuccess(Void result) {
info.favoriteName = newFavoriteName;
info.favoriteSlot = slot;
delegate.onSuccess(result);
EhApplication.getFavouriteStatusRouter().modifyFavourites(info.gid, slot);
}
@Override
public void onFailure(Exception e) {
delegate.onFailure(e);
}
@Override
public void onCancel() {
delegate.onCancel();
}
}
}
@@ -23,16 +23,19 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.hippo.android.resource.AttrResources;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
import com.hippo.ripple.Ripple;
import com.hippo.widget.DirExplorer;
import com.hippo.yorozuya.FileUtils;
import java.io.File;
public class DirPickerActivity extends ToolbarActivity
@@ -20,16 +20,17 @@ import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.hippo.android.resource.AttrResources;
import com.hippo.content.ContextLocalWrapper;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import java.util.Locale;
public abstract class EhActivity extends AppCompatActivity {
@@ -45,10 +46,6 @@ public abstract class EhActivity extends AppCompatActivity {
((EhApplication) getApplication()).registerActivity(this);
if (Analytics.isEnabled()) {
FirebaseAnalytics.getInstance(this);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Settings.getApplyNavBarThemeColor()) {
getWindow().setNavigationBarColor(AttrResources.getAttrColor(this, R.attr.colorPrimaryDark));
}
@@ -64,10 +61,10 @@ public abstract class EhActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
if(Settings.getEnabledSecurity()){
if (Settings.getEnabledSecurity()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
}else{
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
@@ -1,98 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.hippo.android.resource.AttrResources;
import com.hippo.app.PrettyPreferenceActivity;
import com.hippo.content.ContextLocalWrapper;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import java.util.Locale;
public abstract class EhPreferenceActivity extends PrettyPreferenceActivity {
@StyleRes
protected abstract int getThemeResId(int theme);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setTheme(getThemeResId(Settings.getTheme()));
super.onCreate(savedInstanceState);
((EhApplication) getApplication()).registerActivity(this);
if (Analytics.isEnabled()) {
FirebaseAnalytics.getInstance(this);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Settings.getApplyNavBarThemeColor()) {
getWindow().setNavigationBarColor(AttrResources.getAttrColor(this, R.attr.colorPrimaryDark));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
((EhApplication) getApplication()).unregisterActivity(this);
}
@Override
protected void onResume() {
super.onResume();
if(Settings.getEnabledSecurity()){
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE);
}else{
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
@Override
protected void attachBaseContext(Context newBase) {
Locale locale = null;
String language = Settings.getAppLanguage();
if (language != null && !language.equals("system")) {
String[] split = language.split("-");
if (split.length == 1) {
locale = new Locale(split[0]);
} else if (split.length == 2) {
locale = new Locale(split[0], split[1]);
} else if (split.length == 3) {
locale = new Locale(split[0], split[1], split[2]);
}
}
if (locale == null) {
locale = Resources.getSystem().getConfiguration().locale;
}
newBase = ContextLocalWrapper.wrap(newBase, locale);
super.attachBaseContext(newBase);
}
}
@@ -21,10 +21,12 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.hippo.android.resource.AttrResources;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.ehviewer.R;
@@ -29,10 +29,12 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.ehviewer.R;
@@ -41,6 +43,7 @@ import com.hippo.ehviewer.dao.Filter;
import com.hippo.util.DrawableManager;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
public class FilterActivity extends ToolbarActivity {
@@ -44,10 +44,12 @@ import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SwitchCompat;
import com.hippo.android.resource.AttrResources;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
@@ -78,6 +80,7 @@ import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -101,14 +104,13 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private static final long HIDE_SLIDER_DELAY = 3000;
private static final int WRITE_REQUEST_CODE = 43;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(3);
private String mAction;
private String mFilename;
private Uri mUri;
private GalleryInfo mGalleryInfo;
private int mPage;
private String mCacheFileName;
@Nullable
private GLRootView mGLRootView;
@Nullable
@@ -117,11 +119,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private GalleryProvider2 mGalleryProvider;
@Nullable
private GalleryAdapter mGalleryAdapter;
@Nullable
private SystemUiHelper mSystemUiHelper;
private boolean mShowSystemUi;
@Nullable
private ColorView mMaskView;
@Nullable
@@ -132,21 +132,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private View mBattery;
@Nullable
private View mSeekBarPanel;
@Nullable
private TextView mLeftText;
@Nullable
private TextView mRightText;
@Nullable
private ReversibleSeekBar mSeekBar;
private ObjectAnimator mSeekBarPanelAnimator;
private int mLayoutMode;
private int mSize;
private int mCurrentIndex;
private final ConcurrentPool<NotifyTask> mNotifyTaskPool = new ConcurrentPool<>(3);
private final ValueAnimator.AnimatorUpdateListener mUpdateSliderListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
@@ -155,14 +140,19 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
};
@Nullable
private TextView mLeftText;
@Nullable
private TextView mRightText;
@Nullable
private ReversibleSeekBar mSeekBar;
private ObjectAnimator mSeekBarPanelAnimator;
private final SimpleAnimatorListener mShowSliderListener = new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
mSeekBarPanelAnimator = null;
}
};
private final SimpleAnimatorListener mHideSliderListener = new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -172,7 +162,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
};
private final Runnable mHideSliderRunnable = new Runnable() {
@Override
public void run() {
@@ -181,6 +170,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
};
private int mLayoutMode;
private int mSize;
private int mCurrentIndex;
@Override
protected int getThemeResId(int theme) {
@@ -233,7 +225,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private void onRestore(@NonNull Bundle savedInstanceState) {
mAction = savedInstanceState.getString(KEY_ACTION);
mFilename = savedInstanceState.getString(KEY_FILENAME);
mUri= savedInstanceState.getParcelable(KEY_URI);
mUri = savedInstanceState.getParcelable(KEY_URI);
mGalleryInfo = savedInstanceState.getParcelable(KEY_GALLERY_INFO);
mPage = savedInstanceState.getInt(KEY_PAGE, -1);
mCurrentIndex = savedInstanceState.getInt(KEY_CURRENT_INDEX);
@@ -735,6 +727,184 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
w.setAttributes(lp);
}
private void shareImage(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = AppConfig.getExternalTempDir();
if (null == dir) {
Toast.makeText(this, R.string.error_cant_create_temp_file, Toast.LENGTH_SHORT).show();
return;
}
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String filename = file.getName();
if (filename == null) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(filename));
if (TextUtils.isEmpty(mimeType)) {
mimeType = "image/jpeg";
}
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(BuildConfig.FILE_PROVIDER_AUTHORITY)
.appendPath("temp")
.appendPath(filename)
.build();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType(mimeType);
try {
startActivity(Intent.createChooser(intent, getString(R.string.share_image)));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
Toast.makeText(this, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
}
}
private void saveImage(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = AppConfig.getExternalImageDir();
if (null == dir) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, getString(R.string.image_saved, file.getUri()), Toast.LENGTH_SHORT).show();
// Sync media store
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, file.getUri()));
}
private void saveImageTo(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = getCacheDir();
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String filename = file.getName();
if (filename == null) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
mCacheFileName = filename;
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_TITLE, filename);
try {
startActivityForResult(intent, WRITE_REQUEST_CODE);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
Toast.makeText(this, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
String filepath = getCacheDir() + "/" + mCacheFileName;
File cachefile = new File(filepath);
InputStream is = null;
OutputStream os = null;
ContentResolver resolver = getContentResolver();
try {
is = new FileInputStream(cachefile);
os = resolver.openOutputStream(uri);
IOUtils.copy(is, os);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
cachefile.delete();
Toast.makeText(this, getString(R.string.image_saved, uri.getPath()), Toast.LENGTH_SHORT).show();
// Sync media store
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
}
}
private void showPageDialog(final int page) {
Resources resources = GalleryActivity.this.getResources();
AlertDialog.Builder builder = new AlertDialog.Builder(GalleryActivity.this);
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
final CharSequence[] items;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
items = new CharSequence[]{
getString(R.string.page_menu_refresh),
getString(R.string.page_menu_share),
getString(R.string.page_menu_save),
getString(R.string.page_menu_save_to)};
} else {
items = new CharSequence[]{
getString(R.string.page_menu_refresh),
getString(R.string.page_menu_share),
getString(R.string.page_menu_save)};
}
pageDialogListener(builder, items, page);
builder.show();
}
private void pageDialogListener(AlertDialog.Builder builder, CharSequence[] items, int page) {
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mGalleryProvider == null) {
return;
}
switch (which) {
case 0: // Refresh
mGalleryProvider.removeCache(page);
mGalleryProvider.forceRequest(page);
break;
case 1: // Share
shareImage(page);
break;
case 2: // Save
saveImage(page);
break;
case 3: // Save to
saveImageTo(page);
break;
}
}
});
}
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
private final View mView;
@@ -880,184 +1050,6 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private void shareImage(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = AppConfig.getExternalTempDir();
if (null == dir) {
Toast.makeText(this, R.string.error_cant_create_temp_file, Toast.LENGTH_SHORT).show();
return;
}
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String filename = file.getName();
if (filename == null) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
MimeTypeMap.getFileExtensionFromUrl(filename));
if (TextUtils.isEmpty(mimeType)) {
mimeType = "image/jpeg";
}
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(BuildConfig.FILE_PROVIDER_AUTHORITY)
.appendPath("temp")
.appendPath(filename)
.build();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType(mimeType);
try {
startActivity(Intent.createChooser(intent, getString(R.string.share_image)));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
Toast.makeText(this, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
}
}
private void saveImage(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = AppConfig.getExternalImageDir();
if (null == dir) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, getString(R.string.image_saved, file.getUri()), Toast.LENGTH_SHORT).show();
// Sync media store
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, file.getUri()));
}
private void saveImageTo(int page) {
if (null == mGalleryProvider) {
return;
}
File dir = getCacheDir();
UniFile file;
if (null == (file = mGalleryProvider.save(page, UniFile.fromFile(dir), mGalleryProvider.getImageFilename(page)))) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
String filename = file.getName();
if (filename == null) {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
return;
}
mCacheFileName = filename;
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_TITLE, filename);
try {
startActivityForResult(intent, WRITE_REQUEST_CODE);
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
Toast.makeText(this, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null){
Uri uri = resultData.getData();
String filepath = getCacheDir() + "/" + mCacheFileName;
File cachefile = new File(filepath);
InputStream is = null;
OutputStream os = null;
ContentResolver resolver = getContentResolver();
try {
is = new FileInputStream(cachefile);
os = resolver.openOutputStream(uri);
IOUtils.copy(is, os);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
cachefile.delete();
Toast.makeText(this, getString(R.string.image_saved, uri.getPath()), Toast.LENGTH_SHORT).show();
// Sync media store
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
}
}
private void showPageDialog(final int page) {
Resources resources = GalleryActivity.this.getResources();
AlertDialog.Builder builder = new AlertDialog.Builder(GalleryActivity.this);
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
final CharSequence[] items;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
items = new CharSequence[]{
getString(R.string.page_menu_refresh),
getString(R.string.page_menu_share),
getString(R.string.page_menu_save),
getString(R.string.page_menu_save_to)};
}else {
items = new CharSequence[]{
getString(R.string.page_menu_refresh),
getString(R.string.page_menu_share),
getString(R.string.page_menu_save)};
}
pageDialogListener(builder, items, page);
builder.show();
}
private void pageDialogListener(AlertDialog.Builder builder, CharSequence[] items, int page){
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (mGalleryProvider == null) {
return;
}
switch (which) {
case 0: // Refresh
mGalleryProvider.removeCache(page);
mGalleryProvider.forceRequest(page);
break;
case 1: // Share
shareImage(page);
break;
case 2: // Save
saveImage(page);
break;
case 3: // Save to
saveImageTo(page);
break;
}
}
});
}
private class NotifyTask implements Runnable {
public static final int KEY_LAYOUT_MODE = 0;
@@ -31,12 +31,14 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.android.resource.AttrResources;
@@ -47,229 +49,230 @@ import com.hippo.ehviewer.Hosts;
import com.hippo.ehviewer.R;
import com.hippo.ripple.Ripple;
import com.hippo.yorozuya.LayoutUtils;
import java.util.List;
import java.util.Locale;
public class HostsActivity extends ToolbarActivity
implements EasyRecyclerView.OnItemClickListener, View.OnClickListener {
implements EasyRecyclerView.OnItemClickListener, View.OnClickListener {
private static final String DIALOG_TAG_ADD_HOST = AddHostDialogFragment.class.getName();
private static final String DIALOG_TAG_EDIT_HOST = EditHostDialogFragment.class.getName();
private static final String DIALOG_TAG_ADD_HOST = AddHostDialogFragment.class.getName();
private static final String DIALOG_TAG_EDIT_HOST = EditHostDialogFragment.class.getName();
private static final String KEY_HOST = "com.hippo.ehviewer.ui.HostsActivity.HOST";
private static final String KEY_IP = "com.hippo.ehviewer.ui.HostsActivity.IP";
private static final String KEY_HOST = "com.hippo.ehviewer.ui.HostsActivity.HOST";
private static final String KEY_IP = "com.hippo.ehviewer.ui.HostsActivity.IP";
private Hosts hosts;
private List<Pair<String, String>> data;
private Hosts hosts;
private List<Pair<String, String>> data;
private EasyRecyclerView recyclerView;
private View tip;
private HostsAdapter adapter;
private EasyRecyclerView recyclerView;
private View tip;
private HostsAdapter adapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
hosts = EhApplication.getHosts(this);
data = hosts.getAll();
hosts = EhApplication.getHosts(this);
data = hosts.getAll();
setContentView(R.layout.activity_hosts);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
recyclerView = findViewById(R.id.recycler_view);
tip = findViewById(R.id.tip);
FloatingActionButton fab = findViewById(R.id.fab);
setContentView(R.layout.activity_hosts);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
recyclerView = findViewById(R.id.recycler_view);
tip = findViewById(R.id.tip);
FloatingActionButton fab = findViewById(R.id.fab);
adapter = new HostsAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
AttrResources.getAttrColor(this, R.attr.dividerColor),
LayoutUtils.dp2pix(this, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setSelector(Ripple.generateRippleDrawable(this, !AttrResources.getAttrBoolean(this, R.attr.isLightTheme), new ColorDrawable(Color.TRANSPARENT)));
recyclerView.setHasFixedSize(true);
recyclerView.setOnItemClickListener(this);
recyclerView.setPadding(
recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getPaddingRight(),
recyclerView.getPaddingBottom() + getResources().getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab));
adapter = new HostsAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
AttrResources.getAttrColor(this, R.attr.dividerColor),
LayoutUtils.dp2pix(this, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setSelector(Ripple.generateRippleDrawable(this, !AttrResources.getAttrBoolean(this, R.attr.isLightTheme), new ColorDrawable(Color.TRANSPARENT)));
recyclerView.setHasFixedSize(true);
recyclerView.setOnItemClickListener(this);
recyclerView.setPadding(
recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getPaddingRight(),
recyclerView.getPaddingBottom() + getResources().getDimensionPixelOffset(R.dimen.gallery_padding_bottom_fab));
fab.setOnClickListener(this);
fab.setOnClickListener(this);
recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE);
tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE);
}
recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE);
tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onItemClick(EasyRecyclerView easyRecyclerView, View view, int position, long id) {
Pair<String, String> pair = data.get(position);
Bundle args = new Bundle();
args.putString(KEY_HOST, pair.first);
args.putString(KEY_IP, pair.second);
DialogFragment fragment = new EditHostDialogFragment();
fragment.setArguments(args);
fragment.show(getSupportFragmentManager(), DIALOG_TAG_EDIT_HOST);
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onItemClick(EasyRecyclerView easyRecyclerView, View view, int position, long id) {
Pair<String, String> pair = data.get(position);
Bundle args = new Bundle();
args.putString(KEY_HOST, pair.first);
args.putString(KEY_IP, pair.second);
DialogFragment fragment = new EditHostDialogFragment();
fragment.setArguments(args);
fragment.show(getSupportFragmentManager(), DIALOG_TAG_EDIT_HOST);
return true;
}
@Override
public void onClick(View v) {
new AddHostDialogFragment().show(getSupportFragmentManager(), DIALOG_TAG_ADD_HOST);
}
private void notifyHostsChanges() {
data = hosts.getAll();
recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE);
tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE);
adapter.notifyDataSetChanged();
}
private class HostsHolder extends RecyclerView.ViewHolder {
public final TextView host;
public final TextView ip;
public HostsHolder(View itemView) {
super(itemView);
host = itemView.findViewById(R.id.host);
ip = itemView.findViewById(R.id.ip);
}
}
private class HostsAdapter extends RecyclerView.Adapter<HostsHolder> {
private final LayoutInflater inflater = getLayoutInflater();
@NonNull
@Override
public HostsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false));
}
@Override
public void onBindViewHolder(@NonNull HostsHolder holder, int position) {
Pair<String, String> pair = data.get(position);
holder.host.setText(pair.first);
holder.ip.setText(pair.second);
public void onClick(View v) {
new AddHostDialogFragment().show(getSupportFragmentManager(), DIALOG_TAG_ADD_HOST);
}
@Override
public int getItemCount() {
return data.size();
}
}
public abstract static class HostDialogFragment extends DialogFragment {
private TextView host;
private TextView ip;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_hosts, null, false);
host = view.findViewById(R.id.host);
ip = view.findViewById(R.id.ip);
Bundle arguments = getArguments();
if (savedInstanceState == null && arguments != null) {
host.setText(arguments.getString(KEY_HOST));
ip.setText(arguments.getString(KEY_IP));
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext()).setView(view);
onCreateDialogBuilder(builder);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
return dialog;
private void notifyHostsChanges() {
data = hosts.getAll();
recyclerView.setVisibility(data.isEmpty() ? View.GONE : View.VISIBLE);
tip.setVisibility(data.isEmpty() ? View.VISIBLE : View.GONE);
adapter.notifyDataSetChanged();
}
protected abstract void onCreateDialogBuilder(AlertDialog.Builder builder);
public abstract static class HostDialogFragment extends DialogFragment {
protected abstract void onCreateDialog(AlertDialog dialog);
private TextView host;
private TextView ip;
protected void put(AlertDialog dialog) {
TextView host = dialog.findViewById(R.id.host);
TextView ip = dialog.findViewById(R.id.ip);
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
String ipString = ip.getText().toString().trim();
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_hosts, null, false);
host = view.findViewById(R.id.host);
ip = view.findViewById(R.id.ip);
if (!Hosts.isValidHost(hostString)) {
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
hostInputLayout.setError(getContext().getString(R.string.invalid_host));
return;
}
Bundle arguments = getArguments();
if (savedInstanceState == null && arguments != null) {
host.setText(arguments.getString(KEY_HOST));
ip.setText(arguments.getString(KEY_IP));
}
if (!Hosts.isValidIp(ipString)) {
TextInputLayout ipInputLayout = dialog.findViewById(R.id.ip_input_layout);
ipInputLayout.setError(getContext().getString(R.string.invalid_ip));
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(getContext()).setView(view);
onCreateDialogBuilder(builder);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
HostsActivity activity = (HostsActivity) dialog.getOwnerActivity();
activity.hosts.put(hostString, ipString);
activity.notifyHostsChanges();
return dialog;
}
dialog.dismiss();
protected abstract void onCreateDialogBuilder(AlertDialog.Builder builder);
protected abstract void onCreateDialog(AlertDialog dialog);
protected void put(AlertDialog dialog) {
TextView host = dialog.findViewById(R.id.host);
TextView ip = dialog.findViewById(R.id.ip);
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
String ipString = ip.getText().toString().trim();
if (!Hosts.isValidHost(hostString)) {
TextInputLayout hostInputLayout = dialog.findViewById(R.id.host_input_layout);
hostInputLayout.setError(getContext().getString(R.string.invalid_host));
return;
}
if (!Hosts.isValidIp(ipString)) {
TextInputLayout ipInputLayout = dialog.findViewById(R.id.ip_input_layout);
ipInputLayout.setError(getContext().getString(R.string.invalid_ip));
return;
}
HostsActivity activity = (HostsActivity) dialog.getOwnerActivity();
activity.hosts.put(hostString, ipString);
activity.notifyHostsChanges();
dialog.dismiss();
}
protected void delete(AlertDialog dialog) {
TextView host = dialog.findViewById(R.id.host);
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
HostsActivity activity = (HostsActivity) dialog.getOwnerActivity();
activity.hosts.delete(hostString);
activity.notifyHostsChanges();
dialog.dismiss();
}
}
protected void delete(AlertDialog dialog) {
TextView host = dialog.findViewById(R.id.host);
String hostString = host.getText().toString().trim().toLowerCase(Locale.US);
public static class AddHostDialogFragment extends HostDialogFragment {
HostsActivity activity = (HostsActivity) dialog.getOwnerActivity();
activity.hosts.delete(hostString);
activity.notifyHostsChanges();
@Override
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
builder.setTitle(R.string.add_host);
builder.setPositiveButton(R.string.add_host_add, null);
}
dialog.dismiss();
}
}
public static class AddHostDialogFragment extends HostDialogFragment {
@Override
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
builder.setTitle(R.string.add_host);
builder.setPositiveButton(R.string.add_host_add, null);
@Override
protected void onCreateDialog(AlertDialog dialog) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
}
}
@Override
protected void onCreateDialog(AlertDialog dialog) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
}
}
public static class EditHostDialogFragment extends HostDialogFragment {
public static class EditHostDialogFragment extends HostDialogFragment {
@Override
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
builder.setTitle(R.string.edit_host);
builder.setPositiveButton(R.string.edit_host_confirm, null);
builder.setNegativeButton(R.string.edit_host_delete, null);
}
@Override
protected void onCreateDialogBuilder(AlertDialog.Builder builder) {
builder.setTitle(R.string.edit_host);
builder.setPositiveButton(R.string.edit_host_confirm, null);
builder.setNegativeButton(R.string.edit_host_delete, null);
@Override
protected void onCreateDialog(AlertDialog dialog) {
dialog.findViewById(R.id.host_input_layout).setEnabled(false);
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> delete(dialog));
}
}
@Override
protected void onCreateDialog(AlertDialog dialog) {
dialog.findViewById(R.id.host_input_layout).setEnabled(false);
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> put(dialog));
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(v -> delete(dialog));
private class HostsHolder extends RecyclerView.ViewHolder {
public final TextView host;
public final TextView ip;
public HostsHolder(View itemView) {
super(itemView);
host = itemView.findViewById(R.id.host);
ip = itemView.findViewById(R.id.ip);
}
}
private class HostsAdapter extends RecyclerView.Adapter<HostsHolder> {
private final LayoutInflater inflater = getLayoutInflater();
@NonNull
@Override
public HostsHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new HostsHolder(inflater.inflate(R.layout.item_hosts, parent, false));
}
@Override
public void onBindViewHolder(@NonNull HostsHolder holder, int position) {
Pair<String, String> pair = data.get(position);
holder.host.setText(pair.first);
holder.ip.setText(pair.second);
}
@Override
public int getItemCount() {
return data.size();
}
}
}
}
@@ -19,7 +19,9 @@ package com.hippo.ehviewer.ui;
import android.os.Bundle;
import android.view.MenuItem;
import android.webkit.WebView;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.R;
public class LicenseActivity extends ToolbarActivity {
@@ -36,14 +36,16 @@ import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.drawerlayout.DrawerLayout;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
@@ -85,9 +87,9 @@ import com.hippo.util.BitmapUtils;
import com.hippo.util.PermissionRequester;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -103,24 +105,6 @@ public final class MainActivity extends StageActivity
private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item";
private static final String KEY_CLIP_TEXT_HASH_CODE = "clip_text_hash_code";
/*---------------
Whole life cycle
---------------*/
@Nullable
private EhDrawerLayout mDrawerLayout;
@Nullable
private NavigationView mNavView;
@Nullable
private FrameLayout mRightDrawer;
@Nullable
private LoadImageView mAvatar;
@Nullable
private TextView mDisplayName;
@Nullable
private Button mChangeTheme;
private int mNavCheckedItem = 0;
static {
registerLaunchMode(SecurityScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(WarningScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
@@ -142,6 +126,23 @@ public final class MainActivity extends StageActivity
registerLaunchMode(ProgressScene.class, SceneFragment.LAUNCH_MODE_STANDARD);
}
/*---------------
Whole life cycle
---------------*/
@Nullable
private EhDrawerLayout mDrawerLayout;
@Nullable
private NavigationView mNavView;
@Nullable
private FrameLayout mRightDrawer;
@Nullable
private LoadImageView mAvatar;
@Nullable
private TextView mDisplayName;
@Nullable
private Button mChangeTheme;
private int mNavCheckedItem = 0;
@Override
protected int getThemeResId(int theme) {
switch (theme) {
@@ -334,7 +335,7 @@ public final class MainActivity extends StageActivity
mDisplayName = (TextView) ViewUtils.$$(headerLayout, R.id.display_name);
mChangeTheme = (Button) ViewUtils.$$(this, R.id.change_theme);
mDrawerLayout.setStatusBarColor(ResourcesUtils.getAttrColor(this, R.attr.colorPrimaryDark));
//mDrawerLayout.setStatusBarColor(ResourcesUtils.getAttrColor(this, R.attr.colorPrimaryDark));
// Pre-L need shadow drawable
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow_left, Gravity.LEFT);
@@ -524,7 +525,7 @@ public final class MainActivity extends StageActivity
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
if (grantResults.length == 1 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.you_rejected_me, Toast.LENGTH_SHORT).show();
@@ -629,15 +630,9 @@ public final class MainActivity extends StageActivity
}
}
public void setDrawerGestureBlocker(DrawerLayout.GestureBlocker gestureBlocker) {
if (mDrawerLayout != null) {
mDrawerLayout.setGestureBlocker(gestureBlocker);
}
}
public boolean isDrawersVisible() {
if (mDrawerLayout != null) {
return mDrawerLayout.isDrawersVisible();
return mDrawerLayout.isDrawerVisible(mDrawerLayout);
} else {
return false;
}
@@ -26,84 +26,87 @@ import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.widget.DialogWebChromeClient;
import com.hippo.widget.ProgressView;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
public class MyTagsActivity extends ToolbarActivity {
private WebView webView;
private ProgressView progress;
private String url;
private WebView webView;
private ProgressView progress;
private String url;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
cookieManager.removeAllCookies(null);
cookieManager.removeSessionCookies(null);
} else {
CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(this);
cookieSyncManager.startSync();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieSyncManager.stopSync();
}
// Copy cookies from okhttp cookie store to CookieManager
url = EhUrl.getMyTagsUrl();
EhCookieStore store = EhApplication.getEhCookieStore(this);
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
cookieManager.setCookie(url, cookie.toString());
}
setContentView(R.layout.activity_my_tags);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyTagsWebViewClient());
webView.setWebChromeClient(new DialogWebChromeClient(this));
webView.loadUrl(url);
progress = findViewById(R.id.progress);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private class MyTagsWebViewClient extends WebViewClient {
@SuppressLint("SetJavaScriptEnabled")
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Never load other urls
return !url.equals(MyTagsActivity.this.url);
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager cookieManager = CookieManager.getInstance();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
cookieManager.removeAllCookies(null);
cookieManager.removeSessionCookies(null);
} else {
CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(this);
cookieSyncManager.startSync();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieSyncManager.stopSync();
}
// Copy cookies from okhttp cookie store to CookieManager
url = EhUrl.getMyTagsUrl();
EhCookieStore store = EhApplication.getEhCookieStore(this);
for (Cookie cookie : store.getCookies(HttpUrl.parse(url))) {
cookieManager.setCookie(url, cookie.toString());
}
setContentView(R.layout.activity_my_tags);
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new MyTagsWebViewClient());
webView.setWebChromeClient(new DialogWebChromeClient(this));
webView.loadUrl(url);
progress = findViewById(R.id.progress);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
progress.setVisibility(View.VISIBLE);
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onPageFinished(WebView view, String url) {
progress.setVisibility(View.GONE);
private class MyTagsWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// Never load other urls
return !url.equals(MyTagsActivity.this.url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
progress.setVisibility(View.VISIBLE);
}
@Override
public void onPageFinished(WebView view, String url) {
progress.setVisibility(View.GONE);
}
}
}
}
@@ -23,8 +23,10 @@ import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.widget.lockpattern.LockPatternUtils;
@@ -42,6 +44,16 @@ public class SetSecurityActivity extends ToolbarActivity implements View.OnClick
@Nullable
private CheckBox mFingerprint;
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean hasEnrolledFingerprints(FingerprintManager fingerprintManager) {
try {
return fingerprintManager.isHardwareDetected()
&& fingerprintManager.hasEnrolledFingerprints();
} catch (Throwable e) {
return false;
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -73,16 +85,6 @@ public class SetSecurityActivity extends ToolbarActivity implements View.OnClick
mSet.setOnClickListener(this);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean hasEnrolledFingerprints(FingerprintManager fingerprintManager) {
try {
return fingerprintManager.isHardwareDetected()
&& fingerprintManager.hasEnrolledFingerprints();
} catch (Throwable e) {
return false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Loaded 100 of 248 files, more files were not shown because too many files have changed in this diff. Show more