优化天气,增加悬浮窗,增加快捷拨号,增加设置默认桌面

This commit is contained in:
2026-03-28 00:56:54 +08:00
parent 1977fd1cb2
commit b0ea6eff0a
74 changed files with 4055 additions and 242 deletions

View File

@@ -181,6 +181,8 @@ ext {
dependencies {
// implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly files('libs/framework.jar')
//Android 4.4+
implementation files('libs/QWeather_Public_Android_V5.1.2.jar')
implementation 'net.i2p.crypto:eddsa:0.3.0'
@@ -333,3 +335,29 @@ dependencies {
// implementation 'me.jessyan:autosize:1.2.1'
}
preBuild {
doLast {
def imlFile = file(project.name + ".iml")
// def imlFile = file("..\\.idea\\modules\\" + project.name + "\\" + rootProject.name + "." + project.name + ".iml")
println 'Change ' + project.name + '.iml order'
try {
def parsedXml = (new XmlParser()).parse(imlFile)
def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
parsedXml.component[1].remove(jdkNode)
def sdkString = "Android API " + android.compileSdkVersion.substring("android-".length()) + " Platform"
println 'what' + sdkString
new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
} catch (FileNotFoundException e) {
// nop, iml not found
println "no iml found"
}
}
//https://www.pianshen.com/article/93481144911/
//https://blog.csdn.net/dhl_1986/article/details/102856026
//https://blog.csdn.net/zhonghe1114/article/details/80923730
//https://blog.csdn.net/u014175785/article/details/116235760
//使用系统编译后的framework.jar
}

BIN
app/libs/framework.jar Normal file

Binary file not shown.

View File

@@ -6,9 +6,16 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
<!--baidumap start-->
<!-- 这个权限用于进行网络定位-->
@@ -43,11 +50,14 @@
android:name=".activity.main.MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:theme="@style/AppThemeFitsSystemWindows">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@@ -67,10 +77,28 @@
android:name=".activity.app.AppListActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity android:name=".activity.weather.main.WeatherMainActivity"
<activity
android:name=".activity.weather.main.WeatherMainActivity"
android:launchMode="singleTask"
android:theme="@style/AppThemeBlack"
android:screenOrientation="portrait" />
<activity
android:name=".activity.settings.home.SettingsActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".activity.settings.call.SettingsCallActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity
android:name=".activity.settings.utils.SettingsUtilsActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<service
android:name=".service.main.MainService"
android:enabled="true"
android:exported="false" />
<service
android:name=".service.DialerAccessibilityService"

View File

@@ -1,5 +1,7 @@
package com.ttstd.dialer.activity.app;
import android.view.View;
import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -84,6 +86,8 @@ public class AppListActivity extends BaseMvvmActivity<AppListViewModel, Activity
public class BtnClick {
public void exit(View view) {
finish();
}
}
}

View File

@@ -54,6 +54,10 @@ public class ContactAddActivity extends BaseMvvmActivity<ContactAddViewModel, Ac
}
public class BtnClick {
public void exit(View view) {
finish();
}
public void save(View view) {
String name = mViewDataBinding.etName.getText().toString();
String phone = mViewDataBinding.etPhone.getText().toString();

View File

@@ -1,5 +1,7 @@
package com.ttstd.dialer.activity.contact.edit;
import android.view.View;
import com.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivityContactEditBinding;
@@ -37,7 +39,9 @@ public class ContactEditActivity extends BaseMvvmActivity<ContactEditViewModel,
public class BtnClick{
public void exit(View view) {
finish();
}
}
}

View File

@@ -136,6 +136,10 @@ public class ContactListActivity extends BaseMvvmActivity<ContactListViewModel,
}
public class BtnClick {
public void exit(View view) {
finish();
}
public void addContact(View view) {
startActivity(new Intent(ContactListActivity.this, ContactAddActivity.class));
}

View File

@@ -21,6 +21,7 @@ import com.ttstd.dialer.fragment.app.AppFragment;
import com.ttstd.dialer.fragment.contact.ContactFragment;
import com.ttstd.dialer.fragment.home.HomeFragment;
import com.ttstd.dialer.fragment.settings.SettingsFragment;
import com.ttstd.dialer.service.main.MainService;
import com.ttstd.dialer.view.ApkPagerAdapter;
import com.ttstd.dialer.view.ScaleCircleNavigator;
@@ -150,6 +151,9 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
Log.e(TAG, "initView: mDefaultIndex = " + mDefaultIndex);
Log.e(TAG, "initView: mFragmentSize = " + mFragmentSize);
Intent intent = new Intent(this, MainService.class);
startService(intent);
}
@Override

View File

@@ -0,0 +1,52 @@
package com.ttstd.dialer.activity.settings.call;
import android.view.View;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.settings.home.SettingsViewModel;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivitySettingsBinding;
import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
public class SettingsCallActivity extends BaseMvvmActivity<SettingsCallViewModel, ActivitySettingsCallBinding> {
private static final String TAG = "SettingsActivity";
@Override
public boolean setfitWindow() {
return true;
}
@Override
public boolean setNightMode() {
return true;
}
@Override
protected int getLayoutId() {
return R.layout.activity_settings_call;
}
@Override
protected void initDataBinding() {
mViewModel.setContext(this);
mViewModel.setVDBinding(mViewDataBinding);
mViewModel.setLifecycle(getLifecycleSubject());
mViewDataBinding.setClick(new BtnClick());
}
@Override
protected void initView() {
}
@Override
protected void initData() {
}
public class BtnClick {
public void exit(View view) {
finish();
}
}
}

View File

@@ -0,0 +1,9 @@
package com.ttstd.dialer.activity.settings.call;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
public class SettingsCallViewModel extends BaseViewModel<ActivitySettingsCallBinding, ActivityEvent> {
}

View File

@@ -0,0 +1,61 @@
package com.ttstd.dialer.activity.settings.home;
import android.content.Intent;
import android.view.View;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.settings.call.SettingsCallActivity;
import com.ttstd.dialer.activity.settings.utils.SettingsUtilsActivity;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivitySettingsBinding;
public class SettingsActivity extends BaseMvvmActivity<SettingsViewModel, ActivitySettingsBinding> {
private static final String TAG = "SettingsActivity";
@Override
public boolean setfitWindow() {
return true;
}
@Override
public boolean setNightMode() {
return true;
}
@Override
protected int getLayoutId() {
return R.layout.activity_settings;
}
@Override
protected void initDataBinding() {
mViewModel.setContext(this);
mViewModel.setVDBinding(mViewDataBinding);
mViewModel.setLifecycle(getLifecycleSubject());
mViewDataBinding.setClick(new BtnClick());
}
@Override
protected void initView() {
}
@Override
protected void initData() {
}
public class BtnClick {
public void exit(View view) {
finish();
}
public void openCallSettings(View view) {
startActivity(new Intent(SettingsActivity.this, SettingsCallActivity.class));
}
public void openUtilsSettings(View view) {
startActivity(new Intent(SettingsActivity.this, SettingsUtilsActivity.class));
}
}
}

View File

@@ -0,0 +1,10 @@
package com.ttstd.dialer.activity.settings.home;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivitySettingsBinding;
import com.ttstd.dialer.databinding.ActivityTemplateBinding;
public class SettingsViewModel extends BaseViewModel<ActivitySettingsBinding, ActivityEvent> {
}

View File

@@ -0,0 +1,125 @@
package com.ttstd.dialer.activity.settings.utils;
import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Log;
import android.view.View;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.BuildConfig;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.main.MainActivity;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.databinding.ActivitySettingsUtilsBinding;
import com.ttstd.dialer.service.main.MainService;
import com.ttstd.dialer.utils.SystemUtils;
import com.ttstd.dialer.view.ToggleButton;
public class SettingsUtilsActivity extends BaseMvvmActivity<SettingsUtilsViewModel, ActivitySettingsUtilsBinding> {
private static final String TAG = "SettingsActivity";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
@Override
public boolean setfitWindow() {
return true;
}
@Override
public boolean setNightMode() {
return true;
}
@Override
protected int getLayoutId() {
return R.layout.activity_settings_utils;
}
@Override
protected void initDataBinding() {
mViewModel.setContext(this);
mViewModel.setVDBinding(mViewDataBinding);
mViewModel.setLifecycle(getLifecycleSubject());
mViewDataBinding.setClick(new BtnClick());
}
@Override
protected void initView() {
mViewDataBinding.siFloatWindow.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
@Override
public void onToggle(boolean on) {
Intent intent = new Intent(SettingsUtilsActivity.this, MainService.class);
if (on) {
intent.setAction(MainService.SHOW_FLOAT_WINDOW_ACTION);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 1);
} else {
intent.setAction(MainService.HIDE_FLOAT_WINDOW_ACTION);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
}
startService(intent);
}
});
mViewDataBinding.siFloatWindowKill.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
@Override
public void onToggle(boolean on) {
if (on) {
mMMKV.encode(CommonConfig.FLOAT_WINDOW_KILL_APP, 1);
} else {
mMMKV.encode(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
}
}
});
mViewDataBinding.siDefaultLauncher.setOnToggleChanged(new ToggleButton.OnToggleChanged() {
@Override
public void onToggle(boolean on) {
if (on) {
// SystemUtils.setDefaultLauncher(SettingsUtilsActivity.this, MainActivity.class);
// SystemUtils.setDefaultLauncher(SettingsUtilsActivity.this);
SystemUtils.addRoleHolderAsUser(SettingsUtilsActivity.this, BuildConfig.APPLICATION_ID);
} else {
SystemUtils.setOtherDefaultLauncher(SettingsUtilsActivity.this);
}
}
});
}
@Override
protected void initData() {
}
@Override
protected void onResume() {
super.onResume();
int enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0);
Log.e(TAG, "initView: enableFloatWindow = " + enableFloatWindow);
mViewDataBinding.siFloatWindow.setToggleStatu(enableFloatWindow == 1);
int floatWindowKillApp = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
Log.e(TAG, "initView: floatWindowKillApp = " + floatWindowKillApp);
mViewDataBinding.siFloatWindowKill.setToggleStatu(floatWindowKillApp == 1);
boolean defaultLauncher = SystemUtils.isDefaultLauncher(SettingsUtilsActivity.this, MainActivity.class);
Log.e(TAG, "initView: defaultLauncher = " + defaultLauncher);
mViewDataBinding.siDefaultLauncher.setToggleStatu(defaultLauncher);
}
public class BtnClick {
public void exit(View view) {
finish();
}
public void screenshotSnap(View view) {
// Bitmap bitmap = SystemUtils.takeFullScreenshot(SettingsUtilsActivity.this);
Bitmap bitmap = SystemUtils.takeScreenshotHighVersion();
if (bitmap != null) {
mViewDataBinding.ivSnap.setImageBitmap(bitmap);
}
}
}
}

View File

@@ -0,0 +1,10 @@
package com.ttstd.dialer.activity.settings.utils;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivitySettingsCallBinding;
import com.ttstd.dialer.databinding.ActivitySettingsUtilsBinding;
public class SettingsUtilsViewModel extends BaseViewModel<ActivitySettingsUtilsBinding, ActivityEvent> {
}

View File

@@ -0,0 +1,46 @@
package com.ttstd.dialer.activity.template;
import android.view.View;
import com.ttstd.dialer.R;
import com.ttstd.dialer.base.mvvm.BaseMvvmActivity;
import com.ttstd.dialer.databinding.ActivityTemplateBinding;
public class TemplateActivity extends BaseMvvmActivity<TemplateViewModel, ActivityTemplateBinding> {
private static final String TAG = "TemplateActivity";
@Override
public boolean setfitWindow() {
return true;
}
@Override
protected int getLayoutId() {
return R.layout.activity_template;
}
@Override
protected void initDataBinding() {
mViewModel.setContext(this);
mViewModel.setVDBinding(mViewDataBinding);
mViewModel.setLifecycle(getLifecycleSubject());
mViewDataBinding.setClick(new BtnClick());
}
@Override
protected void initView() {
}
@Override
protected void initData() {
}
public class BtnClick {
public void exit(View view) {
finish();
}
}
}

View File

@@ -0,0 +1,9 @@
package com.ttstd.dialer.activity.template;
import com.trello.rxlifecycle4.android.ActivityEvent;
import com.ttstd.dialer.base.mvvm.BaseViewModel;
import com.ttstd.dialer.databinding.ActivityTemplateBinding;
public class TemplateViewModel extends BaseViewModel<ActivityTemplateBinding, ActivityEvent> {
}

View File

@@ -35,6 +35,12 @@ public class WeatherMainActivity extends BaseMvvmActivity<WeatherMainViewModel,
// return true;
// }
@Override
public boolean setNightMode() {
return true;
}
@Override
protected int getLayoutId() {
return R.layout.activity_weather_main;

View File

@@ -44,8 +44,8 @@ public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdap
mContext = (FragmentActivity) parent.getContext();
requestBuilder = GlideApp.with(mContext)
.as(PictureDrawable.class)
.placeholder(R.drawable.image_loading)
.error(R.drawable.not_applicable)
// .placeholder(R.drawable.image_loading)
.error(R.drawable.ic_not_applicable)
.transition(withCrossFade())
.listener(new SvgSoftwareLayerSetter());
return new HourlyWeather(LayoutInflater.from(mContext).inflate(R.layout.item_hourly_weather, parent, false));
@@ -53,31 +53,34 @@ public class HourlyWeatherAdapter extends RecyclerView.Adapter<HourlyWeatherAdap
@Override
public void onBindViewHolder(@NonNull HourlyWeather holder, int position) {
WeatherHourly weatherHourly = mWeatherHourlyList.get(position);
String fxTime = weatherHourly.getFxTime();
holder.tv_time.setText(TimeUtils.formatToHourDescription(fxTime));
String icon = weatherHourly.getIcon();
// 获取资源ID
String fileName = "qweather_" + icon; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
if (resId != 0) {
// 使用Glide加载假设已配置SVG支持
Glide.with(mContext)
.as(PictureDrawable.class)
.load(resId) // 加载raw资源ID
.diskCacheStrategy(DiskCacheStrategy.NONE) // raw资源通常不缓存
.into(holder.iv_icon);
} else {
// 处理错误
Log.e("GlideLoad", "Raw resource not found: " + fileName);
}
if (mWeatherHourlyList != null && !mWeatherHourlyList.isEmpty()) {
WeatherHourly weatherHourly = mWeatherHourlyList.get(position);
String fxTime = weatherHourly.getFxTime();
holder.tv_time.setText(TimeUtils.formatToHourDescription(fxTime));
String icon = weatherHourly.getIcon();
// 获取资源ID
String fileName = "qweather_" + icon; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
if (resId != 0) {
// 使用Glide加载假设已配置SVG支持
// Glide.with(mContext)
// .as(PictureDrawable.class)
// .load(resId) // 加载raw资源ID
// .diskCacheStrategy(DiskCacheStrategy.NONE) // raw资源通常不缓存
// .into(holder.iv_icon);
requestBuilder.load(resId).into(holder.iv_icon);
} else {
// 处理错误
Log.e("GlideLoad", "Raw resource not found: " + fileName);
}
holder.tv_temp.setText(weatherHourly.getTemp() + "°");
holder.tv_temp.setText(weatherHourly.getTemp() + "°");
}
}
@Override
public int getItemCount() {
return mWeatherHourlyList == null ? 0 : mWeatherHourlyList.size();
return mWeatherHourlyList == null ? 27 : mWeatherHourlyList.size();
}
public class HourlyWeather extends RecyclerView.ViewHolder {

View File

@@ -1,5 +1,7 @@
package com.ttstd.dialer.adapter;
import android.graphics.drawable.PictureDrawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -9,15 +11,20 @@ import android.widget.TextView;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.RequestBuilder;
import com.qweather.sdk.response.weather.WeatherDaily;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.R;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.glide.GlideApp;
import com.ttstd.dialer.glide.svg.SvgSoftwareLayerSetter;
import com.ttstd.dialer.utils.DateUtil;
import java.util.List;
import java.util.Locale;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherHolder> {
private static final String TAG = "WeatherAdapter";
@@ -25,6 +32,8 @@ public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherH
private FragmentActivity mContext;
private List<WeatherDaily> mWeatherDailyList;
private RequestBuilder<PictureDrawable> requestBuilder;
public void setWeatherDailyList(List<WeatherDaily> weatherDailyList) {
mWeatherDailyList = weatherDailyList;
@@ -34,31 +43,54 @@ public class WeatherAdapter extends RecyclerView.Adapter<WeatherAdapter.WeatherH
@Override
public WeatherHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mContext = (FragmentActivity) parent.getContext();
requestBuilder = GlideApp.with(mContext)
.as(PictureDrawable.class)
// .placeholder(R.drawable.image_loading)
.error(R.drawable.ic_not_applicable)
.transition(withCrossFade())
.listener(new SvgSoftwareLayerSetter());
return new WeatherHolder(LayoutInflater.from(mContext).inflate(R.layout.item_weather, parent, false));
}
@Override
public void onBindViewHolder(WeatherHolder holder, int position) {
WeatherDaily weatherDaily = mWeatherDailyList.get(position);
String date = weatherDaily.getFxDate();
if (DateUtil.isTodayAll(date)) {
holder.tv_date.setText("今天");
} else {
String week = DateUtil.convertToChineseWeekdayAll(date);
holder.tv_date.setText(week);
if (mWeatherDailyList != null && !mWeatherDailyList.isEmpty()) {
WeatherDaily weatherDaily = mWeatherDailyList.get(position);
String date = weatherDaily.getFxDate();
if (DateUtil.isTodayAll(date)) {
holder.tv_date.setText("今天");
} else {
String week = DateUtil.convertToChineseWeekdayAll(date);
holder.tv_date.setText(week);
}
String tempMin = weatherDaily.getTempMin();
holder.tv_temp_min.setText(tempMin + "°");
String tempMax = weatherDaily.getTempMax();
holder.tv_temp_max.setText(tempMax + "°");
String iconDay = weatherDaily.getIconDay();
// 获取资源ID
String fileName = "qweather_" + iconDay; // 替换为你的文件名
int resId = mContext.getResources().getIdentifier(fileName, "raw", mContext.getPackageName());
if (resId != 0) {
// 使用Glide加载假设已配置SVG支持
// Glide.with(mContext)
// .as(PictureDrawable.class)
// .load(resId) // 加载raw资源ID
// .diskCacheStrategy(DiskCacheStrategy.NONE) // raw资源通常不缓存
// .into(holder.iv_icon);
requestBuilder.load(resId).into(holder.iv_icon);
} else {
// 处理错误
Log.e("GlideLoad", "Raw resource not found: " + fileName);
}
}
String tempMin = weatherDaily.getTempMin();
holder.tv_temp_min.setText(tempMin + "°");
String tempMax = weatherDaily.getTempMax();
holder.tv_temp_max.setText(tempMax + "°");
String iconDay = weatherDaily.getIconDay();
}
@Override
public int getItemCount() {
return mWeatherDailyList == null ? 0 : mWeatherDailyList.size();
return mWeatherDailyList == null ? 10 : mWeatherDailyList.size();
}
public class WeatherHolder extends RecyclerView.ViewHolder {

View File

@@ -5,6 +5,14 @@ public class CommonConfig {
public static final String CONTACT_HOME_PAGE = "contact_home_page_key";
public static final String FLOAT_WINDOW_X = "float_window_x_key";
public static final String FLOAT_WINDOW_Y = "float_window_y_key";
public static final String FLOAT_WINDOW_ENABLE = "float_window_enable_key";
public static final String FLOAT_WINDOW_KILL_APP = "float_window_kill_app_key";
public static final String WECHAT_AUTO_ACCEPT_CALL= "wechat_auto_accept_call_key";
public static final String WECHAT_AUTO_HNADS_FREE= "wechat_auto_hands_free_key";
public static final String MANUALLY_SELECT_LOCATION = "manually_select_location_key";
/*地址改变时发送*/

View File

@@ -38,6 +38,9 @@ public class AppFragment extends BaseMvvmFragment<AppViewModel, FragmentAppBindi
// }
public AppFragment() {
}
public AppFragment(List<AppInfo> appInfos) {
mAppInfos = appInfos;
Log.e(TAG, "AppFragment: mAppInfos = " + mAppInfos.hashCode());

View File

@@ -26,6 +26,8 @@ public class ContactFragment extends BaseMvvmFragment<ContactViewModel, Fragment
private static final int REQUEST_CODE_CALL = 7897;
public ContactFragment() {
}
@Override
protected int getLayoutId() {

View File

@@ -38,6 +38,9 @@ public class CallFragment extends BaseMvvmDialogFragment<CallViewModel, Fragment
private Activity mContext;
private ContactInfo mContactInfo;
public CallFragment() {
}
public CallFragment(ContactInfo contactInfo) {
mContactInfo = contactInfo;
}

View File

@@ -34,6 +34,9 @@ public class PermissionDialogFragment extends BaseDialogFragment {
private DialogFragmentPermissionsBinding mBinding;
private String mContent;
public PermissionDialogFragment() {
}
public PermissionDialogFragment(String content) {
this.mContent = content;
}

View File

@@ -35,6 +35,9 @@ public class ShortcutDialogFagment extends BaseMvvmDialogFragment<ShortcutViewMo
private String mNegativeText;
private String mPositiveText;
public ShortcutDialogFagment() {
}
public ShortcutDialogFagment(AppInfo appInfo) {
mAppInfo = appInfo;
}

View File

@@ -3,12 +3,12 @@ package com.ttstd.dialer.fragment.settings;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.app.AppListActivity;
import com.ttstd.dialer.activity.settings.home.SettingsActivity;
import com.ttstd.dialer.base.mvvm.fragment.BaseMvvmFragment;
import com.ttstd.dialer.databinding.FragmentSettingsBinding;
@@ -17,6 +17,9 @@ public class SettingsFragment extends BaseMvvmFragment<SettingsViewModel, Fragme
private Activity mContext;
public SettingsFragment() {
}
@Override
protected int getLayoutId() {
return R.layout.fragment_settings;
@@ -48,7 +51,7 @@ public class SettingsFragment extends BaseMvvmFragment<SettingsViewModel, Fragme
public class BtnClick {
public void openSettings(View view) {
Intent intent = new Intent(Settings.ACTION_SETTINGS);
Intent intent = new Intent(mContext, SettingsActivity.class);
try {
startActivity(intent);
} catch (Exception e) {

View File

@@ -0,0 +1,63 @@
package com.ttstd.dialer.mdm;
import android.content.Context;
import java.util.List;
public class SystemDeviceController implements IDeviceController {
private Context mContext;
public SystemDeviceController(Context context) {
mContext = context;
}
@Override
public void addDisallowedRunningApp(String pkg) {
}
@Override
public void removeDisallowedRunningApp(String pkg) {
}
@Override
public boolean isDisallowedRunningApp(String pkg) {
return false;
}
@Override
public void uninstallPackage(String pkg) {
}
@Override
public void shutdownDevice() {
}
@Override
public void rebootDevice() {
}
@Override
public void setDefaultLauncher(String pkg, String className) {
}
@Override
public boolean disableBluetoothTransfer(boolean disable) {
return false;
}
@Override
public void addInstallPackageTrustList(List<String> packages) {
}
@Override
public List<String> getInstallPackageTrustList() {
return null;
}
}

View File

@@ -1,45 +1,135 @@
package com.ttstd.dialer.service;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.GestureDescription;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import com.blankj.utilcode.util.ToastUtils;
import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.db.contact.ContactInfo;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.SystemUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.functions.Consumer;
/**
* 通过微信标签最高支持8.0.498.0.50 获取不到数据
* 8.0.54 可以获取
* 通过 {@link AccessibilityService#getWindows}和修改accessibility-service 配置能遍历屏幕元素
*/
public class DialerAccessibilityService extends AccessibilityService {
private static final String TAG = "AccessibilityService";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
public static final int ACTION_VIDEO = 1;
public static final int ACTION_AUDIO = 2;
private ContactInfo mContactInfo;
private static final int ACTION_IME_ENTER_VERSION = 30;
private static final int ACTION_IME_ENTER_ID = 16908372;
private static final String DIALER_TEXT = "音视频通话";
private static final String CONTACT_TEXT = "通讯录";
private static final String SEARCH_TEXT = "搜索";
private static final String TAG_TEXT = "标签";
private static final String MORE_NAME = "更多功能按钮,已折叠";
private static final String PARENT_VIDEO_TEXT = "视频通话";
private static final String VIDEO_TEXT = "视频通话";
private static final String CALL_TEXT = "语音通话";
private static final String RECEIVE_DESCRIPTION = "接听";
private static final String HANDS_FREE_TEXT = "扬声器已关";
private static final String DIALER_HANDS_FREE_TEXT = "免提";
private static final String DIALER_HANDS_FREE_CLOSE_TEXT = "免提,已关闭";
private static final int WAIT_TIME = 1600;
private Handler mHandler;
private Step mCurrentStep = Step.WAITING;
private String mName = "";//微信昵称
private String mTagName = "";//微信联系人标签名
private boolean mAutoAccept = false;
private boolean mAutoHandsFree = false;
private ContactInfo mContactInfo;
private int mCallType = ACTION_VIDEO;
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
public interface AccessibilityEventCallback {
void onAccessibilityEventCallback(AccessibilityEvent accessibilityEvent);
}
@Override
public void onInterrupt() {
}
private AccessibilityEventCallback mAccessibilityEventCallback;
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ");
registerSettingReceiver();
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
mAutoHandsFree = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_HNADS_FREE, false);
analysisAccessibilityEvent();
}
private void analysisAccessibilityEvent() {
Observable.create(new ObservableOnSubscribe<AccessibilityEvent>() {
@Override
public void subscribe(@NonNull ObservableEmitter<AccessibilityEvent> emitter) throws Throwable {
mAccessibilityEventCallback = emitter::onNext;
}
}).throttleLast(WAIT_TIME, TimeUnit.MILLISECONDS)
.subscribe(new Consumer<AccessibilityEvent>() {
@Override
public void accept(AccessibilityEvent accessibilityEvent) throws Throwable {
Log.e(TAG, "analysisAccessibilityEvent accept: ");
_onAccessibilityEvent(accessibilityEvent);
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ");
if (intent != null) {
mContactInfo = (ContactInfo) intent.getSerializableExtra("ContactInfo");
mCallType = intent.getIntExtra("callType", 1);
Log.e(TAG, "onStartCommand: contactInfo = " + mContactInfo);
mCallType = intent.getIntExtra("call_type", ACTION_VIDEO);
mName = mContactInfo.getName();
mCurrentStep = Step.CLICK_HOME;
if (mContactInfo != null) {
startWeixin();
}
@@ -50,6 +140,847 @@ public class DialerAccessibilityService extends AccessibilityService {
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
if (mSettingReceiver != null) {
unregisterReceiver(mSettingReceiver);
}
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.v(TAG, "onAccessibilityEvent: event = " + event.toString());
checkClassName(event);
mAccessibilityEventCallback.onAccessibilityEventCallback(event);
}
private void checkClassName(AccessibilityEvent event) {
Log.e(TAG, "checkClassName: mCurrentStep = " + mCurrentStep);
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
String currentPackageName = event.getPackageName().toString();
String currentClassName = event.getClassName().toString();
switch (mCurrentStep) {
case WAITING:
if (!TextUtils.isEmpty(currentPackageName) && "com.android.incallui".equals(currentPackageName)) {
Log.e(TAG, "checkClassName: to dialer hands free");
// mCurrentStep = Step.DIALER_HANDS_FREE;
}
break;
default:
if (!TextUtils.isEmpty(currentClassName)) {
switch (currentClassName) {
case "com.tencent.mm.ui.LauncherUI":
// if (mCurrentStep != Step.FIND_CONTACT) {
// mCurrentStep = Step.CLICK_CONTACT;
// }
break;
case "com.tencent.mm.plugin.account.ui.WelcomeActivity":
case "com.tencent.mm.plugin.account.ui.LoginPasswordUI":
Toaster.showLong("请先登录微信");
mCurrentStep = Step.WAITING;
break;
case "com.tencent.mm.plugin.label.ui.ContactLabelManagerUI":
break;
default:
}
}
}
}
}
/**
* 1.在微信页面直接找到联系人拨打电话
* 2.在联系人页面找到并拨打
* 3.通过进入联系人-标签找到并拨打
*
* @param event
*/
private void _onAccessibilityEvent(AccessibilityEvent event) {
Log.e(TAG, "_onAccessibilityEvent: " + mCurrentStep);
switch (mCurrentStep) {
case WAITING:
mAutoAccept = mMMKV.decodeBool(CommonConfig.WECHAT_AUTO_ACCEPT_CALL, false);
Log.e(TAG, "_onAccessibilityEvent: mAutoAccept = " + mAutoAccept);
if (mAutoAccept) {
autoAccept();
}
break;
case WECHAT_HANDS_FREE:
mAutoHandsFree = mMMKV.decodeInt(CommonConfig.WECHAT_AUTO_HNADS_FREE, 0) == 1;
Log.e(TAG, "_onAccessibilityEvent: mAutoHandsFree = " + mAutoHandsFree);
if (mAutoHandsFree) {
handsFree(Property.DESCRIPTION, HANDS_FREE_TEXT);
} else {
Log.e(TAG, "_onAccessibilityEvent: not enable auto handsfree");
}
case DIALER_HANDS_FREE:
if (findHandsFree(Property.DESCRIPTION, DIALER_HANDS_FREE_CLOSE_TEXT)) {
dialerHandsFree(Property.TEXT, DIALER_HANDS_FREE_TEXT);
} else {
mCurrentStep = Step.WAITING;
}
break;
case CLICK_HOME://主页能找到直接点击进去更多
if (stepHome(Property.TEXT, mName)) {
Log.e(TAG, "_onAccessibilityEvent: not found contact in home");
} else {
clickViewById("com.tencent.mm:id/jha", Step.CLICK_SEARCH);
// step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
}
break;
case CLICK_SEARCH:
putString(mName, Step.CLICK_SEARCH_CONTACT);
break;
case CLICK_SEARCH_CONTACT:
if (findSearchContact(Step.FIND_CONTACT)) {
findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
} else {
Toaster.show("没有找到联系人");
}
break;
case CLICK_QUICK_WECHAT_CALL://点击更多页面
clickViewById("com.tencent.mm:id/bjz", Step.CLICK_TARGET);
// step(Property.DESCRIPTION, MORE_NAME, Step.CLICK_TARGET);
break;
case CLICK_TARGET://点击视频通话
stepCall(Property.TEXT, PARENT_VIDEO_TEXT);
// clickVideoCall();
break;
case CLICK_CALL://打视频或者电话
if (mCallType == ACTION_AUDIO) {
step(Property.TEXT, VIDEO_TEXT, Step.WAITING);
} else if (mCallType == ACTION_VIDEO) {
step(Property.TEXT, CALL_TEXT, Step.WAITING);
}
break;
case CLICK_CONTACT://进入通讯录界面
if (stepHome(Property.TEXT, CONTACT_TEXT, Step.FIND_TAG)) {
Log.e(TAG, "_onAccessibilityEvent: enter contact");
} else {
touchContact();
}
break;
case FIND_CONTACT://模拟滑动找到联系人
findSearchContact(Property.TEXT, mName, Step.CLICK_QUICK_WECHAT_CALL);
break;
case FIND_TAG:
step(Property.TEXT, TAG_TEXT, Step.CLICK_TAG);
break;
case CLICK_TAG:
if (!step(Property.TEXT, mTagName, Step.CLICK_NAME)) {
Toaster.show("没有找到标签");
mCurrentStep = Step.WAITING;
}
break;
case CLICK_NAME://点击item
findContact(Property.TEXT, mName, Step.CLICK_INFO);
break;
case CLICK_INFO://进入个人信息页面
stepCallDialog(Property.TEXT, DIALER_TEXT, Step.CLICK_CALL);
break;
// case CLICK_VIDEO_CALL:
// if (step(Property.TEXT, VIDEO_TEXT)) {
// Log.d(TAG, "finish, now: " + mCurrentStep);
// ToastUtils.show("成功发起视频聊天");
// }
// break;
default:
}
}
@Override
public void onInterrupt() {
Log.e(TAG, "onInterrupt: ");
}
@Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.e(TAG, "onServiceConnected: ");
}
/**
* 打开这个界面可以直接选择联系人拨打
*/
private void openVoip() {
Intent intent = new Intent();
ComponentName component = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.contact.VoipAddressUI");
intent.setComponent(component);
intent.putExtra("voip_video", false);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
private void autoAccept() {
if (stepAnswer(Property.DESCRIPTION, RECEIVE_DESCRIPTION)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
ToastUtils.showShort("已自动接听视频/语音");
} else if (clickNode("com.tencent.mm:id/kfp", false)) {
mCurrentStep = Step.WECHAT_HANDS_FREE;
ToastUtils.showShort("已自动接听视频/语音");
} else {
mCurrentStep = Step.WAITING;
// clickAnswer();
}
}
/**
* @param text 对应文本
* @param simulate 是否通过坐标模拟点击
* @return
*/
private boolean clickNode(String text, boolean simulate) {
findFloatWindowNode(text);
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(text);
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
AccessibilityNodeInfo node = optional.get();
if (node.isClickable()) {
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.e(TAG, "clickNode: performAction = " + performAction);
node.recycle();
return performAction;
} else {
if (simulate) {
Point point = getPointtByNode(node);
Log.e(TAG, "clickNode: " + point);
clickByPoint(point.x, point.y);
Log.e(TAG, "clickNode: mCurrentStep " + mCurrentStep + " done");
} else {
clickNode(findClickableNode(node));
}
}
return true;
} else {
Log.e(TAG, "clickNode: not found");
return false;
}
}
private AccessibilityNodeInfo findClickableNode(AccessibilityNodeInfo node) {
if (node == null) {
Log.e(TAG, "findClickableNode: node is null");
return null;
}
if (node.isClickable()) {
return node;
} else {
return findClickableNode(node);
}
}
public void findFloatWindowNode(String id) {
List<AccessibilityWindowInfo> windows = getWindows();
for (AccessibilityWindowInfo window : windows) {
// 筛选悬浮窗窗口
if (isFloatingWindow(window)) {
AccessibilityNodeInfo rootNode = window.getRoot();
// 处理悬浮窗节点
traverseNode(rootNode);
rootNode.recycle(); // 释放资源
}
}
}
private boolean isFloatingWindow(AccessibilityWindowInfo window) {
Log.e(TAG, "isFloatingWindow: " + window.getType());
// 根据窗口类型或标题筛选
return window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY;
}
private void traverseNode(AccessibilityNodeInfo node) {
if (node == null) return;
// 提取节点信息如文本、ID等
String text = node.getText() != null ? node.getText().toString() : "";
String id = node.getViewIdResourceName();
// 递归遍历子节点
for (int i = 0; i < node.getChildCount(); i++) {
traverseNode(node.getChild(i));
}
}
@Deprecated
private void clickAnswer() {
String className = SystemUtils.getForegroundActivityClassName(DialerAccessibilityService.this);
Log.e(TAG, "clickAnswer: " + className);
if (!TextUtils.isEmpty(className)) {
if ("com.tencent.mm.plugin.voip.ui.VideoActivity".contentEquals(className)) {
boolean successful = clickByPoint(595, 1376);
Log.e(TAG, "clickAnswer: " + successful);
if (successful) {
ToastUtils.showShort("已自动接听视频/语音");
}
} else {
Log.e(TAG, "clickAnswer: Not in the answering interface");
}
}
}
private boolean step(Property type, String text, Step nextStep) {
AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
if (node != null) {
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Log.e(TAG, "step: rect = " + rect);
if (rect.left < 0 || rect.top < 0 || rect.right < 0 || rect.bottom < 0) {
return false;
}
clickNode(node);
Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = nextStep;
Log.e(TAG, "step: next: " + mCurrentStep);
return true;
} else {
return false;
}
}
// TODO: 2025/2/8 先把通讯录点击的换成node
private boolean stepHome(Property type, String text, Step nextStep) {
AccessibilityNodeInfo node = findNode(getWindows(), type, text);
if (node != null) {
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Log.e(TAG, "step: rect = " + rect);
if (rect.left < 0 || rect.top < 0 || rect.right < 0 || rect.bottom < 0) {
return false;
}
clickNode(node);
Log.e(TAG, "step: mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = nextStep;
Log.e(TAG, "step: next: " + mCurrentStep);
return true;
} else {
return false;
}
}
@Deprecated
private void touchContact() {
boolean successful = clickByPoint(268, 1440);
if (successful) {
mCurrentStep = Step.FIND_TAG;
} else {
mCurrentStep = Step.WAITING;
Toaster.show("点击失败,请重试");
}
}
private boolean findSearchContact(Step nextStep) {
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId("com.tencent.mm:id/gzf");
Log.e(TAG, "findSearchContact: " + nodeInfos);
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
return optional.isPresent();
}
private void findSearchContact(Property type, String text, Step nextStep) {
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId("com.tencent.mm:id/odf");
Log.e(TAG, "findSearchContact: " + nodeInfos);
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
AccessibilityNodeInfo nodeInfo = optional.get();
clickNode(nodeInfo);
mCurrentStep = nextStep;
} else {
Toaster.show("没有找到联系人");
mCurrentStep = Step.WAITING;
}
}
private void putString(String text, Step nextStep) {
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId("com.tencent.mm:id/d98");
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
AccessibilityNodeInfo nodeInfo = optional.get();
Bundle args = new Bundle();
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS); // 确保焦点在输入框
if (Build.VERSION.SDK_INT >= ACTION_IME_ENTER_VERSION) {
//see https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo.AccessibilityAction#ACTION_IME_ENTER
nodeInfo.performAction(ACTION_IME_ENTER_ID);
}
mCurrentStep = nextStep;
} else {
Toaster.show("没有找到搜索框");
mCurrentStep = Step.WAITING;
}
}
private void clickViewById(String id, Step nextStep) {
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId(id);
Optional<AccessibilityNodeInfo> optional = nodeInfos.stream().findAny();
if (optional.isPresent()) {
AccessibilityNodeInfo nodeInfo = optional.get();
clickNode(nodeInfo);
mCurrentStep = nextStep;
} else {
// Toaster.show("没有找到搜索按钮");
step(Property.DESCRIPTION, SEARCH_TEXT, Step.CLICK_SEARCH);
}
}
private List<AccessibilityNodeInfo> findNodesByViewId(String id) {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> accessibilityNodeInfos = nodeInfo.findAccessibilityNodeInfosByViewId(id);
return accessibilityNodeInfos;
} else {
return new ArrayList<>();
}
}
private AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) {
if (root == null) return null;
Log.e(TAG, "findNodeByText: getText = " + root.getText());
Log.e(TAG, "findNodeByText: getContentDescription = " + root.getContentDescription());
boolean found = root.getText() != null && text.contentEquals(root.getText());
if (found) {
return root;
} else {
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo result = findNodeByText(root.getChild(i), text);
if (result != null) {
return result;
}
}
}
root.recycle();
return null;
}
private boolean stepCallDialog(Property type, String text, Step nextStep) {
AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
if (node != null) {
Log.e(TAG, "stepCallDialog: isVisibleToUser: " + node.isVisibleToUser());
if (node.isVisibleToUser()) {
clickNode(node);
Log.e(TAG, "stepCallDialog: mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = nextStep;
Log.e(TAG, "stepCallDialog: next: " + mCurrentStep);
return true;
} else {
scrollDown();
return false;
}
} else {
if (mFindCount == mMaxCount) {
Log.e("stepCallDialog", "mCurrentStep: max");
ToastUtils.showShort("没有找到联系人");
mCurrentStep = Step.WAITING;
mFindCount = 0;
return false;
} else {
Log.e("stepCallDialog", "mCurrentStep: not found");
mFindCount++;
Log.e("stepCallDialog", "mCurrentStep: mFindCount = " + mFindCount);
scrollDown();
return false;
}
}
}
private boolean stepHome(Property type, String text) {
AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
if (node != null) {
clickNode(node);
Log.e(TAG, "stepHome: mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = Step.CLICK_QUICK_WECHAT_CALL;
Log.e(TAG, "stepHome: next: " + mCurrentStep);
return true;
} else {
mCurrentStep = Step.CLICK_SEARCH;
return false;
}
}
private int mFindCount = 0;
private int mMaxCount = 5;
private boolean findContact(Property type, String text, Step nextStep) {
AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
if (node != null) {
clickNode(node);
Log.e("findContact", "mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = nextStep;
Log.e("findContact", "next: " + mCurrentStep);
mFindCount = 0;
return true;
} else {
if (mFindCount == mMaxCount) {
Log.e("findContact", "mCurrentStep: max");
ToastUtils.showShort("没有找到联系人");
mCurrentStep = Step.WAITING;
mFindCount = 0;
return false;
} else {
Log.e("findContact", "mCurrentStep: not found");
mFindCount++;
Log.e("findContact", "mCurrentStep: mFindCount = " + mFindCount);
scrollDown();
return false;
}
}
}
private AccessibilityNodeInfo findNode(AccessibilityNodeInfo root, Property type, String text) {
if (root == null) return null;
// Log.v(TAG, "findNode: getPackageName = " + root.getPackageName());
Log.v(TAG, "findNode: getText = " + root.getText());
Log.v(TAG, "findNode: getClassName = " + root.getClassName());
Log.v(TAG, "findNode: getContentDescription = " + root.getContentDescription());
boolean satisfied = false;
switch (type) {
case TEXT:
satisfied = root.getText() != null && text.contentEquals(root.getText());
break;
case CLASS_NAME:
satisfied = root.getClassName() != null && text.contentEquals(root.getClassName());
break;
case DESCRIPTION:
satisfied = root.getContentDescription() != null && text.contentEquals(root.getContentDescription());
break;
default:
}
if (satisfied) {
return root;
} else {
for (int i = 0; i < root.getChildCount(); i++) {
AccessibilityNodeInfo result = findNode(root.getChild(i), type, text);
if (result != null) {
return result;
}
}
}
root.recycle();
return null;
}
private AccessibilityNodeInfo findNode(List<AccessibilityWindowInfo> windows, Property type, String text) {
for (AccessibilityWindowInfo accessibilityWindowInfo : windows) {
AccessibilityNodeInfo nodeInfo = findNode(accessibilityWindowInfo.getRoot(), type, text);
if (nodeInfo != null) {
return nodeInfo;
}
}
Log.e(TAG, "findNode windows: not found");
return null;
}
private void clickNode(AccessibilityNodeInfo node) {
if (node == null) {
Log.e(TAG, "clickNode: node is null");
return;
}
try {
Log.e(TAG, "clickNode: getText = " + node.getText());
Log.e(TAG, "clickNode: isClickable = " + node.isClickable());
} catch (Exception e) {
Log.e(TAG, "clickNode: e = " + e.getMessage());
}
if (node.isClickable()) {
//防检测机制:
//添加随机延迟(避免高频操作)
// handler.postDelayed(new Runnable() {
// @Override
// public void run() {
//
// }
// }, 1000 + new Random().nextInt(100));
boolean performAction = node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.e(TAG, "clickNode: performAction = " + performAction);
if (!performAction) {
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Log.e(TAG, "clickNode: rect = " + rect);
// 点击节点的中心位置
int centerX = (rect.left + rect.right) / 2;
int centerY = (rect.top + rect.bottom) / 2;
Log.e(TAG, "clickNode: clickByNode = " + clickByPoint(centerX, centerY));
}
node.recycle();
} else {
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Log.e(TAG, "clickNode: rect = " + rect);
// 点击节点的中心位置
int centerX = (rect.left + rect.right) / 2;
int centerY = (rect.top + rect.bottom) / 2;
Log.e(TAG, "clickNode: clickByNode = " + clickByPoint(centerX, centerY));
}
// else {
// AccessibilityNodeInfo parent = node.getParent();
// node.recycle();
// clickNode(parent);
// }
}
@Deprecated
private boolean stepCall(Property type, String text) {
AccessibilityNodeInfo node = findNode(getRootInActiveWindow(), type, text);
if (node != null) {
Point point = getPointtByNode(node);
Log.e(TAG, "stepCall: " + point);
clickByPoint(point.x, point.y);
// clickNode(node);
Log.e(TAG, "stepCall: mCurrentStep " + mCurrentStep + " done");
mCurrentStep = Step.CLICK_CALL;
Log.e(TAG, "stepCall: next " + mCurrentStep);
return true;
} else {
Log.e(TAG, "stepCall: not found");
return false;
}
}
private void clickVideoCall() {
List<AccessibilityNodeInfo> nodeInfos = findNodesByViewId("com.tencent.mm:id/a12");
Optional<AccessibilityNodeInfo> accessibilityNodeInfo = nodeInfos.stream().findAny();
if (accessibilityNodeInfo.isPresent()) {
AccessibilityNodeInfo nodeInfo = accessibilityNodeInfo.get();
clickNode(nodeInfo);
mCurrentStep = Step.CLICK_CALL;
} else {
Toaster.show("没有找到通话按钮");
}
}
private boolean stepAnswer(Property type, String text) {
AccessibilityNodeInfo node = findNode(getWindows(), type, text);
if (node != null) {
Point point = getPointtByNode(node);
Log.e(TAG, "stepAnswer: " + point);
clickByPoint(point.x, point.y - 50);
clickByPoint(point.x, point.y);
// clickNode(node);
Log.e(TAG, "stepAnswer: mCurrentStep " + mCurrentStep + " done");
mCurrentStep = Step.WAITING;
Log.e(TAG, "stepAnswer: next " + mCurrentStep);
return true;
} else {
Log.e(TAG, "stepAnswer: not found");
return false;
}
}
private boolean dialerHandsFree(Property type, String text) {
AccessibilityNodeInfo node = findNode(getWindows(), type, text);
if (node != null) {
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Log.e(TAG, "dialerHandsFree: rect = " + rect);
clickNode(node);
Log.e(TAG, "dialerHandsFree: mCurrentStep: " + mCurrentStep + " done");
mCurrentStep = Step.WAITING;
Log.e(TAG, "dialerHandsFree: next: " + mCurrentStep);
return true;
} else {
return false;
}
}
private boolean findHandsFree(Property type, String text) {
AccessibilityNodeInfo node = findNode(getWindows(), type, text);
if (node != null) {
Log.e(TAG, "findHandsFree: true");
return true;
} else {
Log.e(TAG, "findHandsFree: false");
return false;
}
}
private boolean handsFree(Property type, String text) {
AccessibilityNodeInfo node = findNode(getWindows(), type, text);
if (node != null) {
Point point = getPointtByNode(node);
Log.e(TAG, "handsFree: " + point);
clickByPoint(point.x, point.y - 50);
clickByPoint(point.x, point.y);
// clickNode(node);
Log.e(TAG, "handsFree: mCurrentStep " + mCurrentStep + " done");
mCurrentStep = Step.WAITING;
Log.e(TAG, "handsFree: next " + mCurrentStep);
return true;
} else {
Log.e(TAG, "handsFree: not found");
mCurrentStep = Step.WAITING;
return false;
}
}
//根据节点信息可获得对应的xy坐标
static Point getPointtByNode(AccessibilityNodeInfo node) {
if (node == null) {
return new Point(0, 0);
}
Rect rect = new Rect();
node.getBoundsInScreen(rect);
Point point = new Point(rect.centerX(), rect.centerY());
return point;
}
//实现对xy坐标进行点击操作。
private boolean clickByPoint(int x, int y) {
Log.e(TAG, "clickByNode: x = " + x);
Log.e(TAG, "clickByNode: y = " + y);
Point point = new Point(x, y);
Path path = new Path();
path.moveTo(point.x, point.y);
GestureDescription.Builder builder = new GestureDescription.Builder();
//防检测机制:
//添加随机延迟(避免高频操作)
builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200 + new Random().nextInt(100)));
GestureDescription gesture = builder.build();
boolean dispatched = dispatchGesture(gesture, new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
Log.e("clickByNode", "onCompleted: ");
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
Log.e("clickByNode", "onCompleted: ");
}
}, null);
return dispatched;
}
private boolean scrollScreen(double startY, double endY) {
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getRealMetrics(dm);
int width = dm.widthPixels; // 屏幕宽度(像素)
int height = dm.heightPixels; // 屏幕高度(像素)
float density = dm.density; // 屏幕密度0.75 / 1.0 / 1.5
int densityDpi = dm.densityDpi; // 屏幕密度dpi120 / 160 / 240
// 屏幕宽度算法:屏幕宽度(像素)/屏幕密度
// int screenWidth = (int) (width / density); // 屏幕宽度(dp)
// int screenHeight = (int) (height / density);// 屏幕高度(dp)
Log.e(TAG, "scrollScreen: screenWidth = " + width);
Log.e(TAG, "scrollScreen: screenHeight = " + height);
int center_X = width / 2;
int center_Y = height / 2;
Log.e("scrollScreen", "center position:" + "(" + center_X + "," + center_Y + ")");
Path path = new Path();
path.moveTo(center_X, (int) (center_Y * startY)); //起点坐标。
path.lineTo(center_X, (int) (center_Y * endY)); //终点坐标。
GestureDescription.Builder builder = new GestureDescription.Builder();
GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 0, 200)).build();
boolean dispatched = dispatchGesture(gestureDescription, new GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
Log.d("scrollScreen", "dispatchGesture ScrollUp onCompleted.");
path.close();
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
Log.d("scrollScreen", "dispatchGesture ScrollUp cancel.");
}
}, null);
return dispatched;
}
private boolean scrollDown() {
return scrollScreen(1.5, 0.5);
}
private boolean scrollUp() {
return scrollScreen(0.5, 1.5);
}
private enum Step {
WAITING,
//微信免提
WECHAT_HANDS_FREE,
//电话免提
DIALER_HANDS_FREE,
//1-1微信主页找用户名
CLICK_HOME,
//2-2进入搜索界面
CLICK_SEARCH,
//2-3是否弹出了联系人列表
CLICK_SEARCH_CONTACT,
//1-2 2-4聊天界面+号
CLICK_QUICK_WECHAT_CALL,
//1-3 2-5更多里面视频通话
CLICK_TARGET,
/*1-4 语音通话*/
CLICK_CALL,
//主页点击导航栏通讯录
CLICK_CONTACT,
FIND_CONTACT,
//通讯录页面点击标签
FIND_TAG,
//点击对应的标签名
CLICK_TAG,
CLICK_NAME,
CLICK_INFO,
CLICK_VIDEO_CALL;
private Step next() {
return values()[(this.ordinal() + 1) % values().length];
}
}
private enum Property {
TEXT,
CLASS_NAME,
DESCRIPTION
}
public static final String SETTING_CALL_TYPE_ACTION = "setting_call_type_action";
public static final String SETTING_AUTOMATIC_ANSWER_ACTION = "setting_automatic_answer_action";
private SettingReceiver mSettingReceiver;
private void registerSettingReceiver() {
if (mSettingReceiver == null) {
mSettingReceiver = new SettingReceiver();
}
IntentFilter filter = new IntentFilter();
filter.addAction(SETTING_CALL_TYPE_ACTION);
filter.addAction(SETTING_AUTOMATIC_ANSWER_ACTION);
registerReceiver(mSettingReceiver, filter);
}
private class SettingReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.e("SettingReceiver", "onReceive: " + action);
if (TextUtils.isEmpty(action)) return;
switch (action) {
case SETTING_CALL_TYPE_ACTION:
int callType = intent.getIntExtra("call_type", ACTION_VIDEO);
mCallType = callType;
Log.e("SettingReceiver", "onReceive: callType = " + callType);
break;
case SETTING_AUTOMATIC_ANSWER_ACTION:
boolean autoAnswer = intent.getBooleanExtra("auto_answer", false);
mAutoAccept = autoAnswer;
Log.e("SettingReceiver", "onReceive: autoAnswer = " + autoAnswer);
break;
default:
}
}
}
private void startWeixin() {
@@ -64,7 +995,7 @@ public class DialerAccessibilityService extends AccessibilityService {
try {
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "launchWeChat: " + e.getMessage());
Log.e(TAG, "startWeixin: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,258 @@
package com.ttstd.dialer.service.main;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import com.hjq.toast.Toaster;
import com.shehuan.niv.NiceImageView;
import com.tencent.mmkv.MMKV;
import com.ttstd.dialer.BuildConfig;
import com.ttstd.dialer.R;
import com.ttstd.dialer.activity.main.MainActivity;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.utils.ApkUtils;
import com.ttstd.dialer.utils.SystemUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MainService extends Service {
private static final String TAG = "MainService";
public static final String SHOW_FLOAT_WINDOW_ACTION = "show_float_window";
public static final String HIDE_FLOAT_WINDOW_ACTION = "hide_float_window";
private MMKV mMMKV = MMKV.mmkvWithID(CommonConfig.MMKV_ID, MMKV.MULTI_PROCESS_MODE);
private WindowManager mWindowManager;
private View floatingView;
private WindowManager.LayoutParams mLayoutParams;
private boolean mFloatWindowShowing = false;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ");
mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
boolean enableFloatWindow = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_ENABLE, 0) == 1;
Log.e(TAG, "onCreate: enableFloatWindow = " + enableFloatWindow);
if (enableFloatWindow) {
showFloatWindow();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
Log.e(TAG, "onStartCommand: action = " + action);
if (!TextUtils.isEmpty(action)) {
switch (action) {
case SHOW_FLOAT_WINDOW_ACTION:
showFloatWindow();
break;
case HIDE_FLOAT_WINDOW_ACTION:
hideFloatWindow();
break;
}
}
}
return START_STICKY;
}
private void showFloatWindow() {
Log.e(TAG, "showFloatWindow: ");
initFloatingView();
if (Settings.canDrawOverlays(this)) {
// 4. 将 View 添加到 WindowManager
try {
mWindowManager.addView(floatingView, mLayoutParams);
mFloatWindowShowing = true;
} catch (Exception e) {
Log.e(TAG, "onCreate: addView = " + e.getMessage());
}
}
}
private void initFloatingView() {
Log.e(TAG, "initFloatingView: ");
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating_window, null);
int layoutType;
// Android 8.0 (API 26) 及以上需要使用 TYPE_APPLICATION_OVERLAY
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
layoutType = WindowManager.LayoutParams.TYPE_PHONE;
}
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
layoutType,
// FLAG_NOT_FOCUSABLE 确保悬浮窗不会拦截原本属于背后应用/桌面的按键事件
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
// 初始化位置
mLayoutParams.gravity = Gravity.TOP | Gravity.START;
mLayoutParams.x = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_X, 0);
mLayoutParams.y = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_Y, 0);
NiceImageView nvBack = floatingView.findViewById(R.id.nv_back);
nvBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: ");
// stopSelf(); // 停止服务,触发 onDestroy 移除悬浮窗
// mWindowManager.removeView(floatingView);
// floatingView = null;
// mFloatWindowShowing = false;
String foreground = SystemUtils.getForegroundActivityPackageName(MainService.this);
if (BuildConfig.APPLICATION_ID.equals(foreground)) {
int floatWindowKillApp = mMMKV.decodeInt(CommonConfig.FLOAT_WINDOW_KILL_APP, 0);
if (floatWindowKillApp == 1) {
killApp();
} else {
}
} else {
Intent intent = new Intent(MainService.this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
});
nvBack.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "onTouch: ");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录初始位置
initialX = mLayoutParams.x;
initialY = mLayoutParams.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
// 计算偏移量并更新位置
mLayoutParams.x = initialX + (int) (event.getRawX() - initialTouchX);
mLayoutParams.y = initialY + (int) (event.getRawY() - initialTouchY);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_X, mLayoutParams.x);
mMMKV.encode(CommonConfig.FLOAT_WINDOW_Y, mLayoutParams.y);
mWindowManager.updateViewLayout(floatingView, mLayoutParams);
break;
case MotionEvent.ACTION_UP:
break;
default:
}
return false;
}
});
}
private void hideFloatWindow() {
Log.e(TAG, "hideFloatWindow: ");
if (mFloatWindowShowing) {
if (floatingView != null) {
mWindowManager.removeView(floatingView);
floatingView = null;
}
mFloatWindowShowing = false;
}
}
private static final Set<String> mWhiteRunningAppSets = new HashSet<String>() {{
this.add(BuildConfig.APPLICATION_ID);
this.add("com.tencent.mm");
this.add("com.ss.android.ugc.aweme");
this.add("com.tencent.wetype");
this.add("com.tencent.qqpinyin");
this.add("com.google.android.inputmethod.pinyin");
this.add("com.sohu.inputmethod.sogou");
this.add("com.iflytek.inputmethod");
this.add("com.baidu.input");
}};
private void killApp() {
Log.e(TAG, "killApp: ");
List<String> runningPackages = SystemUtils.getRunningTaskPackages(MainService.this);
int i = 0;
for (String pkg : runningPackages) {
if (mWhiteRunningAppSets.contains(pkg)) {
continue;
}
if (ApkUtils.isSystemApp(MainService.this, pkg)) {
continue;
}
i++;
SystemUtils.killBackgroundProcesses(MainService.this, pkg);
}
Toaster.show(String.format(getString(R.string.kill_app_number), i));
}
@Override
public void onDestroy() {
super.onDestroy();
// 务必在服务销毁时移除 View防止内存泄漏或系统崩溃
if (floatingView != null) {
mWindowManager.removeView(floatingView);
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
}
}

View File

@@ -0,0 +1,4 @@
package com.ttstd.dialer.utils;
public class BitmapUtils {
}

View File

@@ -2,14 +2,47 @@ package com.ttstd.dialer.utils;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityTaskManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.os.Process;
import android.view.SurfaceControl;
import android.view.WindowManager;
import com.ttstd.dialer.BuildConfig;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
public class SystemUtils {
private static final String TAG = "SystemUtils";
/**
* 获取设备序列号
@@ -72,4 +105,364 @@ public class SystemUtils {
}
public static String getForegroundActivityPackageName(Context context) {
ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(1);
if (runningTaskInfos != null && !runningTaskInfos.isEmpty()) {
ComponentName componentName = runningTaskInfos.get(0).topActivity;
if (componentName != null) {
String currentPackageName = componentName.getPackageName();
return currentPackageName;
}
}
return "";
}
/**
* 获取栈顶的应用包名
*/
public static String getForegroundActivityClassName(Context context) {
ActivityManager manager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
String currentClassName = manager.getRunningTasks(1).get(0).topActivity.getClassName();
return currentClassName;
}
public static List<String> getRunningTaskPackages(Context context) {
ActivityManager activityManager = (ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> runningTaskInfos = activityManager.getRunningTasks(Integer.MAX_VALUE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return runningTaskInfos.stream().map(new Function<ActivityManager.RunningTaskInfo, String>() {
@Override
public String apply(ActivityManager.RunningTaskInfo runningTaskInfo) {
return runningTaskInfo.topActivity.getPackageName();
}
}).collect(Collectors.toList());
} else {
List<String> packageNames = new ArrayList<>();
for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {
packageNames.add(runningTaskInfo.topActivity.getPackageName());
}
return packageNames;
}
}
public static void killBackgroundProcesses(Context context, String processName) {
Log.e(TAG, "killBackgroundProcesses: " + processName);
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
String packageName;
try {
if (!processName.contains(":")) {
packageName = processName;
} else {
packageName = processName.split(":")[0];
}
activityManager.killBackgroundProcesses(packageName);
activityManager.forceStopPackage(packageName);
// removeTask(context, processName);
// Method forceStopPackage = activityManager.getClass()
// .getDeclaredMethod("forceStopPackage", String.class);
// forceStopPackage.setAccessible(true);
// forceStopPackage.invoke(activityManager, packageName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void removeTask(Context context, String packageName) {
Log.e(TAG, "removeTask: " + packageName);
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
List<ActivityManager.RecentTaskInfo> list = getRecentTasks(ActivityManager.getMaxRecentTasksStatic(), getCurrentUserId());
HashMap<String, Integer> taskMap = new HashMap<>();
for (ActivityManager.RecentTaskInfo info : list) {
taskMap.put(info.realActivity.getPackageName(), info.id);
}
try {
ActivityManagerNative.getDefault().removeTask(taskMap.get(packageName));
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "removeTask: " + e.getMessage());
} catch (NullPointerException e) {
Log.e(TAG, "removeTask: " + e.getMessage());
}
// } else {
// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//
// }
}
/**
* @return a list of the recents tasks.
* 获取近期任务列表
*/
public static List<ActivityManager.RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
try {
return ActivityTaskManager.getService().getRecentTasks(numTasks,
RECENT_IGNORE_UNAVAILABLE, userId).getList();
} catch (RemoteException e) {
Log.e(TAG, "Failed to get recent tasks " + e);
return new ArrayList<>();
}
}
/**
* @return the current user's id.
* 获取userId
*/
public static int getCurrentUserId() {
UserInfo ui;
try {
ui = ActivityManager.getService().getCurrentUser();
return ui != null ? ui.id : 0;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
public static boolean isDefaultLauncher(Context context, Class<?> launcherActivityClass) {
ComponentName componentName = new ComponentName(context, launcherActivityClass);
Log.e(TAG, "isDefaultLauncher: componentName = " + componentName);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
if (resolveInfo != null && resolveInfo.activityInfo != null) {
ComponentName defaultComponentName = resolveInfo.getComponentInfo().getComponentName();
Log.e(TAG, "isDefaultLauncher: defaultComponentName = " + defaultComponentName);
return componentName.equals(defaultComponentName);
}
return false;
}
/**
* 将指定的 Activity 设置为系统默认桌面(需要系统签名)
*
* @param context 上下文
* @param launcherActivityClass 你的桌面 Activity 类,例如 MyLauncherActivity.class
*/
public static void setDefaultLauncher(Context context, Class<?> launcherActivityClass) {
PackageManager pm = context.getPackageManager();
// 1. 创建桌面过滤的 IntentFilter
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
// 2. 查询当前系统中所有的桌面应用(用来构建 ComponentName 数组)
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
int bestMatch = 0;
ComponentName[] set = new ComponentName[resolveInfos.size()];
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo info = resolveInfos.get(i);
set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
if (info.match > bestMatch) {
bestMatch = info.match; // 获取最匹配的值
}
}
// 3. 构建你自己的桌面 ComponentName
ComponentName myLauncher = new ComponentName(context, launcherActivityClass);
// 4. 替换首选 Activity核心步骤
try {
// 注意API 29 (Android 10) 中此方法对第三方应用废弃,但对系统签名应用依然有效
pm.replacePreferredActivity(filter, bestMatch, set, myLauncher);
Log.i("LauncherHelper", "成功设置为默认桌面");
// 可选:发送一个回到桌面的 Intent 来验证
// Intent startHome = new Intent(Intent.ACTION_MAIN);
// startHome.addCategory(Intent.CATEGORY_HOME);
// startHome.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// context.startActivity(startHome);
} catch (SecurityException e) {
Log.e("LauncherHelper", "缺少权限或未生效,请检查系统签名是否正确", e);
} catch (Exception e) {
Log.e("LauncherHelper", "设置默认桌面失败", e);
}
}
public static void setDefaultLauncher(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
RoleManager roleManager = (RoleManager) context.getSystemService(Context.ROLE_SERVICE);
// 检查当前应用是否已经是桌面角色持有者
if (roleManager != null && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
// 检查该角色是否可用
if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)) {
// 创建请求意图
Intent roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME);
// 注意:在普通应用中,这会弹出系统选择框
// 在系统签名应用中,这通常能直接提升优先级或简化流程
context.startActivity(roleRequestIntent);
}
}
} else {
// Android 10 以下的传统做法:清除当前默认并弹出选择
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
/**
* 将指定包名设置为默认桌面 (需要系统签名)
*/
public static void addRoleHolderAsUser(Context context, String packageName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Log.e(TAG, "RoleManager requires Android 10 or higher.");
return;
}
RoleManager roleManager = context.getSystemService(RoleManager.class);
if (roleManager == null) return;
String roleName = RoleManager.ROLE_HOME;
// 1. 检查是否已经是当前角色持有者,避免重复调用
if (roleManager.isRoleHeld(roleName)) {
// 注意:这里最好再判断一下持有的包名是否为目标包名
Log.i(TAG, "Role " + roleName + " is already held by some package.");
// 如果已经是自己,直接返回
}
try {
UserHandle user = Process.myUserHandle();
Executor executor = context.getMainExecutor();
// 定义回调
Consumer<Boolean> callback = successful -> {
if (successful) {
Log.i(TAG, "Successfully set " + packageName + " as " + roleName);
} else {
Log.e(TAG, "Failed to set " + packageName + " as " + roleName + ". Check system logs.");
}
};
// 2. 使用反射调用隐藏方法 addRoleHolderAsUser
// 方法签名: addRoleHolderAsUser(String, String, int, UserHandle, Executor, Consumer<Boolean>)
Method method = RoleManager.class.getMethod("addRoleHolderAsUser",
String.class, String.class, int.class, UserHandle.class, Executor.class, Consumer.class);
Log.d(TAG, "Invoking addRoleHolderAsUser for package: " + packageName);
method.invoke(roleManager, roleName, packageName, 0, user, executor, callback);
// roleManager.addRoleHolderAsUser(roleName, packageName, 0, user, executor, callback);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Method not found. Is this a non-standard ROM?", e);
} catch (Exception e) {
Log.e(TAG, "Error invoking addRoleHolderAsUser", e);
}
}
public static void setOtherDefaultLauncher(Context context) {
PackageManager pm = context.getPackageManager();
IntentFilter filter = new IntentFilter(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(homeIntent, PackageManager.MATCH_DEFAULT_ONLY);
Optional<ResolveInfo> systemLauncher = resolveInfos.stream().filter(new Predicate<ResolveInfo>() {
@Override
public boolean test(ResolveInfo resolveInfo) {
return !BuildConfig.APPLICATION_ID.equals(resolveInfo.activityInfo.packageName);
}
}).filter(new Predicate<ResolveInfo>() {
@Override
public boolean test(ResolveInfo resolveInfo) {
return ApkUtils.isSystemApp(context, resolveInfo.activityInfo.packageName);
}
}).findAny();
systemLauncher.ifPresent(resolveInfo -> addRoleHolderAsUser(context, resolveInfo.activityInfo.packageName));
}
/**
* 系统签名应用专用静默获取全屏截图Bitmap
*
* @param context 上下文
* @return 全屏截图Bitmap失败返回null
*/
public static Bitmap takeFullScreenshot(Context context) {
// 获取屏幕真实宽高(包含状态栏、导航栏)
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
wm.getDefaultDisplay().getRealMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
Log.e(TAG, "takeFullScreenshot: screenWidth " + screenWidth);
Log.e(TAG, "takeFullScreenshot: screenHeight " + screenHeight);
try {
// 注意:不同 Android 版本的 SurfaceControl.screenshot 方法签名可能不同
// 以下代码适用于 Android 9.0 及以上版本常见的隐藏 API 调用方式
// 1. 获取屏幕显示的 IBinder (通常为 DisplayControl 或 SurfaceControl 的内部方法)
// 在较高版本中,可能需要通过 SurfaceControl.getInternalDisplayToken() 获取
// 2. 反射调用 screenshot 方法
Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
// Android 10+ 建议使用新的反射路径,这里以通用逻辑为例:
// 对于 Android 11+Google 引入了 SurfaceControl.LayerCaptureArgs 等内部类
// 这是一个针对 Android 9/10 的简化逻辑参考:
Bitmap bitmap = (Bitmap) surfaceControlClass.getDeclaredMethod("screenshot",
Rect.class, Integer.TYPE, Integer.TYPE, Integer.TYPE)
.invoke(null, new Rect(), screenWidth, screenHeight, 0);
return bitmap;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Bitmap takeScreenshotHighVersion() {
try {
// 1. 获取主屏幕的 Token (Internal Display)
// 反射调用 SurfaceControl.getInternalDisplayToken()
Method getInternalDisplayTokenMethod = SurfaceControl.class.getDeclaredMethod("getInternalDisplayToken");
getInternalDisplayTokenMethod.setAccessible(true);
IBinder displayToken = (IBinder) getInternalDisplayTokenMethod.invoke(null);
if (displayToken == null) return null;
// 2. 构造 DisplayCaptureArgs.Builder (Android 11+ 的新包装类)
Class<?> builderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");
Constructor<?> builderConstructor = builderClass.getConstructor(IBinder.class);
Object builder = builderConstructor.newInstance(displayToken);
// 可以通过 Builder 设置缩放、格式等,这里直接 build()
Method buildMethod = builderClass.getDeclaredMethod("build");
Object captureArgs = buildMethod.invoke(builder);
// 3. 调用 SurfaceControl.screenshot(DisplayCaptureArgs)
// 返回值是一个 ScreenshotHardwareBuffer 对象
Class<?> captureArgsClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs");
Method screenshotMethod = SurfaceControl.class.getDeclaredMethod("screenshot", captureArgsClass);
screenshotMethod.setAccessible(true);
Object screenshotBuffer = screenshotMethod.invoke(null, captureArgs);
if (screenshotBuffer == null) return null;
// 4. 从 ScreenshotHardwareBuffer 中提取 Bitmap
Method asBitmapMethod = screenshotBuffer.getClass().getDeclaredMethod("asBitmap");
asBitmapMethod.setAccessible(true);
return (Bitmap) asBitmapMethod.invoke(screenshotBuffer);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,230 @@
package com.ttstd.dialer.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.ttstd.dialer.R;
import org.jetbrains.annotations.NotNull;
public class SettingItem extends ConstraintLayout {
private OnClickListener mRootOnClickListener;
private ToggleButton.OnToggleChanged mOnToggleChanged;
private static final String DefaultOptionsText = "设置选项";
private static final String DefaultEnableText = "开启描述";
private static final String DefaultDisableText = "关闭描述";
private String mOptionsText = "";
private String mEnableText = "";
private String mDisableText = "";
private int mOptionsTextColor = 0xFF000000;
private int mHintTextColor = 0xFF9D9D9D;
// private boolean mRootClick = false;
private boolean mShowHintText = true;
private boolean mShowToggle = true;
private boolean mShowMore = false;
private boolean mLinkage = true;
private boolean mShowDivider = true;
private ConstraintLayout cl_root;
private TextView tv_options, tv_hint;
private ToggleButton tb;
private ImageView iv_more;
private View dividerLine;
public void setRootOnClickListener(OnClickListener rootOnClickListener) {
mRootOnClickListener = rootOnClickListener;
}
public void setOnToggleChanged(ToggleButton.OnToggleChanged onToggleChanged) {
mOnToggleChanged = onToggleChanged;
tb.setOnToggleChanged(mOnToggleChanged);
}
public void setToggleStatu(boolean on) {
tb.setToggleStatu(on);
if (on) {
if (!TextUtils.isEmpty(mEnableText)) {
tv_hint.setText(mEnableText);
} else {
tv_hint.setText(DefaultEnableText);
}
} else {
if (!TextUtils.isEmpty(mDisableText)) {
tv_hint.setText(mDisableText);
} else {
tv_hint.setText(DefaultDisableText);
}
}
requestLayout();
}
public SettingItem(@NonNull @NotNull Context context) {
super(context);
init(context, null);
}
public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
public SettingItem(@NonNull @NotNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.layout_setting_content_item, this, true);
cl_root = findViewById(R.id.cl_root);
tv_options = findViewById(R.id.tv_options);
tv_hint = findViewById(R.id.tv_hint);
tb = findViewById(R.id.tb);
iv_more = findViewById(R.id.iv_more);
dividerLine = findViewById(R.id.divider);
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SettingItem);
try {
mOptionsText = typedArray.getString(R.styleable.SettingItem_optionsText);
if (!TextUtils.isEmpty(mOptionsText)) {
tv_options.setText(mOptionsText);
} else {
tv_options.setText(DefaultOptionsText);
}
int optionsTextColor = typedArray.getColor(R.styleable.SettingItem_optionsTextColor, mOptionsTextColor);
tv_options.setTextColor(optionsTextColor);
mEnableText = typedArray.getString(R.styleable.SettingItem_enableText);
mDisableText = typedArray.getString(R.styleable.SettingItem_disableText);
int enableTextColor = typedArray.getColor(R.styleable.SettingItem_enableTextColor, mHintTextColor);
tv_hint.setTextColor(enableTextColor);
// boolean rootClick = typedArray.getBoolean(R.styleable.SettingItem_rootClick, mRootClick);
// if (rootClick) {
// if (mRootOnClickListener != null) {
// cl_root.setOnClickListener(mRootOnClickListener);
// }
// }
mShowHintText = typedArray.getBoolean(R.styleable.SettingItem_showHintText, mShowHintText);
mShowToggle = typedArray.getBoolean(R.styleable.SettingItem_showToggle, mShowToggle);
mShowMore = typedArray.getBoolean(R.styleable.SettingItem_showMore, mShowMore);
mLinkage = typedArray.getBoolean(R.styleable.SettingItem_linkage, mLinkage);
mShowDivider = typedArray.getBoolean(R.styleable.SettingItem_dividerLine, mShowDivider);
} finally {
typedArray.recycle();
}
} else {
tv_options.setText(mOptionsText);
tv_hint.setText(mEnableText);
tv_options.setTextColor(mOptionsTextColor);
tv_hint.setTextColor(mHintTextColor);
}
if (mShowHintText) {
tv_hint.setVisibility(VISIBLE);
} else {
tv_hint.setVisibility(GONE);
}
if (mShowToggle) {
tb.setVisibility(VISIBLE);
} else {
tb.setVisibility(GONE);
}
if (mShowMore) {
iv_more.setVisibility(VISIBLE);
} else {
iv_more.setVisibility(GONE);
}
if (mShowDivider) {
dividerLine.setVisibility(VISIBLE);
} else {
dividerLine.setVisibility(GONE);
}
if (tb.isToggleOn()) {
if (!TextUtils.isEmpty(mEnableText)) {
tv_hint.setText(mEnableText);
} else {
tv_hint.setText(DefaultEnableText);
}
} else {
if (!TextUtils.isEmpty(mDisableText)) {
tv_hint.setText(mDisableText);
} else {
tv_hint.setText(DefaultDisableText);
}
}
tb.setOnToggleInsideChanged(new ToggleButton.OnToggleInsideChanged() {
@Override
public void onToggle(boolean on) {
if (on) {
if (!TextUtils.isEmpty(mEnableText)) {
tv_hint.setText(mEnableText);
} else {
tv_hint.setText(DefaultEnableText);
}
} else {
if (!TextUtils.isEmpty(mDisableText)) {
tv_hint.setText(mDisableText);
} else {
tv_hint.setText(DefaultDisableText);
}
}
requestLayout();
}
});
if (mRootOnClickListener != null) {
cl_root.setOnClickListener(mRootOnClickListener);
}
if (mLinkage) {
cl_root.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (tb.isToggleOn()) {
tb.toggleOff();
} else {
tb.toggleOn();
}
}
});
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
}

View File

@@ -0,0 +1,389 @@
package com.ttstd.dialer.view;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;
import com.ttstd.dialer.R;
public class ToggleButton extends View {
private static final String TAG = "ToggleButton";
private SpringSystem springSystem;
private Spring spring;
/**
*
*/
private float radius;
/**
* 开启颜色
*/
private int onColor = Color.parseColor("#246FFE");
/**
* 关闭颜色
*/
private int offBorderColor = Color.parseColor("#c7c7c7");
/**
* 灰色带颜色
*/
private int offColor = Color.parseColor("#ffffff");
/**
* 手柄颜色
*/
private int spotColor = Color.parseColor("#ffffff");
/**
* 边框颜色
*/
private int borderColor = offBorderColor;
/**
* 画笔
*/
private Paint paint;
/**
* 开关状态
*/
private boolean toggleOn = false;
/**
* 边框大小
*/
private int borderWidth = 2;
/**
* 垂直中心
*/
private float centerY;
/**
* 按钮的开始和结束位置
*/
private float startX, endX;
/**
* 手柄X位置的最小和最大值
*/
private float spotMinX, spotMaxX;
/**
* 手柄大小
*/
private int spotSize;
/**
* 手柄X位置
*/
private float spotX;
/**
* 关闭时内部灰色带高度
*/
private float offLineWidth;
/**
*
*/
private RectF rect = new RectF();
/**
* 默认使用动画
*/
private boolean defaultAnimate = true;
/**
* 是否默认处于打开状态
*/
private boolean isDefaultOn = false;
/**
* 禁止点击
*/
private boolean disable = false;
private OnToggleChanged listener;
public void setDisable(boolean dis) {
this.disable = dis;
}
private ToggleButton(Context context) {
super(context);
}
public ToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setup(attrs);
}
public ToggleButton(Context context, AttributeSet attrs) {
super(context, attrs);
setup(attrs);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
spring.removeListener(springListener);
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
spring.addListener(springListener);
}
public void setup(AttributeSet attrs) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeCap(Paint.Cap.ROUND);
springSystem = SpringSystem.create();
spring = springSystem.createSpring();
//张力tension摩擦力friction
//增大张力会使弹簧更快地向目标值运动,减小摩擦力会减少弹簧运动过程中的阻力,从而使回弹更加迅速和有力。
spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(80, 10));
this.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
if (disable) {
} else {
toggle(defaultAnimate);
}
}
});
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ToggleButton);
offBorderColor = typedArray.getColor(R.styleable.ToggleButton_tbOffBorderColor, offBorderColor);
onColor = typedArray.getColor(R.styleable.ToggleButton_tbOnColor, onColor);
spotColor = typedArray.getColor(R.styleable.ToggleButton_tbSpotColor, spotColor);
offColor = typedArray.getColor(R.styleable.ToggleButton_tbOffColor, offColor);
borderWidth = typedArray.getDimensionPixelSize(R.styleable.ToggleButton_tbBorderWidth, borderWidth);
defaultAnimate = typedArray.getBoolean(R.styleable.ToggleButton_tbAnimate, defaultAnimate);
isDefaultOn = typedArray.getBoolean(R.styleable.ToggleButton_tbAsDefaultOn, isDefaultOn);
typedArray.recycle();
borderColor = offBorderColor;
if (isDefaultOn) {
toggleOn();
}
}
public void toggle() {
toggle(true);
}
public void toggle(boolean animate) {
toggleOn = !toggleOn;
Log.e(TAG, "toggle: toggleOn = " + toggleOn);
takeEffect(animate);
if (listener != null) {
listener.onToggle(toggleOn);
}
if (mOnToggleInsideChanged != null) {
mOnToggleInsideChanged.onToggle(toggleOn);
}
}
public void toggleOn() {
setToggleOn();
if (listener != null) {
listener.onToggle(toggleOn);
}
if (mOnToggleInsideChanged != null) {
mOnToggleInsideChanged.onToggle(toggleOn);
}
}
public void toggleOff() {
setToggleOff();
if (listener != null) {
listener.onToggle(toggleOn);
}
if (mOnToggleInsideChanged != null) {
mOnToggleInsideChanged.onToggle(toggleOn);
}
}
public void setToggleStatu(boolean on) {
if (on) {
setToggleOn();
} else {
setToggleOff();
}
}
/**
* 设置显示成打开样式不会触发toggle事件
*/
public void setToggleOn() {
setToggleOn(true);
}
/**
* @param animate asd
*/
public void setToggleOn(boolean animate) {
toggleOn = true;
takeEffect(animate);
}
/**
* 设置显示成关闭样式不会触发toggle事件
*/
public void setToggleOff() {
setToggleOff(true);
}
public void setToggleOff(boolean animate) {
toggleOn = false;
takeEffect(animate);
}
public int getToggleOnStatu() {
Log.e(TAG, "getToggleOnStatu: " + toggleOn);
return toggleOn ? 1 : 0;
}
public boolean isToggleOn() {
Log.e(TAG, "isToggleOn: " + toggleOn);
return toggleOn;
}
private void takeEffect(boolean animate) {
if (animate) {
spring.setEndValue(toggleOn ? 1 : 0);
} else {
//这里没有调用spring所以spring里的当前值没有变更这里要设置一下同步两边的当前值
spring.setCurrentValue(toggleOn ? 1 : 0);
calculateEffect(toggleOn ? 1 : 0);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Resources r = Resources.getSystem();
if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
final int width = getWidth();
final int height = getHeight();
radius = Math.min(width, height) * 0.5f;
centerY = radius;
startX = radius;
endX = width - radius;
spotMinX = startX + borderWidth;
spotMaxX = endX - borderWidth;
spotSize = height - 4 * borderWidth;
spotX = toggleOn ? spotMaxX : spotMinX;
offLineWidth = 0;
}
SimpleSpringListener springListener = new SimpleSpringListener() {
@Override
public void onSpringUpdate(Spring spring) {
final double value = spring.getCurrentValue();
calculateEffect(value);
}
};
private int clamp(int value, int low, int high) {
return Math.min(Math.max(value, low), high);
}
@Override
public void draw(Canvas canvas) {
//
super.draw(canvas);
rect.set(0, 0, getWidth(), getHeight());
paint.setColor(borderColor);
canvas.drawRoundRect(rect, radius, radius, paint);
if (offLineWidth > 0) {
final float cy = offLineWidth * 0.5f;
rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
// paint.setColor(offColor);
canvas.drawRoundRect(rect, cy, cy, paint);
}
rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
paint.setColor(borderColor);
canvas.drawRoundRect(rect, radius, radius, paint);
final float spotR = spotSize * 0.5f;
rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
paint.setColor(spotColor);
canvas.drawRoundRect(rect, spotR, spotR, paint);
}
/**
* @param value
*/
private void calculateEffect(final double value) {
final float mapToggleX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
spotX = mapToggleX;
float mapOffLineWidth = (float) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
offLineWidth = mapOffLineWidth;
final int fb = Color.blue(onColor);
final int fr = Color.red(onColor);
final int fg = Color.green(onColor);
final int tb = Color.blue(offBorderColor);
final int tr = Color.red(offBorderColor);
final int tg = Color.green(offBorderColor);
int sb = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fb, tb);
int sr = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fr, tr);
int sg = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fg, tg);
sb = clamp(sb, 0, 255);
sr = clamp(sr, 0, 255);
sg = clamp(sg, 0, 255);
borderColor = Color.rgb(sr, sg, sb);
postInvalidate();
}
/**
* @author ThinkPad
*/
public interface OnToggleChanged {
/**
* @param on = =
*/
public void onToggle(boolean on);
}
public interface OnToggleInsideChanged {
/**
* @param on = =
*/
void onToggle(boolean on);
}
private OnToggleInsideChanged mOnToggleInsideChanged;
public void setOnToggleInsideChanged(OnToggleInsideChanged onToggleChanged) {
mOnToggleInsideChanged = onToggleChanged;
}
public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
listener = onToggleChanged;
}
public boolean isAnimate() {
return defaultAnimate;
}
public void setAnimate(boolean animate) {
this.defaultAnimate = animate;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/wechat_call_video" android:state_pressed="true" />
<item android:drawable="@drawable/wechat_call_video" android:state_focused="true" />
<item android:drawable="@drawable/wechat_call_normal" />
</selector>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 内部颜色 -->
<solid android:color="@color/white" />
<stroke
android:width="@dimen/dp_1"
android:color="@color/colorAccent" />
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/dp_12" />
<padding
android:bottom="@dimen/dp_8"
android:left="@dimen/dp_8"
android:right="@dimen/dp_8"
android:top="@dimen/dp_8" />
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 内部颜色 -->
<solid android:color="@color/white" />
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/dp_12" />
<padding
android:bottom="@dimen/dp_8"
android:left="@dimen/dp_8"
android:right="@dimen/dp_8"
android:top="@dimen/dp_8" />
</shape>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 内部颜色 -->
<solid android:color="@color/white" />
<stroke
android:width="@dimen/dp_1"
android:color="@color/colorPrimary" />
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/dp_12" />
<padding
android:bottom="@dimen/dp_8"
android:left="@dimen/dp_8"
android:right="@dimen/dp_8"
android:top="@dimen/dp_8" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/contact_edit_text_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/contact_edit_text_focused" android:state_focused="true" />
<item android:drawable="@drawable/contact_edit_text_normal" />
</selector>

View File

@@ -3,11 +3,11 @@
<!-- 内部颜色 -->
<solid android:color="@color/lightGray" />
<!-- 圆角的幅度 -->
<corners android:radius="32dp" />
<corners android:radius="@dimen/dp_32" />
<padding
android:bottom="4dp"
android:left="20dp"
android:right="20dp"
android:top="4dp" />
android:bottom="@dimen/dp_4"
android:left="@dimen/dp_20"
android:right="@dimen/dp_20"
android:top="@dimen/dp_4" />
</shape>

View File

@@ -3,11 +3,11 @@
<!-- 内部颜色 -->
<solid android:color="#5591F3" />
<!-- 圆角的幅度 -->
<corners android:radius="32dp" />
<corners android:radius="@dimen/dp_32" />
<padding
android:bottom="4dp"
android:left="20dp"
android:right="20dp"
android:top="4dp" />
android:bottom="@dimen/dp_4"
android:left="@dimen/dp_20"
android:right="@dimen/dp_20"
android:top="@dimen/dp_4" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M801.63,222.37a30.72,30.72 0,0 1,0 43.44L555.44,512l246.19,246.21a30.72,30.72 0,1 1,-43.44 43.44L512,555.44 265.83,801.63a30.72,30.72 0,1 1,-43.46 -43.44L468.54,512l-246.17,-246.17a30.72,30.72 0,1 1,43.44 -43.46L512,468.54l246.21,-246.17a30.72,30.72 0,0 1,43.44 0z"
android:fillColor="#131415"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M149.95,471.78a30.72,30.72 0,0 1,41.27 -2.01l2.19,2.01 224.46,224.46 412.71,-412.73a30.72,30.72 0,0 1,41.27 -1.99l2.19,1.99a30.72,30.72 0,0 1,1.99 41.25l-1.99,2.19 -434.46,434.46a30.72,30.72 0,0 1,-41.25 1.99l-2.19,-1.99 -246.17,-246.19a30.72,30.72 0,0 1,0 -43.44z"
android:fillColor="#131415"/>
</vector>

View File

@@ -4,9 +4,6 @@
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M0,0m91.02,0l841.96,0q91.02,0 91.02,91.02l0,841.96q0,91.02 -91.02,91.02l-841.96,0q-91.02,0 -91.02,-91.02l0,-841.96q0,-91.02 91.02,-91.02Z"
android:fillColor="#C5C5C5"/>
<path
android:pathData="M794.83,824.43c-60.46,-27.12 -37.16,-6.35 -111.37,-34.16 -74.18,-27.67 -91.75,-36.75 -91.75,-36.75l4.3,-48.51s68.12,-21.17 80.9,-104.25c25.62,6.28 34.29,-25.28 35.66,-45.51 1.58,-19.54 15.18,-80.33 -16.19,-74.93 6.43,-40.58 11.49,-77.28 9.16,-96.75 -7.79,-68.19 -63.67,-139.38 -204.62,-144.63 -31.79,1.17 -63.15,7.76 -92.71,19.47 -10.39,4.1 -77.89,-4.3 -86.97,14.14 -4.93,10.05 10.24,40.52 2.05,47.08 -33,26.51 -24.12,31.22 -27.88,64.01 -2.25,19.47 2.32,56.09 8.74,96.81 -31.29,-5.46 -17.75,55.33 -16.38,74.87 1.42,20.23 9.9,51.93 35.59,45.65C336.13,684.04 419.41,705.42 419.41,705.42l4.31,48.72s-17.57,9.69 -91.76,37.43c-74.18,27.74 -50.89,5.73 -111.35,32.79C125.16,867.41 125.16,985.33 125.16,985.33c0,17.91 16.05,32.46 35.86,32.46H854.47c19.82,0 35.87,-14.56 35.87,-32.39v-0.07s0,-117.92 -95.52,-160.82v-0.07z"
android:fillColor="#E5E5E5"/>
android:pathData="M512,64C264.8,64 64,264.8 64,512s200.8,448 448,448 448,-200.8 448,-448S759.2,64 512,64zM384.8,376c4,-64 56,-115.2 120,-119.2 74.4,-4 135.2,55.2 135.2,128 0,70.4 -57.6,128 -128,128 -73.6,0 -132,-62.4 -127.2,-136.8zM768,746.4c0,12 -9.6,21.6 -21.6,21.6H278.4c-12,0 -21.6,-9.6 -21.6,-21.6v-64c0,-84.8 170.4,-128 255.2,-128 84.8,0 255.2,42.4 255.2,128l0.8,64z"
android:fillColor="#cdcdcd"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M877.7,0H146.3C65.47,0 -0.01,65.54 -0.01,146.3v731.46c0,80.76 65.48,146.24 146.3,146.24H877.7c80.82,0 146.3,-65.48 146.3,-146.24V146.3C1024,65.54 958.52,0 877.7,0z"
android:fillColor="#C3B898"/>
<path
android:pathData="M831.51,774.63s-54.05,106.01 -103.32,108.61c-228.33,-16.95 -564.75,-441.07 -533.7,-644.66 8.41,-49.28 87.99,-103.75 138.26,-97.28 46.4,25.58 138.45,164.55 138.45,232.1l-44.99,39.51c8.79,86.38 105.69,199.15 186.6,231.11 0,0 45.08,-34.93 63.3,-37.53 38.33,3.49 155.39,128.06 155.39,168.14z"
android:fillColor="#e6e6e6"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M570.03,237.23c-32.43,-32.43 -83.63,-32.43 -116.05,0L213.33,477.87c-11.95,11.95 -15.36,29.01 -8.53,44.37 6.83,15.36 20.48,25.6 37.55,25.6l8.53,0 0,179.2c0,44.37 37.55,81.92 81.92,81.92l356.69,0c44.37,0 81.92,-37.55 81.92,-81.92L771.41,547.84l8.53,0c17.07,0 30.72,-10.24 37.55,-25.6 6.83,-15.36 3.41,-32.43 -8.53,-44.37L570.03,237.23zM774.83,505.17c0,1.71 -1.71,1.71 -3.41,1.71 -22.19,0 -39.25,18.77 -39.25,40.96l0,0 0,179.2c0,22.19 -18.77,40.96 -40.96,40.96L334.51,768c-22.19,0 -40.96,-18.77 -40.96,-40.96L293.55,547.84l0,0c0,-22.19 -17.07,-40.96 -39.25,-40.96 -1.71,0 -3.41,-1.71 -3.41,-1.71 0,-1.71 0,-3.41 1.71,-5.12l232.11,-232.11c15.36,-15.36 42.67,-15.36 58.03,0l232.11,232.11C774.83,501.76 774.83,503.47 774.83,505.17zM512,0C228.69,0 0,228.69 0,512c0,283.31 228.69,512 512,512s512,-228.69 512,-512C1024,228.69 795.31,0 512,0zM512,983.04C252.59,983.04 40.96,771.41 40.96,512 40.96,252.59 252.59,40.96 512,40.96c259.41,0 471.04,209.92 471.04,471.04C983.04,771.41 771.41,983.04 512,983.04z"
android:fillColor="#272636"/>
</vector>

View File

@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="M0.1,5.1h1.5l2.1,3.8 0.7,1.5h0c0,-0.7 -0.2,-1.7 -0.2,-2.5v-2.8h1.3v7.1h-1.4l-2.1,-3.8 -0.7,-1.5h0c0,0.8 0.2,1.7 0.2,2.5v2.9H0.1v-7.1Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M8.7,5.1h0.8l-2.1,7.1h-0.8l2.1,-7.1Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M14,10.4h-2.3l-0.5,1.8h-1.4l2.3,-7.1h1.7l2.3,7.1h-1.5l-0.5,-1.8ZM13.7,9.3l-0.2,-0.8c-0.2,-0.7 -0.4,-1.6 -0.6,-2.3h0c-0.2,0.8 -0.4,1.6 -0.6,2.3l-0.2,0.8h1.7Z"/>
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M231.8,478.6c-16,16 -16,41.9 0,57.9l388.6,388.6c16,16 41.9,16 57.9,0s16,-41.9 0,-57.9L289.7,478.6c-16,-16 -41.9,-16 -57.9,0z"
android:fillColor="#242424"/>
<path
android:pathData="M231.8,537.9c16,16 41.9,16 57.9,0l388.6,-388.6c16,-16 16,-41.9 0,-57.9s-41.9,-16 -57.9,0L231.8,480c-16,16 -16,41.9 0,57.9z"
android:fillColor="#242424"/>
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 填充的颜色:这里设置背景透明 -->
<solid android:color="#FFFFFF" />
<!-- 边框的颜色 :不能和窗口背景色一样 -->
<!-- 设置按钮的四个角为弧形 -->
<!-- android:radius 弧形的半径 -->
<corners
android:bottomLeftRadius="@dimen/dp_10"
android:bottomRightRadius="@dimen/dp_10"
android:topLeftRadius="@dimen/dp_10"
android:topRightRadius="@dimen/dp_10" />
<!-- paddingButton里面的文字与Button边界的间隔 -->
<!-- <padding-->
<!-- android:bottom="@dimen/dp_10"-->
<!-- android:left="@dimen/dp_10"-->
<!-- android:right="@dimen/dp_10"-->
<!-- android:top="@dimen/dp_10" />-->
</shape>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 内部颜色 -->
<solid android:color="#1A000000" />
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/dp_16" />
<padding
android:bottom="@dimen/dp_4"
android:left="@dimen/dp_4"
android:right="@dimen/dp_4"
android:top="@dimen/dp_4" />
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/default_radius" />
<solid android:color="#BABABA" />
<!-- <gradient-->
<!-- android:angle="270"-->
<!-- android:endColor="#E6418B24"-->
<!-- android:startColor="#E612CA56"-->
<!-- android:type="linear" />-->
</shape>

View File

@@ -4,7 +4,7 @@
<!-- 圆角的幅度 -->
<corners android:radius="@dimen/default_radius" />
<solid android:color="#17D26B" />
<solid android:color="#3ABF11" />
<!-- <gradient-->
<!-- android:angle="270"-->

View File

@@ -5,6 +5,7 @@
tools:context=".activity.app.AppListActivity">
<data>
<variable
name="click"
type="com.ttstd.dialer.activity.app.AppListActivity.BtnClick" />
@@ -14,10 +15,46 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_return"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="应用列表"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -15,27 +15,77 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="新建联系人"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:src="@drawable/ic_confirm"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="@dimen/dp_8"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/tv_save"
app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toBottomOf="@+id/bar">
<com.shehuan.niv.NiceImageView
android:id="@+id/nv_avatar"
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_32"
android:src="@drawable/ic_default_avatar"
app:is_circle="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48">
android:layout_height="@dimen/dp_64"
android:layout_marginTop="@dimen/dp_8">
<TextView
<com.shehuan.niv.NiceImageView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:singleLine="true"
android:text="姓名"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -43,12 +93,13 @@
<EditText
android:id="@+id/et_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/contact_edit_text_selector"
android:hint="姓名"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_name"
@@ -58,18 +109,13 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48">
android:layout_height="@dimen/dp_64"
android:layout_marginTop="@dimen/dp_8">
<TextView
<ImageView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:singleLine="true"
android:text="电话"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -77,13 +123,14 @@
<EditText
android:id="@+id/et_phone"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/contact_edit_text_selector"
android:hint="电话"
android:inputType="phone"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_phone"
@@ -91,23 +138,52 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_64"
android:layout_marginTop="@dimen/dp_8">
<ImageView
android:id="@+id/tv_remark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_remark"
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="@drawable/contact_edit_text_selector"
android:hint="备注"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_remark"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<TextView
android:id="@+id/tv_save"
android:layout_width="wrap_content"
android:onClick="@{click::save}"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/dp_32"
android:maxLines="1"
android:onClick="@{click::save}"
android:singleLine="true"
android:text="保存"
android:textColor="@color/black"
android:textSize="@dimen/sp_18"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".activity.contact.edit.ContactEditActivity">
<data>
@@ -13,5 +14,47 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="编辑联系人"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:src="@drawable/ic_confirm"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -11,27 +11,69 @@
type="com.ttstd.dialer.activity.contact.list.ContactListActivity.BtnClick" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="@dimen/dp_64"
android:layout_height="@dimen/dp_64"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/dp_32"
android:layout_marginBottom="@dimen/dp_32"
android:onClick="@{click::addContact}"
android:src="@drawable/ic_add"
app:fabCustomSize="@dimen/dp_64"
app:layout_behavior="com.ttstd.dialer.view.ScrollAwareFABBehavior" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_return"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="联系人列表"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="@dimen/dp_64"
android:layout_height="@dimen/dp_64"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/dp_32"
android:layout_marginBottom="@dimen/dp_32"
android:onClick="@{click::addContact}"
android:src="@drawable/ic_add"
app:fabCustomSize="@dimen/dp_64"
app:layout_behavior="com.ttstd.dialer.view.ScrollAwareFABBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.settings.home.SettingsActivity">
<data>
<variable
name="click"
type="com.ttstd.dialer.activity.settings.home.SettingsActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="桌面设置"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:src="@drawable/ic_confirm"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/settings_card_bg"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="@dimen/dp_100"
android:layout_weight="1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
app:layout_constraintBottom_toBottomOf="parent"
android:onClick="@{click::openCallSettings}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拨号设置"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="@dimen/dp_100"
android:layout_weight="1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
android:onClick="@{click::openUtilsSettings}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="功能设置"
android:textColor="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="@dimen/dp_100"
android:layout_weight="1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.settings.call.SettingsCallActivity">
<data>
<variable
name="click"
type="com.ttstd.dialer.activity.settings.call.SettingsCallActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="拨号设置"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:src="@drawable/ic_confirm"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar"
app:layout_constraintVertical_bias="0.0">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/settings_card_bg"
android:orientation="vertical">
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_fast_call_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_fast_call_phone"
app:enableText="@string/enable_text_fast_call_phone"
app:optionsText="@string/options_text_fast_call_phone" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_voice_broadcast"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_voice_broadcast"
app:enableText="@string/enable_text_voice_broadcast"
app:optionsText="@string/options_text_voice_broadcast" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_auto_accept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_auto_accept"
app:enableText="@string/enable_text_auto_accept"
app:optionsText="@string/options_text_auto_accept" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_hands_free"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_hands_free"
app:enableText="@string/enable_text_hands_free"
app:optionsText="@string/options_text_hands_free" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_auto_call"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_auto_call"
app:enableText="@string/enable_text_auto_call"
app:optionsText="@string/options_text_auto_call" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".activity.settings.utils.SettingsUtilsActivity">
<data>
<variable
name="click"
type="com.ttstd.dialer.activity.settings.utils.SettingsUtilsActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:onClick="@{click::exit}"
android:src="@drawable/ic_cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="拨号设置"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:src="@drawable/ic_confirm"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar"
app:layout_constraintVertical_bias="0.0">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/settings_card_bg"
android:orientation="vertical">
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_float_window"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_float"
app:enableText="@string/enable_text_float"
app:optionsText="@string/options_text_float" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_float_window_kill"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_float_kill"
app:enableText="@string/enable_text_float_kill"
app:optionsText="@string/options_text_float_kill" />
<com.ttstd.dialer.view.SettingItem
android:id="@+id/si_default_launcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:disableText="@string/disable_text_default_launcher"
app:enableText="@string/enable_text_default_launcher"
app:optionsText="@string/options_text_default_launcher" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_100">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="截图"
android:onClick="@{click::screenshotSnap}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_snap"
android:layout_width="@dimen/dp_100"
android:layout_height="@dimen/dp_100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".activity.template.TemplateActivity">
<data>
<variable
name="click"
type="com.ttstd.dialer.activity.template.TemplateActivity.BtnClick" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_8"
android:src="@drawable/ic_cancel"
android:onClick="@{click::exit}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:singleLine="true"
android:text="页面标题"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_8"
android:visibility="gone"
android:src="@drawable/ic_confirm"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bar">
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -17,104 +17,108 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_180"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="@dimen/sp_30"
tools:text="北京" />
<TextView
android:id="@+id/tv_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_4"
android:maxLines="1"
android:singleLine="true"
android:text="@{weatherNow.temp+`°`}"
android:textColor="@color/white"
android:textSize="@dimen/sp_40"
tools:text="N/A°" />
<TextView
android:id="@+id/tv_weather_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:singleLine="true"
android:text="@{weatherNow.text}"
android:textColor="@color/white"
android:textSize="@dimen/sp_20"
tools:text="晴" />
<TextView
android:id="@+id/tv_temp_range"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="@dimen/sp_20"
tools:text="最高10° 最低0°" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout">
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_180"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/sp_30"
tools:text="北京" />
<TextView
android:id="@+id/tv_temp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_4"
android:maxLines="1"
android:singleLine="true"
android:text="@{weatherNow==null?`N/A°`: weatherNow.temp+`°`}"
android:textColor="@color/black"
android:textSize="@dimen/sp_40"
tools:text="N/A°" />
<TextView
android:id="@+id/tv_weather_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_4"
android:maxLines="1"
android:singleLine="true"
android:text="@{weatherNow.text}"
android:textColor="@color/black"
android:textSize="@dimen/sp_24"
tools:text="晴" />
<TextView
android:id="@+id/tv_temp_range"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_4"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="@dimen/sp_20"
tools:text="最高10° 最低0°" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_hourly"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:layout_margin="@dimen/dp_8"
android:background="@drawable/weather_card_background" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/dp_8"
android:background="@drawable/weather_card_background" />
</LinearLayout>

View File

@@ -20,10 +20,10 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_220"
android:layout_marginBottom="@dimen/dp_16"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_32"
android:layout_marginEnd="@dimen/dp_32"
android:layout_marginBottom="@dimen/dp_16"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
@@ -39,14 +39,13 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_video"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_height="@dimen/dp_56">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@drawable/wechat_call_video"
android:background="@drawable/call_phone_selector"
android:onClick="@{click::callWechatVideo}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -64,7 +63,7 @@
android:singleLine="true"
android:text="微信视频"
android:textColor="@color/white"
android:textSize="@dimen/sp_16"
android:textSize="@dimen/sp_22"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -78,15 +77,14 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_audio"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_height="@dimen/dp_56"
android:layout_marginTop="@dimen/dp_8">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_8"
android:background="@drawable/wechat_call_audio"
android:background="@drawable/call_phone_selector"
android:onClick="@{click::callWechatAudio}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -104,7 +102,7 @@
android:singleLine="true"
android:text="微信语音"
android:textColor="@color/white"
android:textSize="@dimen/sp_16"
android:textSize="@dimen/sp_22"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -118,15 +116,14 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_dial"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_height="@dimen/dp_56"
android:layout_marginTop="@dimen/dp_8">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_8"
android:background="@drawable/wechat_call_dialer"
android:background="@drawable/call_phone_selector"
android:onClick="@{click::callPhone}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -144,7 +141,7 @@
android:singleLine="true"
android:text="电话"
android:textColor="@color/white"
android:textSize="@dimen/sp_16"
android:textSize="@dimen/sp_22"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -156,17 +153,16 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_sms"
android:id="@+id/cl_cancel"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_height="@dimen/dp_56"
android:layout_marginTop="@dimen/dp_8">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="@dimen/dp_8"
android:background="@drawable/wechat_call_cancel"
android:background="@drawable/call_phone_selector"
android:onClick="@{click::cancel}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -184,7 +180,7 @@
android:singleLine="true"
android:text="取消"
android:textColor="@color/white"
android:textSize="@dimen/sp_16"
android:textSize="@dimen/sp_22"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -2,12 +2,12 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="@dimen/dp_48"
android:layout_width="@dimen/dp_60"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_100"
android:layout_height="@dimen/dp_140"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -25,8 +25,8 @@
android:layout_height="wrap_content"
android:maxLines="1"
android:minEms="4"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -42,14 +42,16 @@
<ImageView
android:id="@+id/iv_icon"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_not_applicable"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
@@ -62,8 +64,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:textColor="@color/black"
android:textSize="@dimen/sp_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -18,7 +18,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_48"
android:layout_height="@dimen/dp_52"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view">
@@ -29,9 +29,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:minEms="4"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:minEms="3"
android:textColor="@color/black"
android:textSize="@dimen/sp_22"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -39,11 +39,12 @@
<ImageView
android:id="@+id/iv_icon"
android:layout_width="@dimen/dp_24"
android:layout_height="@dimen/dp_24"
android:layout_marginStart="@dimen/dp_24"
android:layout_width="@dimen/dp_32"
android:layout_height="@dimen/dp_32"
android:layout_marginStart="@dimen/dp_32"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_not_applicable"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/tv_date"
app:layout_constraintTop_toTopOf="parent" />
@@ -51,8 +52,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/dp_24"
android:layout_marginEnd="@dimen/dp_48"
android:layout_marginStart="@dimen/dp_48"
android:layout_marginEnd="@dimen/dp_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iv_icon">
@@ -60,10 +61,9 @@
android:id="@+id/tv_temp_min"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/dp_16"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:textColor="@color/black"
android:textSize="@dimen/sp_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -74,8 +74,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:textSize="@dimen/sp_18"
android:textColor="@color/black"
android:textSize="@dimen/sp_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.shehuan.niv.NiceImageView
android:id="@+id/nv_back"
android:layout_width="@dimen/dp_80"
android:layout_height="@dimen/dp_80"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_home_float_window"
app:is_circle="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_root"
android:layout_width="match_parent"
android:layout_height="@dimen/settings_item_height"
android:layout_weight="1"
android:background="@drawable/settings_card_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tb"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_options"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/settings_text_color_item"
android:textSize="@dimen/settings_text_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_hint"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/top_height"
android:maxLines="1"
android:textColor="@color/setting_disable_color"
android:textSize="@dimen/settings_text_explanation_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_options" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.ttstd.dialer.view.ToggleButton
android:id="@+id/tb"
android:layout_width="@dimen/dp_48"
android:layout_height="@dimen/dp_24"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/iv_more"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_more"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="16dp"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="@drawable/icon_more"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/lightGray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,6 +2,7 @@
<resources>
<dimen name="home_item_text_size">71.1111sp</dimen>
<dimen name="default_radius">28.4444dp</dimen>
<dimen name="dp_m_60">-213.3333dp</dimen>
<dimen name="dp_m_30">-106.6667dp</dimen>
<dimen name="dp_m_20">-71.1111dp</dimen>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ToggleButton">
<attr name="tbBorderWidth" format="dimension" />
<attr name="tbOffBorderColor" format="reference|color" />
<attr name="tbOffColor" format="reference|color" />
<attr name="tbOnColor" format="reference|color" />
<attr name="tbSpotColor" format="reference|color" />
<attr name="tbAnimate" format="reference|boolean" />
<attr name="tbAsDefaultOn" format="reference|boolean" />
</declare-styleable>
<declare-styleable name="SettingItem">
<attr name="optionsText" format="reference|string" />
<attr name="optionsTextColor" format="reference|color" />
<attr name="enableText" format="reference|string" />
<attr name="disableText" format="reference|string" />
<attr name="enableTextColor" format="reference|color" />
<attr name="showHintText" format="reference|boolean" />
<attr name="showToggle" format="reference|boolean" />
<attr name="showMore" format="reference|boolean" />
<attr name="linkage" format="reference|boolean" />
<attr name="dividerLine" format="reference|boolean" />
</declare-styleable>
</resources>

View File

@@ -1,14 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="colorPrimary">#2196F3</color>
<color name="colorPrimaryDark">#3F51B5</color>
<color name="colorAccent">#03A9F4</color>
<color name="indicator_color_normal">#99bd2f25</color>
<color name="indicator_color_selected">#BD2F25</color>
<color name="default_background_color">#F8CBBD</color>
<color name="default_background_color">#ECECEC</color>
<color name="settings_text_color_item">#000000</color>
<color name="setting_enable_color">#000000</color>
<color name="setting_disable_color">#9D9D9D</color>
<!--https://www.jianshu.com/p/8dc258dfd189-->

View File

@@ -3,6 +3,12 @@
<dimen name="home_item_text_size">20sp</dimen>
<dimen name="default_radius">8dp</dimen>
<dimen name="settings_item_height">80dp</dimen>
<dimen name="settings_text_size">18sp</dimen>
<dimen name="settings_text_explanation_size">15sp</dimen>
<dimen name="top_height">2dp</dimen>
<dimen name="dp_m_60">-60dp</dimen>
<dimen name="dp_m_30">-30dp</dimen>
<dimen name="dp_m_20">-20dp</dimen>

View File

@@ -5,6 +5,45 @@
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="floating_action_button_behavior">com.ttstd.dialer.view.ScrollAwareFABBehavior</string>
<string name="kill_app_number">共清理了%d个应用</string>
<string name="accessibility_service_label">❤拨号助手无障碍服务👈🏻</string>
<string name="accessibility_service_description">使用拨号助手一键拨号</string>
<!--call start-->
<string name="options_text_fast_call_phone">开启快捷通话</string>
<string name="enable_text_fast_call_phone">已开启,快捷联系人通话类型</string>
<string name="disable_text_fast_call_phone">未开启,快捷联系人通话类型</string>
<string name="options_text_voice_broadcast">来电语音播报</string>
<string name="enable_text_voice_broadcast">已开启,电话呼入时语音播报联系人</string>
<string name="disable_text_voice_broadcast">未开启,电话呼入时语音播报联系人</string>
<string name="options_text_auto_accept">微信自动接听</string>
<string name="enable_text_auto_accept">已开启,自动接听视频和语音</string>
<string name="disable_text_auto_accept">未开启,自动接听视频和语音</string>
<string name="options_text_hands_free">自动开启免提</string>
<string name="enable_text_hands_free">已开启,自动开启免提</string>
<string name="disable_text_hands_free">未开启,自动开启免提</string>
<string name="options_text_auto_call">微信自动拨打视频</string>
<string name="enable_text_auto_call">已开启,无障碍模式实现微信一键通话</string>
<string name="disable_text_auto_call">未开启,无障碍模式实现微信一键通话</string>
<!--call end-->
<!--utils start-->
<string name="options_text_float">开启悬浮按钮</string>
<string name="enable_text_float">已开启,点小圆点可以直接返回桌面</string>
<string name="disable_text_float">未开启,点小圆点可以直接返回桌面</string>
<string name="options_text_float_kill">悬浮按钮清理内存</string>
<string name="enable_text_float_kill">已开启,在桌面时点击悬浮窗清理内存</string>
<string name="disable_text_float_kill">未开启,在桌面时点击悬浮窗清理内存</string>
<string name="options_text_default_launcher">设置为默认桌面</string>
<string name="enable_text_default_launcher">已设置为默认桌面</string>
<string name="disable_text_default_launcher">未设置为默认桌面</string>
<!--utils end-->
</resources>

View File

@@ -13,6 +13,8 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="backgroundColor">@color/default_background_color</item>
</style>
<style name="AppThemeFitsSystemWindows" parent="Theme.AppCompat.Light.NoActionBar">
@@ -28,6 +30,7 @@
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/black</item>
<item name="colorAccent">@color/black</item>
<item name="backgroundColor">@color/black</item>
<item name="android:fitsSystemWindows">true</item>
</style>