diff --git a/README.md b/README.md
index 7c014360e..b7249d50a 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,11 @@
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
## 阅读3.0
-书源规则 https://celeter.github.io
-
-[![aVElJs.th.jpg](https://s1.ax1x.com/2020/07/28/aVElJs.th.jpg)](https://imgchr.com/i/aVElJs) [![aVEQij.th.jpg](https://s1.ax1x.com/2020/07/28/aVEQij.th.jpg)](https://imgchr.com/i/aVEQij) [![aVEudg.th.jpg](https://s1.ax1x.com/2020/07/28/aVEudg.th.jpg)](https://imgchr.com/i/aVEudg) [![aVEneS.th.jpg](https://s1.ax1x.com/2020/07/28/aVEneS.th.jpg)](https://imgchr.com/i/aVEneS) [![aVEKoQ.th.jpg](https://s1.ax1x.com/2020/07/28/aVEKoQ.th.jpg)](https://imgchr.com/i/aVEKoQ) [![aVE1Wn.th.jpg](https://s1.ax1x.com/2020/07/28/aVE1Wn.th.jpg)](https://imgchr.com/i/aVE1Wn)
+书源规则 https://celeter.github.io
+### 阅读API
+阅读3.0 提供了2种方式的API:`Web方式`和`Content Provider方式`。您可以在[这里](api.md)根据需要自行调用。
## 免责声明
+[![aVElJs.th.jpg](https://s1.ax1x.com/2020/07/28/aVElJs.th.jpg)](https://imgchr.com/i/aVElJs) [![aVEQij.th.jpg](https://s1.ax1x.com/2020/07/28/aVEQij.th.jpg)](https://imgchr.com/i/aVEQij) [![aVEudg.th.jpg](https://s1.ax1x.com/2020/07/28/aVEudg.th.jpg)](https://imgchr.com/i/aVEudg) [![aVEneS.th.jpg](https://s1.ax1x.com/2020/07/28/aVEneS.th.jpg)](https://imgchr.com/i/aVEneS) [![aVEKoQ.th.jpg](https://s1.ax1x.com/2020/07/28/aVEKoQ.th.jpg)](https://imgchr.com/i/aVEKoQ) [![aVE1Wn.th.jpg](https://s1.ax1x.com/2020/07/28/aVE1Wn.th.jpg)](https://imgchr.com/i/aVE1Wn)
+
https://gedoor.github.io/MyBookshelf/disclaimer.html
diff --git a/api.md b/api.md
new file mode 100644
index 000000000..3c4aa0983
--- /dev/null
+++ b/api.md
@@ -0,0 +1,88 @@
+#阅读API
+## 对于Web的配置
+您需要先在设置中启用"Web 服务"。
+## 使用
+### Web
+待补充
+### Content Provider
+#### 插入单个书源
+```
+URL = content://io.legado.app.api.ReaderProvider/source/insert
+Method = insert
+```
+
+创建`Key="json"`的`ContentValues`,内容为`JSON`字符串,
+格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt)
+
+#### 插入多个书源
+```
+URL = content://io.legado.app.api.ReaderProvider/sources/insert
+Method = insert
+```
+
+创建`Key="json"`的`ContentValues`,内容为`JSON`字符串,
+格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。
+
+#### 获取书源
+```
+URL = content://io.legado.app.api.ReaderProvider/source/query?url=xxx
+Method = query
+```
+
+获取指定URL对应的书源信息。
+用`Cursor.getString(0)`取出返回结果。
+
+#### 获取所有书源
+```
+URL = content://io.legado.app.api.ReaderProvider/sources/query
+Method = query
+```
+
+获取APP内的所有书源。
+用`Cursor.getString(0)`取出返回结果。
+
+#### 删除多个书源
+```
+URL = content://io.legado.app.api.ReaderProvider/sources/delete
+Method = delete
+```
+
+创建`Key="json"`的`ContentValues`,内容为`JSON`字符串,
+格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookSource.kt),**为数组格式**。
+
+#### 插入书籍
+```
+URL = content://io.legado.app.api.ReaderProvider/book/insert
+Method = insert
+```
+
+创建`Key="json"`的`ContentValues`,内容为`JSON`字符串,
+格式参考[这个文件](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)。
+
+#### 获取所有书籍
+```
+URL = content://io.legado.app.api.ReaderProvider/books/query
+Method = query
+```
+
+获取APP内的所有书籍。
+用`Cursor.getString(0)`取出返回结果。
+
+#### 获取书籍章节列表
+```
+URL = content://io.legado.app.api.ReaderProvider/book/chapter/query?url=xxx
+Method = query
+```
+
+获取指定图书的章节列表。
+用`Cursor.getString(0)`取出返回结果。
+
+#### 获取书籍内容
+
+```
+URL = content://io.legado.app.api.ReaderProvider/book/content/query?url=xxx&index=1
+Method = query
+```
+
+获取指定图书的第`index`章节的文本内容。
+用`Cursor.getString(0)`取出返回结果。
\ No newline at end of file
diff --git a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt
index 86b312778..9820b40fb 100644
--- a/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt
@@ -1,5 +1,7 @@
package io.legado.app
+import android.net.Uri
+import android.util.Log
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
@@ -16,9 +18,12 @@ import org.junit.Assert.*
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
- fun useAppContext() {
+ fun testContentProvider() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
- assertEquals("cn.legado.book", appContext.packageName)
- }
+ Log.d("test",
+ appContext.contentResolver.query(Uri.parse("content://io.legado.app.api.ReaderProvider/sources/query"),null,null,null,null)
+ !!.getString(0)
+ )
+ }
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index db69e078b..7d0a3f19e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -25,164 +25,163 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
- android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
+ android:supportsRtl="true"
android:theme="@style/AppTheme.Light"
tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute">
-
+
+
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher1">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher2">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher3">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher4">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher5">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
+ android:enabled="false"
+ android:icon="@mipmap/launcher6">
+
+
-
-
+ android:launchMode="singleTask"
+ android:resource="@xml/shortcuts" />
+
-
+ android:alwaysRetainTaskState="true"
+ android:launchMode="singleTask" />
-
+ android:launchMode="singleTask" />
-
+ android:launchMode="singleTop" />
-
+ android:name=".ui.book.info.edit.BookInfoEditActivity"
+ android:launchMode="singleTask" />
-
+ android:launchMode="singleTask" />
-
+ android:theme="@style/Activity.Permission" />
-
+ android:launchMode="singleTask" />
-
+ android:windowSoftInputMode="adjustResize|stateHidden" />
-
+ android:windowSoftInputMode="adjustResize|stateHidden" />
-
+ android:launchMode="singleTask" />
-
+ android:name=".ui.book.search.SearchActivity"
+ android:launchMode="singleTask" />
-
+ android:launchMode="singleTask" />
-
+ android:launchMode="singleTask" />
@@ -196,8 +195,7 @@
android:host="booksource"
android:scheme="yuedu" />
-
-
+
@@ -211,8 +209,7 @@
android:host="rsssource"
android:scheme="yuedu" />
-
-
+
@@ -226,67 +223,59 @@
android:host="replace"
android:scheme="yuedu" />
-
-
+
-
+ android:launchMode="singleTop" />
-
+ android:launchMode="singleTop" />
-
+ android:name=".ui.book.chapterlist.ChapterListActivity"
+ android:launchMode="singleTop"
+ android:screenOrientation="behind" />
-
+ android:launchMode="singleTop" />
-
+ android:launchMode="singleTop" />
-
+ android:name=".ui.book.local.ImportBookActivity"
+ android:launchMode="singleTop" />
-
+ android:name=".ui.book.explore.ExploreShowActivity"
+ android:launchMode="singleTop" />
-
+ android:launchMode="singleTop" />
-
+ android:launchMode="singleTop" />
-
+ android:name=".ui.book.download.DownloadActivity"
+ android:launchMode="singleTop" />
-
+ android:hardwareAccelerated="true" />
+
+
+
+
diff --git a/app/src/main/java/io/legado/app/api/ReaderProvider.java b/app/src/main/java/io/legado/app/api/ReaderProvider.java
new file mode 100644
index 000000000..81d918219
--- /dev/null
+++ b/app/src/main/java/io/legado/app/api/ReaderProvider.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2020 w568w
+ */
+
+package io.legado.app.api;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.CharArrayBuffer;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.google.gson.Gson;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.legado.app.web.controller.BookshelfController;
+import io.legado.app.web.controller.SourceController;
+import io.legado.app.web.utils.ReturnData;
+
+/**
+ * Export book data to other app.
+ */
+public class ReaderProvider extends ContentProvider {
+ private enum RequestCode {
+ saveSource, saveSources, saveBook, deleteSources, getSource, getSources, getBookshelf, getChapterList, getBookContent
+ }
+
+ public static final String POST_BODY_KEY = "json";
+ public static final String AUTHORITY = "io.legado.app.api.ReaderProvider";
+ private static UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ private SourceController mSourceController;
+ private BookshelfController mBookshelfController;
+
+ static {
+ sMatcher.addURI(AUTHORITY, "source/insert", RequestCode.saveSource.ordinal());
+ sMatcher.addURI(AUTHORITY, "sources/insert", RequestCode.saveSources.ordinal());
+ sMatcher.addURI(AUTHORITY, "book/insert", RequestCode.saveBook.ordinal());
+ sMatcher.addURI(AUTHORITY, "sources/delete", RequestCode.deleteSources.ordinal());
+ sMatcher.addURI(AUTHORITY, "source/query", RequestCode.getSource.ordinal());
+ sMatcher.addURI(AUTHORITY, "sources/query", RequestCode.getSources.ordinal());
+ sMatcher.addURI(AUTHORITY, "books/query", RequestCode.getBookshelf.ordinal());
+ sMatcher.addURI(AUTHORITY, "book/chapter/query", RequestCode.getChapterList.ordinal());
+ sMatcher.addURI(AUTHORITY, "book/content/query", RequestCode.getBookContent.ordinal());
+ }
+
+ public ReaderProvider() {
+ }
+
+ @Override
+ public int delete(@NotNull Uri uri, String selection, String[] selectionArgs) {
+ if (sMatcher.match(uri) < 0)
+ return -1;
+ switch (RequestCode.values()[sMatcher.match(uri)]) {
+ case deleteSources:
+ mSourceController.deleteSources(selection);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name());
+ }
+ return 0;
+ }
+
+ @Override
+ public String getType(@NotNull Uri uri) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public Uri insert(@NotNull Uri uri, ContentValues values) {
+ if (sMatcher.match(uri) < 0)
+ return null;
+ switch (RequestCode.values()[sMatcher.match(uri)]) {
+ case saveSource:
+ mSourceController.saveSource(values.getAsString(POST_BODY_KEY));
+ break;
+ case saveBook:
+ mBookshelfController.saveBook(values.getAsString(POST_BODY_KEY));
+ case saveSources:
+ mSourceController.saveSources(values.getAsString(POST_BODY_KEY));
+ default:
+ throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name());
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onCreate() {
+ mSourceController = new SourceController();
+ mBookshelfController = new BookshelfController();
+ return false;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+
+ Map> map = new HashMap<>();
+
+ ArrayList list = new ArrayList<>();
+ list.add(uri.getQueryParameter("url"));
+ map.put("url", list);
+
+ list = new ArrayList<>();
+ list.add(uri.getQueryParameter("index"));
+ map.put("index", list);
+
+ if (sMatcher.match(uri) < 0)
+ return null;
+ switch (RequestCode.values()[sMatcher.match(uri)]) {
+ case getSource:
+ return new SimpleCursor(mSourceController.getSource(map));
+ case getSources:
+ return new SimpleCursor(mSourceController.getSources());
+ case getBookshelf:
+ return new SimpleCursor(mBookshelfController.getBookshelf());
+ case getBookContent:
+ return new SimpleCursor(mBookshelfController.getBookContent(map));
+ case getChapterList:
+ return new SimpleCursor(mBookshelfController.getChapterList(map));
+ default:
+ throw new IllegalStateException("Unexpected value: " + RequestCode.values()[sMatcher.match(uri)].name());
+ }
+ }
+
+ @Override
+ public int update(@NotNull Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ /**
+ * Simple inner class to deliver json callback data.
+ *
+ * Only getString() makes sense.
+ */
+ private static class SimpleCursor implements Cursor {
+ private String mData;
+
+ public SimpleCursor(ReturnData data) {
+ this.mData = new Gson().toJson(data);
+ }
+
+ @Override
+ public int getCount() {
+ return 1;
+ }
+
+ @Override
+ public int getPosition() {
+ return 0;
+ }
+
+ @Override
+ public boolean move(int i) {
+ return true;
+ }
+
+ @Override
+ public boolean moveToPosition(int i) {
+ return true;
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ return true;
+ }
+
+ @Override
+ public boolean moveToLast() {
+ return true;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ return true;
+ }
+
+ @Override
+ public boolean moveToPrevious() {
+ return true;
+ }
+
+ @Override
+ public boolean isFirst() {
+ return true;
+ }
+
+ @Override
+ public boolean isLast() {
+ return true;
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ return false;
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return false;
+ }
+
+ @Override
+ public int getColumnIndex(String s) {
+ return 0;
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String s) throws IllegalArgumentException {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
+ @Override
+ public String getColumnName(int i) {
+ return null;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return new String[0];
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getBlob(int i) {
+ return new byte[0];
+ }
+
+ @Override
+ public String getString(int i) {
+ return mData;
+ }
+
+ @Override
+ public void copyStringToBuffer(int i, CharArrayBuffer charArrayBuffer) {
+
+ }
+
+ @Override
+ public short getShort(int i) {
+ return 0;
+ }
+
+ @Override
+ public int getInt(int i) {
+ return 0;
+ }
+
+ @Override
+ public long getLong(int i) {
+ return 0;
+ }
+
+ @Override
+ public float getFloat(int i) {
+ return 0;
+ }
+
+ @Override
+ public double getDouble(int i) {
+ return 0;
+ }
+
+ @Override
+ public int getType(int i) {
+ return 0;
+ }
+
+ @Override
+ public boolean isNull(int i) {
+ return false;
+ }
+
+ @Override
+ public void deactivate() {
+
+ }
+
+ @Override
+ public boolean requery() {
+ return false;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ public void registerContentObserver(ContentObserver contentObserver) {
+
+ }
+
+ @Override
+ public void unregisterContentObserver(ContentObserver contentObserver) {
+
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver dataSetObserver) {
+
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver dataSetObserver) {
+
+ }
+
+ @Override
+ public void setNotificationUri(ContentResolver contentResolver, Uri uri) {
+
+ }
+
+ @Override
+ public Uri getNotificationUri() {
+ return null;
+ }
+
+ @Override
+ public boolean getWantsAllOnMoveCalls() {
+ return false;
+ }
+
+ @Override
+ public void setExtras(Bundle bundle) {
+
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return null;
+ }
+
+ @Override
+ public Bundle respond(Bundle bundle) {
+ return null;
+ }
+ }
+}