diff --git a/app/build.gradle b/app/build.gradle index 51ef5f575..52929ed73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,6 +11,11 @@ android { minSdk 21 targetSdk 29 ndk { abiFilters "armeabi-v7a" } + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] + } + } } productFlavors { diff --git a/app/schemas/com.fongmi.android.tv.db.AppDatabase/21.json b/app/schemas/com.fongmi.android.tv.db.AppDatabase/21.json new file mode 100644 index 000000000..b2aa1a5e0 --- /dev/null +++ b/app/schemas/com.fongmi.android.tv.db.AppDatabase/21.json @@ -0,0 +1,432 @@ +{ + "formatVersion": 1, + "database": { + "version": 21, + "identityHash": "0dd6fc8bd9e6415bed916c27d8febede", + "entities": [ + { + "tableName": "Keep", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `siteName` TEXT, `vodName` TEXT, `vodPic` TEXT, `createTime` INTEGER NOT NULL, `type` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteName", + "columnName": "siteName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Site", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `name` TEXT, `searchable` INTEGER, `filterable` INTEGER, `changeable` INTEGER, `activated` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "searchable", + "columnName": "searchable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "filterable", + "columnName": "filterable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "changeable", + "columnName": "changeable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "activated", + "columnName": "activated", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Track", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `track` INTEGER NOT NULL, `player` INTEGER NOT NULL, `key` TEXT, `name` TEXT, `selected` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "player", + "columnName": "player", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "selected", + "columnName": "selected", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Track_key_player_type", + "unique": true, + "columnNames": [ + "key", + "player", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Track_key_player_type` ON `${TABLE_NAME}` (`key`, `player`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `time` INTEGER NOT NULL, `url` TEXT, `json` TEXT, `name` TEXT, `home` TEXT, `parse` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "json", + "columnName": "json", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "home", + "columnName": "home", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parse", + "columnName": "parse", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Config_url_type", + "unique": true, + "columnNames": [ + "url", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Config_url_type` ON `${TABLE_NAME}` (`url`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` TEXT, `name` TEXT, `ip` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Device_uuid_name", + "unique": true, + "columnNames": [ + "uuid", + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Device_uuid_name` ON `${TABLE_NAME}` (`uuid`, `name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "History", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `vodPic` TEXT, `vodName` TEXT, `vodFlag` TEXT, `vodRemarks` TEXT, `episodeUrl` TEXT, `revSort` INTEGER NOT NULL, `revPlay` INTEGER NOT NULL, `createTime` INTEGER NOT NULL, `opening` INTEGER NOT NULL, `ending` INTEGER NOT NULL, `position` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `speed` REAL NOT NULL, `player` INTEGER NOT NULL, `scale` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodFlag", + "columnName": "vodFlag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodRemarks", + "columnName": "vodRemarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episodeUrl", + "columnName": "episodeUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revSort", + "columnName": "revSort", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "revPlay", + "columnName": "revPlay", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "opening", + "columnName": "opening", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ending", + "columnName": "ending", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "speed", + "columnName": "speed", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "player", + "columnName": "player", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scale", + "columnName": "scale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0dd6fc8bd9e6415bed916c27d8febede')" + ] + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java index d8d648dee..dd4e509b8 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -20,12 +20,14 @@ import com.fongmi.android.tv.Updater; import com.fongmi.android.tv.api.ApiConfig; import com.fongmi.android.tv.api.LiveConfig; import com.fongmi.android.tv.api.WallConfig; +import com.fongmi.android.tv.bean.Config; import com.fongmi.android.tv.bean.Func; import com.fongmi.android.tv.bean.History; import com.fongmi.android.tv.bean.Result; import com.fongmi.android.tv.bean.Site; import com.fongmi.android.tv.bean.Vod; import com.fongmi.android.tv.databinding.ActivityHomeBinding; +import com.fongmi.android.tv.event.CastEvent; import com.fongmi.android.tv.event.RefreshEvent; import com.fongmi.android.tv.event.ServerEvent; import com.fongmi.android.tv.model.SiteViewModel; @@ -310,6 +312,33 @@ public class HomeActivity extends BaseActivity implements CustomTitleView.Listen } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onCastEvent(CastEvent event) { + if (ApiConfig.getUrl().equals(event.getUrl())) { + History history = History.objectFrom(event.getHistory()); + history.setCid(ApiConfig.getCid()); + onItemClick(history.update()); + } else { + ApiConfig.get().clear().config(Config.find(event.getUrl(), 0)).load(getCallback(event)); + } + } + + private Callback getCallback(CastEvent event) { + return new Callback() { + @Override + public void success() { + RefreshEvent.history(); + RefreshEvent.video(); + onCastEvent(event); + } + + @Override + public void error(int resId) { + Notify.show(resId); + } + }; + } + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (Utils.isMenuKey(event)) showDialog(); diff --git a/app/src/main/java/com/fongmi/android/tv/bean/History.java b/app/src/main/java/com/fongmi/android/tv/bean/History.java index 06a5a4b7b..e37eb7f6d 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/History.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/History.java @@ -7,6 +7,8 @@ import androidx.room.PrimaryKey; import com.fongmi.android.tv.R; import com.fongmi.android.tv.api.ApiConfig; import com.fongmi.android.tv.db.AppDatabase; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; import java.util.List; @@ -15,24 +17,45 @@ public class History { @NonNull @PrimaryKey + @SerializedName("key") private String key; + @SerializedName("vodPic") private String vodPic; + @SerializedName("vodName") private String vodName; + @SerializedName("vodFlag") private String vodFlag; + @SerializedName("vodRemarks") private String vodRemarks; + @SerializedName("episodeUrl") private String episodeUrl; + @SerializedName("revSort") private boolean revSort; + @SerializedName("revPlay") private boolean revPlay; + @SerializedName("createTime") private long createTime; + @SerializedName("opening") private long opening; + @SerializedName("ending") private long ending; + @SerializedName("position") private long position; + @SerializedName("duration") private long duration; + @SerializedName("speed") private float speed; + @SerializedName("player") private int player; + @SerializedName("scale") private int scale; + @SerializedName("cid") private int cid; + public static History objectFrom(String str) { + return new Gson().fromJson(str, History.class); + } + public History() { this.speed = 1; this.scale = -1; @@ -228,8 +251,13 @@ public class History { public void update(long position, long duration) { setPosition(position); setDuration(duration); + update(); + } + + public History update() { checkMerge(AppDatabase.get().getHistoryDao().findByName(ApiConfig.getCid(), getVodName())); AppDatabase.get().getHistoryDao().insertOrUpdate(this); + return this; } public History delete() { diff --git a/app/src/main/java/com/fongmi/android/tv/server/Nano.java b/app/src/main/java/com/fongmi/android/tv/server/Nano.java index cc279aa24..c60bc9c77 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/Nano.java +++ b/app/src/main/java/com/fongmi/android/tv/server/Nano.java @@ -2,6 +2,7 @@ package com.fongmi.android.tv.server; import com.fongmi.android.tv.R; import com.fongmi.android.tv.api.ApiConfig; +import com.fongmi.android.tv.bean.Device; import com.fongmi.android.tv.server.process.InputRequestProcess; import com.fongmi.android.tv.server.process.RawRequestProcess; import com.fongmi.android.tv.server.process.RequestProcess; @@ -71,6 +72,7 @@ public class Nano extends NanoHTTPD { case GET: if (url.startsWith("/file")) return doFile(url); else if (url.startsWith("/proxy")) return doProxy(session.getParms()); + else if (url.startsWith("/device")) return createPlainTextResponse(NanoHTTPD.Response.Status.OK, Device.get().toString()); break; case POST: if (url.startsWith("/upload")) return doUpload(session.getParms(), files); @@ -99,9 +101,9 @@ public class Nano extends NanoHTTPD { String path = url.substring(6); File file = FileUtil.getRootFile(path); if (file.isFile()) return newChunkedResponse(Response.Status.OK, "application/octet-stream", new FileInputStream(file)); - else return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, listFiles(file)); + else return createPlainTextResponse(Response.Status.OK, listFiles(file)); } catch (Exception e) { - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage()); + return createPlainTextResponse(Response.Status.INTERNAL_ERROR, e.getMessage()); } } @@ -110,7 +112,7 @@ public class Nano extends NanoHTTPD { Object[] rs = ApiConfig.get().proxyLocal(params); return newChunkedResponse(Response.Status.lookup((Integer) rs[0]), (String) rs[1], (InputStream) rs[2]); } catch (Exception e) { - return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "500"); + return createPlainTextResponse(Response.Status.INTERNAL_ERROR, "500"); } } @@ -122,20 +124,20 @@ public class Nano extends NanoHTTPD { if (fn.toLowerCase().endsWith(".zip")) FileUtil.unzip(temp, FileUtil.getRootPath() + File.separator + path); else FileUtil.copy(temp, FileUtil.getRootFile(path + File.separator + fn)); } - return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); + return createPlainTextResponse(Response.Status.OK, "OK"); } private Response doNewFolder(Map params) { String path = params.get("path"); String name = params.get("name"); FileUtil.getRootFile(path + File.separator + name).mkdirs(); - return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); + return createPlainTextResponse(Response.Status.OK, "OK"); } private Response doDelFolder(Map params) { String path = params.get("path"); FileUtil.clearDir(FileUtil.getRootFile(path)); - return newFixedLengthResponse(Response.Status.OK, MIME_PLAINTEXT, "OK"); + return createPlainTextResponse(Response.Status.OK, "OK"); } private String getParent(File root) { @@ -175,10 +177,12 @@ public class Nano extends NanoHTTPD { public interface Listener { - void onSearch(String text); + void onSearch(String word); - void onPush(String text); + void onPush(String url); - void onApi(String text); + void onApi(String url); + + void onCast(String url, String history); } } diff --git a/app/src/main/java/com/fongmi/android/tv/server/Server.java b/app/src/main/java/com/fongmi/android/tv/server/Server.java index 00494deaa..f4a0121c6 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/Server.java +++ b/app/src/main/java/com/fongmi/android/tv/server/Server.java @@ -5,6 +5,7 @@ import android.net.wifi.WifiManager; import android.text.format.Formatter; import com.fongmi.android.tv.App; +import com.fongmi.android.tv.event.CastEvent; import com.fongmi.android.tv.event.ServerEvent; import java.net.Inet4Address; @@ -86,17 +87,22 @@ public class Server implements Nano.Listener { } @Override - public void onSearch(String text) { - if (text.length() > 0) ServerEvent.search(text); + public void onSearch(String word) { + if (word.length() > 0) ServerEvent.search(word); } @Override - public void onPush(String text) { - if (text.length() > 0) ServerEvent.push(text); + public void onPush(String url) { + if (url.length() > 0) ServerEvent.push(url); } @Override - public void onApi(String text) { - if (text.length() > 0) ServerEvent.api(text); + public void onApi(String url) { + if (url.length() > 0) ServerEvent.api(url); + } + + @Override + public void onCast(String url, String history) { + CastEvent.post(url, history); } } diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/InputRequestProcess.java b/app/src/main/java/com/fongmi/android/tv/server/process/InputRequestProcess.java index ad0f4c01a..95998a318 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/InputRequestProcess.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/InputRequestProcess.java @@ -33,6 +33,9 @@ public class InputRequestProcess implements RequestProcess { case "api": nano.getListener().onApi(params.get("url").trim()); break; + case "cast": + nano.getListener().onCast(params.get("url").trim(), params.get("history").trim()); + break; } return Nano.createPlainTextResponse(NanoHTTPD.Response.Status.OK, "ok"); } diff --git a/app/src/main/java/com/fongmi/android/tv/utils/Utils.java b/app/src/main/java/com/fongmi/android/tv/utils/Utils.java index 92655b0cf..3a6c06a52 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/Utils.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/Utils.java @@ -144,10 +144,16 @@ public class Utils { } } - public static String getUUID() { + public static String getDeviceId() { return Settings.Secure.getString(App.get().getContentResolver(), Settings.Secure.ANDROID_ID); } + public static String getDeviceName() { + String model = Build.MODEL; + String manufacturer = Build.MANUFACTURER; + return model.startsWith(manufacturer) ? model : manufacturer + " " + model; + } + public static String getBase64(String ext) { return Base64.encodeToString(ext.getBytes(), Base64.DEFAULT | Base64.NO_WRAP); }