Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TV Support Scan Devices And Sync Keep and History #460

Open
wants to merge 14 commits into
base: release
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.fongmi.android.tv.ui.dialog.ProxyDialog;
import com.fongmi.android.tv.ui.dialog.RestoreDialog;
import com.fongmi.android.tv.ui.dialog.SiteDialog;
import com.fongmi.android.tv.ui.dialog.SyncDialog;
import com.fongmi.android.tv.utils.FileUtil;
import com.fongmi.android.tv.utils.Notify;
import com.fongmi.android.tv.utils.ResUtil;
Expand Down Expand Up @@ -128,6 +129,8 @@ protected void initEvent() {
mBinding.custom.setOnClickListener(this::onCustom);
mBinding.doh.setOnClickListener(this::setDoh);
mBinding.about.setOnClickListener(this::onAbout);
mBinding.syncKeep.setOnClickListener(this::onSyncKeep);
mBinding.syncHistory.setOnClickListener(this::onSyncHistory);
}

@Override
Expand Down Expand Up @@ -291,6 +294,14 @@ private void onAbout(View view) {
mBinding.aboutText.setText(BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_api + "-" + BuildConfig.FLAVOR_abi);
}

private void onSyncKeep(View view){
SyncDialog.create(this).keep().show();
}

private void onSyncHistory(View view){
SyncDialog.create(this).history().show();
}

private void setDoh(View view) {
DohDialog.create(this).index(getDohIndex()).show();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.fongmi.android.tv.ui.adapter;

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.fongmi.android.tv.R;
import com.fongmi.android.tv.bean.Device;
import com.fongmi.android.tv.databinding.AdapterDeviceBinding;

import java.util.ArrayList;
import java.util.List;

public class DeviceAdapter extends RecyclerView.Adapter<DeviceAdapter.ViewHolder> {

private final OnClickListener mListener;
private final List<Device> mItems;

public DeviceAdapter(OnClickListener listener) {
this.mItems = new ArrayList<>();
this.mListener = listener;
}

public interface OnClickListener {

void onItemClick(Device item);

boolean onLongClick(Device item);
}

public void addAll(List<Device> items) {
if (items == null) return;
mItems.removeAll(items);
mItems.addAll(items);
notifyDataSetChanged();
}

public void remove(Device item) {
if (item == null) return;
mItems.remove(item);
notifyDataSetChanged();
}

public void clear() {
mItems.clear();
Device.delete();
notifyDataSetChanged();
}

public List<String> getIps() {
List<String> ips = new ArrayList<>();
for (Device item : mItems) if (item.isApp()) ips.add(item.getIp());
return ips;
}

@Override
public int getItemCount() {
return mItems.size();
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(AdapterDeviceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Device item = mItems.get(position);
holder.binding.name.setText(item.getName());
holder.binding.host.setText(item.getHost());
holder.binding.type.setImageResource(getIcon(item));
holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(item));
holder.binding.getRoot().setOnLongClickListener(v -> mListener.onLongClick(item));
}

private int getIcon(Device item) {
return item.isMobile() ? R.drawable.ic_cast_tv : R.drawable.ic_cast_tv;
}

static class ViewHolder extends RecyclerView.ViewHolder {

private final AdapterDeviceBinding binding;

ViewHolder(@NonNull AdapterDeviceBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
20 changes: 20 additions & 0 deletions app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.fongmi.android.tv.ui.cast;

import org.greenrobot.eventbus.EventBus;

public class ScanEvent {

private final String address;

public static void post(String address) {
EventBus.getDefault().post(new ScanEvent(address));
}

public ScanEvent(String address) {
this.address = address;
}

public String getAddress() {
return address;
}
}
81 changes: 81 additions & 0 deletions app/src/leanback/java/com/fongmi/android/tv/ui/cast/ScanTask.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.fongmi.android.tv.ui.cast;

import com.fongmi.android.tv.App;
import com.fongmi.android.tv.bean.Device;
import com.fongmi.android.tv.server.Server;
import com.github.catvod.net.OkHttp;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import okhttp3.OkHttpClient;

public class ScanTask {

private final Listener listener;
private final OkHttpClient client;
private final List<Device> devices;

public static ScanTask create(Listener listener) {
return new ScanTask(listener);
}

public ScanTask(Listener listener) {
this.listener = listener;
this.client = OkHttp.client(1000);
this.devices = new ArrayList<>();
}

public void start(List<String> ips) {
App.execute(() -> run(getUrl(ips)));
}

public void start(String url) {
App.execute(() -> run(Arrays.asList(url)));
}

private void run(List<String> items) {
try {
getDevice(items);
} catch (Exception e) {
e.printStackTrace();
} finally {
App.post(() -> listener.onFind(devices));
}
}

private void getDevice(List<String> urls) throws Exception {
CountDownLatch cd = new CountDownLatch(urls.size());
for (String url : urls) new Thread(() -> findDevice(cd, url)).start();
cd.await();
}

private List<String> getUrl(List<String> ips) {
LinkedHashSet<String> urls = new LinkedHashSet<>(ips);
String local = Server.get().getAddress();
String base = local.substring(0, local.lastIndexOf(".") + 1);
for (int i = 1; i < 256; i++) urls.add(base + i + ":9978");
return new ArrayList<>(urls);
}

private void findDevice(CountDownLatch cd, String url) {
try {
if (url.contains(Server.get().getAddress())) return;
String result = OkHttp.newCall(client, url.concat("/device")).execute().body().string();
Device device = Device.objectFrom(result);
if (device == null) return;
devices.add(device.save());
} catch (Exception ignored) {
} finally {
cd.countDown();
}
}

public interface Listener {

void onFind(List<Device> devices);
}
}
166 changes: 166 additions & 0 deletions app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.fongmi.android.tv.ui.dialog;

import android.view.LayoutInflater;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;

import com.fongmi.android.tv.App;
import com.fongmi.android.tv.Constant;
import com.fongmi.android.tv.R;
import com.fongmi.android.tv.api.config.VodConfig;
import com.fongmi.android.tv.bean.Config;
import com.fongmi.android.tv.bean.Device;
import com.fongmi.android.tv.bean.History;
import com.fongmi.android.tv.bean.Keep;
import com.fongmi.android.tv.databinding.DialogDeviceBinding;
import com.fongmi.android.tv.impl.Callback;
import com.fongmi.android.tv.ui.adapter.DeviceAdapter;
import com.fongmi.android.tv.ui.cast.ScanEvent;
import com.fongmi.android.tv.ui.cast.ScanTask;
import com.fongmi.android.tv.utils.Notify;
import com.fongmi.android.tv.utils.ResUtil;
import com.github.catvod.net.OkHttp;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.IOException;
import java.util.List;

import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Response;

public class SyncDialog implements DeviceAdapter.OnClickListener, ScanTask.Listener {

private final FormBody.Builder body;
private final OkHttpClient client;
private DialogDeviceBinding binding;
private DeviceAdapter adapter;
private String type;

private final AlertDialog dialog;


public static SyncDialog create(FragmentActivity activity) {
return new SyncDialog(activity);
}

public SyncDialog(FragmentActivity activity) {
client = OkHttp.client(Constant.TIMEOUT_SYNC);
this.binding = DialogDeviceBinding.inflate(LayoutInflater.from(activity));
this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create();
body = new FormBody.Builder();
}

private void initDialog() {
WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
params.width = (int) (ResUtil.getScreenWidth() * 0.4f);
dialog.getWindow().setAttributes(params);
dialog.getWindow().setDimAmount(0);
dialog.show();
}

public SyncDialog history() {
type = "history";
body.add("device", Device.get().toString());
body.add("targets", App.gson().toJson(History.get()));
if (VodConfig.getUrl() != null) body.add("url", VodConfig.getUrl());
return this;
}

public SyncDialog keep() {
type = "keep";
body.add("device", Device.get().toString());
body.add("targets", App.gson().toJson(Keep.getVod()));
body.add("configs", App.gson().toJson(Config.findUrls()));
return this;
}

public void show() {
initDialog();
initView();
initEvent();
}


protected void initView() {
EventBus.getDefault().register(this);
setRecyclerView();
getDevice();
}

protected void initEvent() {
binding.refresh.setOnClickListener(v -> onRefresh());
}

private void setRecyclerView() {
binding.recycler.setHasFixedSize(true);
binding.recycler.setAdapter(adapter = new DeviceAdapter(this));
}

private void getDevice() {
adapter.addAll(Device.getAll());
if (adapter.getItemCount() == 0) App.post(this::onRefresh, 1000);
}

private void onRefresh() {
ScanTask.create(this).start(adapter.getIps());
adapter.clear();
}

private void onSuccess() {
dismiss();
}

private void onError() {
Notify.show(R.string.device_offline);
}

@Subscribe(threadMode = ThreadMode.MAIN)
public void onScanEvent(ScanEvent event) {
ScanTask.create(this).start(event.getAddress());
}

@Override
public void onFind(List<Device> devices) {
if (devices.size() > 0) adapter.addAll(devices);
}

@Override
public void onItemClick(Device item) {
OkHttp.newCall(client, item.getIp().concat("/action?do=sync&mode=0&type=").concat(type), body.build()).enqueue(getCallback());
}

@Override
public boolean onLongClick(Device item) {
OkHttp.newCall(client, item.getIp().concat("/action?do=sync&mode=1&type=").concat(type), body.build()).enqueue(getCallback());
return true;
}

private Callback getCallback() {
return new Callback() {
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
App.post(() -> onSuccess());
}

@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
App.post(() -> onError());
}
};
}
private void dismiss() {
try {
if (dialog != null) dialog.dismiss();
} catch (Exception ignored) {
}
}
}
Loading