111
private void setupAgreementText() {
setupAgreement1();
setupAgreement2();
}
private void setupAgreement1() {
String agreement1Text = getString(R.string.agreement1);
int termsStart = agreement1Text.indexOf("Terms of Use");
int termsEnd = termsStart + "Terms of Use".length();
SpannableString termsSpannable = SpannableStringInstance.getInstance().generateSpannableFontText(
this,
agreement1Text,
termsStart,
termsEnd,
false,
R.color.link_pressed_color,
R.color.white,
new SpannableStringInstance.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(AgreementActivity.this, "Terms of Use", Toast.LENGTH_SHORT).show();
}
}
);
int privacyStart = agreement1Text.indexOf("Privacy Policy");
int privacyEnd = privacyStart + "Privacy Policy".length();
SpannableStringBuilder agreement1Builder = new SpannableStringBuilder(agreement1Text);
agreement1Builder.setSpan(new SpannableStringInstance.TouchableSpan(
getResources().getColor(R.color.link_pressed_color),
getResources().getColor(R.color.white),
false
) {
@Override
public void onClick(View view) {
Toast.makeText(AgreementActivity.this, "Terms of Use", Toast.LENGTH_SHORT).show();
}
}, termsStart, termsEnd, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
agreement1Builder.setSpan(new SpannableStringInstance.TouchableSpan(
getResources().getColor(R.color.link_pressed_color),
getResources().getColor(R.color.white),
false
) {
@Override
public void onClick(View view) {
Toast.makeText(AgreementActivity.this, "Privacy Policy", Toast.LENGTH_SHORT).show();
}
}, privacyStart, privacyEnd, SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
viewBinding.agreement1.setText(agreement1Builder);
viewBinding.agreement1.setMovementMethod(SpannableStringInstance.getInstance().getLinkMovementMethod());
}
private void setupAgreement2() {
String agreement2Text = getString(R.string.agreement2);
int start = agreement2Text.indexOf("User Experience Improvement");
int end = start + "User Experience Improvement".length();
SpannableString agreement2Spannable = SpannableStringInstance.getInstance().generateSpannableFontText(
this,
agreement2Text,
start,
end,
false,
R.color.link_pressed_color,
R.color.white,
new SpannableStringInstance.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(AgreementActivity.this, "User Experience Improvement", Toast.LENGTH_SHORT).show();
}
}
);
viewBinding.agreement2.setText(agreement2Spannable);
viewBinding.agreement2.setMovementMethod(SpannableStringInstance.getInstance().getLinkMovementMethod());
}
三、系统相关者和需求
3.1 系统相关者
整个系统包括 Mercusys App/Tether App、路由器设备端、网络服务提供商(ISP),他们之间的通信关系如图所示:
3.2 系统相关者需求
3.2.1 双端有差异的产品需求
3.2.1.1 Hotspot备份功能支持差异
Tether App版本1.3支持Hotspot的backup和load balance功能,版本1.2不支持,Hotspot为单独模式;Mercusys App完全支持Hotspot功能,需要根据设备接口确定是否支持Hotspot备份
3.2.1.2 连接卡片交互展示差异
Tether App支持连接卡片的折叠/展开功能,记住用户操作偏好,默认全部折叠;Mercusys App注重信息密度展示,同时显示所有连接详情,需要根据界面设计规范确定交互方式
3.2.1.3 首次连接引导范围差异
Tether App版本1.2首次连接引导仅针对Ethernet和USB Internet;Mercusys App和Tether App版本1.3支持完整连接方式引导,需要根据设备支持的连接类型确定引导范围
3.2.1.4 Slider Control物理控制差异
Tether App支持Slider Control物理拨片控制Operation Mode切换,可选择开启或关闭该功能;Mercusys App可能不支持物理拨片控制,需要根据硬件接口确定是否支持物理控制
3.2.1.5 Load Balance权重配置差异
Tether App和Mercusys App在Traffic Allocation Weight的界面实现上存在细微差异,需要根据对应的权重配置接口来调整滑块交互和数值显示
3.2.1.6 连接类型检测支持差异
Tether App支持Auto Detect自动检测连接类型功能,部分Mercusys设备可能不支持,需要根据设备接口确定是否支持自动检测以及检测类型范围
3.2.1.7 USB设备类型识别差异
Tether App支持3/4G Modem和Phone/Tablet两种USB设备类型自动识别,Mercusys App的支持程度可能不同,需要根据USB接口能力确定设备识别范围
3.2.1.8 IPv6 LAN配置功能差异
Tether App支持完整的IPv6 LAN设置包括SLAAC+Stateless DHCP配置,Mercusys App的IPv6支持可能有差异,需要根据IPv6接口确定支持的配置项
3.2.2 技术需求
3.2.2.1 Internet Mode管理模块统一化
统一Backup模式和Load Balance模式的底层实现逻辑,包含连接状态管理、优先级调度、流量权重分配等核心算法,确保两个App的一致性
3.2.2.2 多连接方式适配层抽象
建立统一的连接方式适配层,封装Ethernet、USB Internet、Hotspot的底层差异,为上层应用提供一致的接口调用方式
3.2.2.3 设备能力动态检测机制
实现设备能力的动态检测和上报机制,让App能够根据设备实际支持的功能动态调整界面展示和功能可用性
3.2.2.4 降低App接入复杂度
App侧尽可能减少需要实现的底层网络配置接口,将复杂的网络管理逻辑下沉到设备端,降低App开发和维护成本
package com.tplink.deco.homepage.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ImageSpan
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tplink.deco.R
import com.tplink.deco.homepage.viewmodel.ClientViewModel
import com.tplink.design.list.TPTwoLineItemView
import com.tplink.design.list.TPTwoLineItemViewHolder
import com.tplink.design.menu.TPPopupMenu
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Date
import java.util.List
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
/**
* 设备列表适配器
* 职责:纯UI组件,负责显示设备列表和处理用户交互
* 不再直接操作数据,所有数据操作都委托给Fragment
* 移除了模拟数据生成,改为直接使用设备自带的网络数据
*/
public class ClientListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<ClientViewModel.DeviceInfo> deviceList = new ArrayList<>()
private Context context
private boolean isOnlineAdapter
// 设备操作监听接口
public interface OnDeviceActionListener {
void onBlockDevice(ClientViewModel.DeviceInfo device, int position)
void onDeleteDevice(ClientViewModel.DeviceInfo device, int position)
}
private OnDeviceActionListener listener
public ClientListAdapter(Context context, boolean isOnlineAdapter) {
this.context = context
this.isOnlineAdapter = isOnlineAdapter
System.out.println("ClientListAdapter创建,isOnline: " + isOnlineAdapter)
}
public void setOnDeviceActionListener(OnDeviceActionListener listener) {
this.listener = listener
}
/**
* 更新设备列表
* 这个方法会被Fragment调用,传入来自ViewModel的LiveData数据
*/
public void updateDeviceList(List<ClientViewModel.DeviceInfo> devices) {
System.out.println("ClientListAdapter.updateDeviceList调用,新设备数量: " +
(devices != null ? devices.size() : 0) + ", isOnline: " + isOnlineAdapter)
this.deviceList.clear()
if (devices != null) {
this.deviceList.addAll(devices)
}
notifyDataSetChanged()
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return TPTwoLineItemViewHolder.create(parent)
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (position >= deviceList.size()) return
TPTwoLineItemView lineItem = (TPTwoLineItemView) holder.itemView
ClientViewModel.DeviceInfo device = deviceList.get(position)
// 设置设备类型图标
if (device.getDeviceType() == 1) { // 电脑
lineItem.setStartIcon(R.mipmap.laptop3)
} else { // 手机
lineItem.setStartIcon(R.mipmap.game3)
}
// 设置图标尺寸
ViewGroup.LayoutParams layoutParams = lineItem.getStartIcon().getLayoutParams()
int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_40)
layoutParams.width = iconSize
layoutParams.height = iconSize
lineItem.getStartIcon().setLayoutParams(layoutParams)
lineItem.setTitleText(device.getDeviceName())
lineItem.showDivider(true)
// 根据设备状态设置内容和交互
if (device.isOnline()) {
setupOnlineDevice(lineItem, device, position)
} else {
setupOfflineDevice(lineItem, device, position)
}
}
/**
* 设置在线设备的显示和交互
* 使用设备自带的网络数据,不再生成模拟数据
*/
private void setupOnlineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
// 直接使用设备自带的网络数据
int signalStrength = device.getSignalStrength()
int downloadSpeed = device.getDownloadSpeed()
int uploadSpeed = device.getUploadSpeed()
System.out.println("设备 " + device.getDeviceName() + " 网络数据: " +
"信号=" + signalStrength + ", 下载=" + downloadSpeed + "KB/s, 上传=" + uploadSpeed + "KB/s")
lineItem.setEndIcon(createSignalIcon(signalStrength))
setOnlineContent(uploadSpeed, downloadSpeed, lineItem)
// 设置长按菜单 - 在线设备只能拉黑
lineItem.setOnLongClickListener(v -> {
TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_block)
.setIconEnable(true)
popupMenu.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.block) {
showBlockConfirmDialog(device, position)
return true
}
return false
})
popupMenu.show()
return true
})
}
/**
* 设置离线设备的显示和交互
* 使用设备自带的最后在线时间
*/
private void setupOfflineDevice(TPTwoLineItemView lineItem, ClientViewModel.DeviceInfo device, int position) {
lineItem.setEndIcon(R.drawable.next)
// 使用设备自带的最后在线时间
long lastOnlineTime = device.getLastOnlineTime()
setOfflineContent(lastOnlineTime, lineItem)
System.out.println("离线设备 " + device.getDeviceName() + " 最后在线: " + new Date(lastOnlineTime))
// 设置长按菜单 - 离线设备可以拉黑或删除
lineItem.setOnLongClickListener(v -> {
TPPopupMenu popupMenu = new TPPopupMenu(context, v, R.menu.menu_delete)
.setIconEnable(true)
popupMenu.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.block) {
showBlockConfirmDialog(device, position)
return true
} else if (item.getItemId() == R.id.delete) {
showDeleteConfirmDialog(device, position)
return true
}
return false
})
popupMenu.show()
return true
})
}
/**
* 显示拉黑确认对话框
*/
private void showBlockConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
new MaterialAlertDialogBuilder(context)
.setTitle("拉黑设备")
.setMessage("确定要将 \"" + device.getDeviceName() + "\" 加入黑名单吗?\n" +
"设备MAC地址: " + device.getMacAddress())
.setPositiveButton("确定", (dialog, which) -> {
if (listener != null) {
System.out.println("适配器:准备拉黑设备 " + device.getDeviceName())
listener.onBlockDevice(device, position)
}
})
.setNegativeButton("取消", null)
.show()
}
/**
* 显示删除确认对话框
*/
private void showDeleteConfirmDialog(ClientViewModel.DeviceInfo device, int position) {
new MaterialAlertDialogBuilder(context)
.setTitle("删除设备")
.setMessage("确定要永久删除 \"" + device.getDeviceName() + "\" 吗?\n" +
"删除后设备将从所有列表中移除。")
.setPositiveButton("确定", (dialog, which) -> {
if (listener != null) {
listener.onDeleteDevice(device, position)
}
})
.setNegativeButton("取消", null)
.show()
}
@Override
public int getItemCount() {
return deviceList.size()
}
/**
* 设置在线设备的内容显示(上传下载速度)
* 直接使用传入的速度参数,不再生成模拟数据
*/
private void setOnlineContent(int uploadRate, int downloadRate, TPTwoLineItemView lineItem) {
TextView textView = lineItem.getContent()
android.graphics.Paint.FontMetrics fm = textView.getPaint().getFontMetrics()
float textHeight = fm.descent - fm.ascent
String downloadText = " " + downloadRate + "KB/s"
String spaceText = " "
String uploadText = " " + uploadRate + "KB/s"
SpannableString spannable = new SpannableString(downloadText + spaceText + uploadText)
Drawable uploadIconDrawable = ContextCompat.getDrawable(context, R.drawable.upload)
Drawable downloadIconDrawable = ContextCompat.getDrawable(context, R.drawable.download)
if (uploadIconDrawable != null && downloadIconDrawable != null) {
float iconAspectRatio = (float) uploadIconDrawable.getIntrinsicWidth() / uploadIconDrawable.getIntrinsicHeight()
int scaleWidth = (int) (textHeight * iconAspectRatio)
downloadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight)
uploadIconDrawable.setBounds(0, 0, scaleWidth, (int) textHeight)
ImageSpan downloadIconSpan = new ImageSpan(downloadIconDrawable, ImageSpan.ALIGN_BOTTOM)
ImageSpan uploadIconSpan = new ImageSpan(uploadIconDrawable, ImageSpan.ALIGN_BOTTOM)
spannable.setSpan(downloadIconSpan, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
spannable.setSpan(uploadIconSpan,
downloadText.length() + spaceText.length(),
downloadText.length() + spaceText.length() + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.setText(spannable)
lineItem.getContent().setVisibility(View.VISIBLE)
}
/**
* 设置离线设备的内容显示(最后在线时间)
* 使用传入的时间戳,不再生成固定时间
*/
@SuppressLint("SimpleDateFormat")
private void setOfflineContent(long lastOnlineTime, TPTwoLineItemView lineItem) {
try {
SimpleDateFormat timeFormat = new SimpleDateFormat(context.getString(R.string.clients_time_format_pattern))
String timeString = timeFormat.format(new Date(lastOnlineTime))
String contentText = context.getString(R.string.clients_hint_current_online_time, timeString)
lineItem.setContentText(contentText)
} catch (Exception e) {
// 如果格式化失败,显示相对时间
long timeDiff = System.currentTimeMillis() - lastOnlineTime
String relativeTime
if (timeDiff < 60 * 60 * 1000) { // 小于1小时
relativeTime = "1小时前"
} else if (timeDiff < 24 * 60 * 60 * 1000) { // 小于24小时
long hours = timeDiff / (60 * 60 * 1000)
relativeTime = hours + "小时前"
} else { // 超过24小时
long days = timeDiff / (24 * 60 * 60 * 1000)
relativeTime = days + "天前"
}
lineItem.setContentText("上次在线: " + relativeTime)
}
}
/**
* 创建信号强度图标
* 使用传入的信号强度,不再使用数组索引
*/
private LayerDrawable createSignalIcon(int strength) {
Drawable signalIcon
switch (strength) {
case 1:
signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi1)
break
case 2:
signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi2)
break
case 3:
signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi3)
break
case 4:
default:
signalIcon = ContextCompat.getDrawable(context, R.drawable.wifi4)
break
}
Drawable nextIcon = ContextCompat.getDrawable(context, R.drawable.next)
if (signalIcon == null || nextIcon == null) {
return null
}
Drawable[] layers = {signalIcon, nextIcon}
LayerDrawable layerDrawable = new LayerDrawable(layers)
int iconSize = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20)
int spacing = (int) context.getResources().getDimension(com.tplink.design.R.dimen.tpds_all_dp_20)
layerDrawable.setLayerSize(0, iconSize, iconSize)
layerDrawable.setLayerGravity(0, Gravity.CENTER_HORIZONTAL)
layerDrawable.setLayerGravity(1, Gravity.CENTER_HORIZONTAL)
layerDrawable.setLayerInset(0, -spacing, 0, 0, 0)
layerDrawable.setLayerInset(1, 2 * spacing, 0, 0, 0)
return layerDrawable
}
}
package com.tplink.deco.homepage.viewmodel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import com.tplink.deco.homepage.repository.DeviceRepository;
import java.util.List;
public class ClientViewModel extends ViewModel {
private DeviceRepository repository;
public static class DeviceInfo {
private String deviceName;
private String macAddress;
private int deviceType;
private boolean isOnline;
private boolean isGuestNetwork;
private int signalStrength;
private int downloadSpeed;
private int uploadSpeed;
private long lastOnlineTime;
public DeviceInfo(String deviceName, String macAddress, int deviceType,
boolean isOnline, boolean isGuestNetwork,
int signalStrength, int downloadSpeed, int uploadSpeed, long lastOnlineTime) {
this.deviceName = deviceName;
this.macAddress = macAddress;
this.deviceType = deviceType;
this.isOnline = isOnline;
this.isGuestNetwork = isGuestNetwork;
this.signalStrength = signalStrength;
this.downloadSpeed = downloadSpeed;
this.uploadSpeed = uploadSpeed;
this.lastOnlineTime = lastOnlineTime;
}
public DeviceInfo(DeviceRepository.DeviceInfo repoDevice) {
this.deviceName = repoDevice.getDeviceName();
this.macAddress = repoDevice.getMacAddress();
this.deviceType = repoDevice.getDeviceType();
this.isOnline = repoDevice.isOnline();
this.isGuestNetwork = repoDevice.isGuestNetwork();
this.signalStrength = repoDevice.getSignalStrength();
this.downloadSpeed = repoDevice.getDownloadSpeed();
this.uploadSpeed = repoDevice.getUploadSpeed();
this.lastOnlineTime = repoDevice.getLastOnlineTime();
}
public String getDeviceName() { return deviceName; }
public String getMacAddress() { return macAddress; }
public int getDeviceType() { return deviceType; }
public boolean isOnline() { return isOnline; }
public boolean isGuestNetwork() { return isGuestNetwork; }
public int getSignalStrength() { return signalStrength; }
public int getDownloadSpeed() { return downloadSpeed; }
public int getUploadSpeed() { return uploadSpeed; }
public long getLastOnlineTime() { return lastOnlineTime; }
}
public static class BlacklistItem {
private String displayName;
private String macAddress;
private boolean isMacOnly;
public BlacklistItem(DeviceRepository.BlacklistItem repoItem) {
this.displayName = repoItem.getDisplayName();
this.macAddress = repoItem.getMacAddress();
this.isMacOnly = repoItem.isMacOnly();
}
public String getDisplayName() { return displayName; }
public String getMacAddress() { return macAddress; }
public boolean isMacOnly() { return isMacOnly; }
public String getIdentifier() { return isMacOnly ? macAddress : displayName; }
}
public ClientViewModel() {
repository = DeviceRepository.getInstance();
System.out.println("ClientViewModel创建,Repository实例: " + repository.hashCode());
}
public boolean blockDevice(String deviceName) {
System.out.println("ClientViewModel.blockDevice调用: " + deviceName);
return repository.blockDevice(deviceName);
}
public boolean blockDeviceByMac(String macAddress) {
return repository.blockDeviceByMac(macAddress);
}
public boolean unblockDevice(String identifier) {
return repository.unblockDevice(identifier);
}
public boolean deleteDevice(String deviceName) {
return repository.deleteDevice(deviceName);
}
public boolean deleteBlacklistDevice(String identifier) {
return repository.deleteBlacklistItem(identifier);
}
public LiveData<List<BlacklistItem>> getBlacklistItems() {
return Transformations.map(repository.getBlacklistItems(), repoItems -> {
List<BlacklistItem> viewModelItems = new java.util.ArrayList<>();
if (repoItems != null) {
for (DeviceRepository.BlacklistItem repoItem : repoItems) {
viewModelItems.add(new BlacklistItem(repoItem));
}
}
System.out.println("黑名单LiveData转换,数量: " + viewModelItems.size());
return viewModelItems;
});
}
public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
return Transformations.map(repository.getMainOnlineDevices(), repoDevices -> {
List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
if (repoDevices != null) {
for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
viewModelDevices.add(new DeviceInfo(repoDevice));
}
}
return viewModelDevices;
});
}
public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
return Transformations.map(repository.getMainOfflineDevices(), repoDevices -> {
List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
if (repoDevices != null) {
for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
viewModelDevices.add(new DeviceInfo(repoDevice));
}
}
return viewModelDevices;
});
}
public LiveData<List<DeviceInfo>> getGuestDevices() {
return Transformations.map(repository.getGuestDevices(), repoDevices -> {
List<DeviceInfo> viewModelDevices = new java.util.ArrayList<>();
if (repoDevices != null) {
for (DeviceRepository.DeviceInfo repoDevice : repoDevices) {
viewModelDevices.add(new DeviceInfo(repoDevice));
}
}
return viewModelDevices;
});
}
public LiveData<Boolean> getRefreshingState() {
return repository.getRefreshingState();
}
public LiveData<Integer> getMainOnlineCount() {
return Transformations.map(getMainOnlineDevices(), devices ->
devices != null ? devices.size() : 0);
}
public LiveData<Integer> getMainOfflineCount() {
return Transformations.map(getMainOfflineDevices(), devices ->
devices != null ? devices.size() : 0);
}
public LiveData<Integer> getGuestCount() {
return Transformations.map(getGuestDevices(), devices ->
devices != null ? devices.size() : 0);
}
public int getCurrentMainOnlineCount() {
return repository.getCurrentMainOnlineCount();
}
public int getCurrentMainOfflineCount() {
return repository.getCurrentMainOfflineCount();
}
public int getCurrentGuestCount() {
return repository.getCurrentGuestCount();
}
public int getCurrentBlacklistCount() {
return repository.getCurrentBlacklistCount();
}
public void refreshData() {
repository.refreshData();
}
@Deprecated
public void updateMainOnlineCount(int count) {
System.out.println("updateMainOnlineCount调用被忽略,由Repository自动管理");
}
@Deprecated
public void updateMainOfflineCount(int count) {
System.out.println("updateMainOfflineCount调用被忽略,由Repository自动管理");
}
@Deprecated
public void updateGuestCount(int count) {
System.out.println("updateGuestCount调用被忽略,由Repository自动管理");
}
public boolean blockMainDevice(String deviceName) {
return blockDevice(deviceName);
}
public boolean blockGuestDevice(String deviceName) {
return blockDevice(deviceName);
}
public boolean deleteGuestDevice(String deviceName) {
return deleteDevice(deviceName);
}
@Override
protected void onCleared() {
super.onCleared();
System.out.println("ClientViewModel被清理");
}
}
package com.tplink.deco.homepage.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import java.util.ArrayList
import java.util.List
import java.util.Iterator
import java.util.Random
/**
* 设备数据仓库 - 单例模式
* 负责管理所有设备数据,是唯一的数据源
* 确保Fragment和Activity之间的数据共享
*/
public class DeviceRepository {
private static volatile DeviceRepository INSTANCE
// 设备数据模型
public static class DeviceInfo {
private String deviceName
private String macAddress
private int deviceType
private boolean isOnline
private boolean isGuestNetwork
// 网络相关字段
private int signalStrength
private int downloadSpeed
private int uploadSpeed
private long lastOnlineTime
public DeviceInfo(String deviceName, String macAddress, int deviceType,
boolean isOnline, boolean isGuestNetwork,
int signalStrength, int downloadSpeed, int uploadSpeed, long lastOnlineTime) {
this.deviceName = deviceName
this.macAddress = macAddress
this.deviceType = deviceType
this.isOnline = isOnline
this.isGuestNetwork = isGuestNetwork
this.signalStrength = signalStrength
this.downloadSpeed = downloadSpeed
this.uploadSpeed = uploadSpeed
this.lastOnlineTime = lastOnlineTime
}
// Getters
public String getDeviceName() { return deviceName
public String getMacAddress() { return macAddress
public int getDeviceType() { return deviceType
public boolean isOnline() { return isOnline
public boolean isGuestNetwork() { return isGuestNetwork
public int getSignalStrength() { return signalStrength
public int getDownloadSpeed() { return downloadSpeed
public int getUploadSpeed() { return uploadSpeed
public long getLastOnlineTime() { return lastOnlineTime
// Setters
public void setOnline(boolean online) { isOnline = online
public void setDeviceType(int deviceType) { this.deviceType = deviceType
public void setSignalStrength(int signalStrength) { this.signalStrength = signalStrength
public void setDownloadSpeed(int downloadSpeed) { this.downloadSpeed = downloadSpeed
public void setUploadSpeed(int uploadSpeed) { this.uploadSpeed = uploadSpeed
@Override
public boolean equals(Object obj) {
if (this == obj) return true
if (obj == null || getClass() != obj.getClass()) return false
DeviceInfo that = (DeviceInfo) obj
return deviceName.equals(that.deviceName) && macAddress.equals(that.macAddress)
}
@Override
public int hashCode() {
return deviceName.hashCode() + macAddress.hashCode()
}
}
// 黑名单项目模型
public static class BlacklistItem {
private String displayName
private String macAddress
private boolean isMacOnly
public BlacklistItem(String deviceName, String macAddress) {
this.displayName = deviceName
this.macAddress = macAddress
this.isMacOnly = false
}
public BlacklistItem(String macAddress) {
this.displayName = macAddress
this.macAddress = macAddress
this.isMacOnly = true
}
public String getDisplayName() { return displayName
public String getMacAddress() { return macAddress
public boolean isMacOnly() { return isMacOnly
public String getIdentifier() { return isMacOnly ? macAddress : displayName
@Override
public boolean equals(Object obj) {
if (this == obj) return true
if (obj == null || getClass() != obj.getClass()) return false
BlacklistItem that = (BlacklistItem) obj
return macAddress.equals(that.macAddress)
}
}
// 数据存储
private List<DeviceInfo> allDevices = new ArrayList<>()
private List<BlacklistItem> blacklistItems = new ArrayList<>()
// LiveData - 供外部观察
private MutableLiveData<List<DeviceInfo>> allDevicesLiveData = new MutableLiveData<>()
private MutableLiveData<List<BlacklistItem>> blacklistLiveData = new MutableLiveData<>()
private MutableLiveData<Boolean> isRefreshing = new MutableLiveData<>(false)
// 随机数生成器用于模拟数据
private Random random = new Random()
private DeviceRepository() {
initializeData()
}
// 单例获取方法
public static DeviceRepository getInstance() {
if (INSTANCE == null) {
synchronized (DeviceRepository.class) {
if (INSTANCE == null) {
INSTANCE = new DeviceRepository()
}
}
}
return INSTANCE
}
// 初始化设备数据
private void initializeData() {
allDevices.clear()
blacklistItems.clear()
long currentTime = System.currentTimeMillis()
// 主网络在线设备 - 包含完整网络信息
allDevices.add(new DeviceInfo("Melanie's iPhone", generateMacAddress("iPhone"), 0, true, false,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
allDevices.add(new DeviceInfo("Melanie's iMac", generateMacAddress("iMac"), 1, true, false,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
allDevices.add(new DeviceInfo("Melanie's iPad", generateMacAddress("iPad"), 0, true, false,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
allDevices.add(new DeviceInfo("Office PC", generateMacAddress("PC"), 1, true, false,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
// 主网络离线设备 - 只有最后在线时间,网络数据为0
allDevices.add(new DeviceInfo("John's MacBook", generateMacAddress("MacBook"), 1, false, false,
0, 0, 0, currentTime - 24 * 60 * 60 * 1000))
allDevices.add(new DeviceInfo("Smart TV", generateMacAddress("TV"), 1, false, false,
0, 0, 0, currentTime - 48 * 60 * 60 * 1000))
allDevices.add(new DeviceInfo("Gaming Console", generateMacAddress("Console"), 1, false, false,
0, 0, 0, currentTime - 12 * 60 * 60 * 1000))
// 访客网络设备 - 包含网络信息
allDevices.add(new DeviceInfo("Guest Phone 1", generateMacAddress("GuestPhone1"), 0, true, true,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
allDevices.add(new DeviceInfo("Guest Laptop", generateMacAddress("GuestLaptop"), 1, true, true,
generateSignalStrength(), generateDownloadSpeed(), generateUploadSpeed(), currentTime))
// 更新LiveData
updateLiveData()
System.out.println("设备数据初始化完成,总设备数:" + allDevices.size())
}
// 数据生成辅助方法
private int generateSignalStrength() {
return random.nextInt(4) + 1
}
private int generateDownloadSpeed() {
return random.nextInt(80) + 20
}
private int generateUploadSpeed() {
return random.nextInt(40) + 10
}
// 生成MAC地址
private String generateMacAddress(String deviceIdentifier) {
int hash = Math.abs(deviceIdentifier.hashCode())
return String.format("%02X-%02X-%02X-%02X-%02X-%02X",
(hash >> 20) & 0xFF, (hash >> 16) & 0xFF,
(hash >> 12) & 0xFF, (hash >> 8) & 0xFF,
(hash >> 4) & 0xFF, hash & 0xFF)
}
// 更新LiveData
private void updateLiveData() {
allDevicesLiveData.setValue(new ArrayList<>(allDevices))
blacklistLiveData.setValue(new ArrayList<>(blacklistItems))
}
// ========== 核心功能方法 ==========
/**
* 拉黑设备 - 核心方法
* 从设备列表移除设备并添加到黑名单
*/
public boolean blockDevice(String deviceName) {
System.out.println("准备拉黑设备: " + deviceName)
// 查找要拉黑的设备
DeviceInfo deviceToBlock = null
for (DeviceInfo device : allDevices) {
if (device.getDeviceName().equals(deviceName)) {
deviceToBlock = device
break
}
}
if (deviceToBlock != null) {
// 检查是否已在黑名单中
boolean alreadyBlocked = false
for (BlacklistItem item : blacklistItems) {
if (item.getIdentifier().equals(deviceName)) {
alreadyBlocked = true
break
}
}
if (!alreadyBlocked) {
// 从设备列表移除
allDevices.remove(deviceToBlock)
// 添加到黑名单
BlacklistItem newBlacklistItem = new BlacklistItem(
deviceToBlock.getDeviceName(),
deviceToBlock.getMacAddress()
)
blacklistItems.add(newBlacklistItem)
// 更新LiveData,通知所有观察者
updateLiveData()
System.out.println("设备拉黑成功: " + deviceName +
", 剩余设备数: " + allDevices.size() +
", 黑名单数: " + blacklistItems.size())
return true
} else {
System.out.println("设备已在黑名单中: " + deviceName)
}
} else {
System.out.println("未找到设备: " + deviceName)
}
return false
}
/**
* 通过MAC地址拉黑
*/
public boolean blockDeviceByMac(String macAddress) {
// 检查MAC地址格式
if (!isValidMacAddress(macAddress)) {
return false
}
// 检查是否已存在
for (BlacklistItem item : blacklistItems) {
if (item.getMacAddress().equals(macAddress)) {
return false
}
}
// 添加到黑名单
blacklistItems.add(new BlacklistItem(macAddress))
updateLiveData()
System.out.println("MAC地址拉黑成功: " + macAddress)
return true
}
/**
* 解除拉黑
*/
public boolean unblockDevice(String identifier) {
BlacklistItem itemToRemove = null
for (BlacklistItem item : blacklistItems) {
if (item.getIdentifier().equals(identifier)) {
itemToRemove = item
break
}
}
if (itemToRemove != null) {
blacklistItems.remove(itemToRemove)
// 如果是设备名称(非纯MAC),重新添加到设备列表
if (!itemToRemove.isMacOnly()) {
long currentTime = System.currentTimeMillis()
DeviceInfo restoredDevice = new DeviceInfo(
itemToRemove.getDisplayName(),
itemToRemove.getMacAddress(),
0, // 默认手机类型
false, // 离线状态
false, // 主网络
0, 0, 0, currentTime - 24 * 60 * 60 * 1000 // 默认24小时前离线
)
allDevices.add(restoredDevice)
}
updateLiveData()
System.out.println("设备解除拉黑: " + identifier)
return true
}
return false
}
/**
* 删除设备(永久删除)
*/
public boolean deleteDevice(String deviceName) {
Iterator<DeviceInfo> iterator = allDevices.iterator()
boolean removed = false
while (iterator.hasNext()) {
DeviceInfo device = iterator.next()
if (device.getDeviceName().equals(deviceName)) {
iterator.remove()
removed = true
break
}
}
if (removed) {
updateLiveData()
System.out.println("设备删除成功: " + deviceName)
}
return removed
}
/**
* 删除黑名单项目
*/
public boolean deleteBlacklistItem(String identifier) {
Iterator<BlacklistItem> iterator = blacklistItems.iterator()
boolean removed = false
while (iterator.hasNext()) {
BlacklistItem item = iterator.next()
if (item.getIdentifier().equals(identifier)) {
iterator.remove()
removed = true
break
}
}
if (removed) {
updateLiveData()
System.out.println("黑名单项目删除: " + identifier)
}
return removed
}
// ========== LiveData 获取方法 ==========
public LiveData<List<BlacklistItem>> getBlacklistItems() {
return blacklistLiveData
}
public LiveData<List<DeviceInfo>> getAllDevices() {
return allDevicesLiveData
}
public LiveData<List<DeviceInfo>> getMainOnlineDevices() {
return Transformations.map(allDevicesLiveData, devices -> {
List<DeviceInfo> result = new ArrayList<>()
if (devices != null) {
for (DeviceInfo device : devices) {
if (!device.isGuestNetwork() && device.isOnline()) {
result.add(device)
}
}
}
return result
})
}
public LiveData<List<DeviceInfo>> getMainOfflineDevices() {
return Transformations.map(allDevicesLiveData, devices -> {
List<DeviceInfo> result = new ArrayList<>()
if (devices != null) {
for (DeviceInfo device : devices) {
if (!device.isGuestNetwork() && !device.isOnline()) {
result.add(device)
}
}
}
return result
})
}
public LiveData<List<DeviceInfo>> getGuestDevices() {
return Transformations.map(allDevicesLiveData, devices -> {
List<DeviceInfo> result = new ArrayList<>()
if (devices != null) {
for (DeviceInfo device : devices) {
if (device.isGuestNetwork()) {
result.add(device)
}
}
}
return result
})
}
public LiveData<Boolean> getRefreshingState() {
return isRefreshing
}
// ========== 辅助方法 ==========
/**
* 刷新数据
*/
public void refreshData() {
isRefreshing.setValue(true)
// 模拟网络请求
new android.os.Handler().postDelayed(() -> {
// 刷新时重新生成网络数据
for (DeviceInfo device : allDevices) {
if (device.isOnline()) {
device.setSignalStrength(generateSignalStrength())
device.setDownloadSpeed(generateDownloadSpeed())
device.setUploadSpeed(generateUploadSpeed())
}
}
updateLiveData()
isRefreshing.setValue(false)
}, 1500)
}
/**
* 验证MAC地址格式
*/
private boolean isValidMacAddress(String mac) {
return mac != null && mac.matches("^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$")
}
/**
* 获取当前统计数据
*/
public int getCurrentMainOnlineCount() {
int count = 0
for (DeviceInfo device : allDevices) {
if (!device.isGuestNetwork() && device.isOnline()) {
count++
}
}
return count
}
public int getCurrentMainOfflineCount() {
int count = 0
for (DeviceInfo device : allDevices) {
if (!device.isGuestNetwork() && !device.isOnline()) {
count++
}
}
return count
}
public int getCurrentGuestCount() {
int count = 0
for (DeviceInfo device : allDevices) {
if (device.isGuestNetwork()) {
count++
}
}
return count
}
public int getCurrentBlacklistCount() {
return blacklistItems.size()
}
}