diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index aa6329525a..56e37139b2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,6 +28,9 @@
+
+
diff --git a/app/src/main/java/com/gh/common/util/DialogUtils.java b/app/src/main/java/com/gh/common/util/DialogUtils.java
index bef388bdd5..fc8d2e5a8b 100644
--- a/app/src/main/java/com/gh/common/util/DialogUtils.java
+++ b/app/src/main/java/com/gh/common/util/DialogUtils.java
@@ -23,6 +23,10 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+
import com.gh.common.view.DrawableView;
import com.gh.gamecenter.AboutActivity;
import com.gh.gamecenter.R;
@@ -33,10 +37,6 @@ import com.lightgame.utils.Utils;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-
public class DialogUtils {
public static Dialog showWaitDialog(Context context, String msg) {
@@ -788,7 +788,7 @@ public class DialogUtils {
public static void showPrivacyPolicyDialog(Context context, String title, String content, EmptyCallback callback) {
final Context activityContext = checkDialogContext(context);
-
+
// 区分此 dialog 是点击 dialog 外部取消的还是点击返回取消的
AtomicBoolean isCanceledByClickOutsideOfDialog = new AtomicBoolean(true);
@@ -804,7 +804,7 @@ public class DialogUtils {
}
final Dialog dialog = new Dialog(activityContext, R.style.GhAlertDialog);
-
+
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
View contentView = LayoutInflater.from(activityContext).inflate(R.layout.dialog_privacy_policy, null);
TextView contentTv = contentView.findViewById(R.id.dialog_content);
@@ -844,13 +844,13 @@ public class DialogUtils {
dialog.setOnDismissListener(d -> {
callback.onCallback();
});
-
+
dialog.setOnCancelListener(cd -> {
if (isCanceledByClickOutsideOfDialog.get()) {
MtaHelper.onEvent("隐私政策弹窗", "隐私政策弹窗", "点击空白");
}
});
-
+
dialog.setOnKeyListener((dialog1, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
isCanceledByClickOutsideOfDialog.set(false);
@@ -923,6 +923,48 @@ public class DialogUtils {
return dialog;
}
+ public static Dialog showUsageStatsDialog(Context context, final ConfirmListener cmListener, final CancelListener clListener) {
+ context = checkDialogContext(context);
+
+ final Dialog dialog = new Dialog(context, R.style.GhAlertDialog);
+
+ View contentView = LayoutInflater.from(context).inflate(R.layout.dialog_alert, null);
+ TextView contentTv = contentView.findViewById(R.id.dialog_content);
+ TextView titleTv = contentView.findViewById(R.id.dialog_title);
+ TextView negativeTv = contentView.findViewById(R.id.dialog_negative);
+ TextView positiveTv = contentView.findViewById(R.id.dialog_positive);
+ contentTv.setText("开启后可以为你记录每款游戏的游戏时长,你只需再系统【有权查看使用情况的应用】设置中开启即可");
+
+ titleTv.setText("游戏时长记录");
+ negativeTv.setText("暂不");
+ positiveTv.setText("去开启");
+
+ negativeTv.setOnClickListener(view -> {
+ if (clListener != null) {
+ clListener.onCancel();
+ }
+ dialog.dismiss();
+ });
+
+ positiveTv.setOnClickListener(view -> {
+ if (cmListener != null) {
+ cmListener.onConfirm();
+ }
+ dialog.dismiss();
+ });
+
+ dialog.setOnDismissListener(dialog1 -> {
+ if (clListener != null) {
+ clListener.onCancel();
+ }
+ });
+
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setContentView(contentView);
+ dialog.show();
+ return dialog;
+ }
+
/**
* @param context may be is application context
* @return activity context
diff --git a/app/src/main/java/com/gh/common/util/UsageStatsHelper.kt b/app/src/main/java/com/gh/common/util/UsageStatsHelper.kt
new file mode 100644
index 0000000000..e8792664d6
--- /dev/null
+++ b/app/src/main/java/com/gh/common/util/UsageStatsHelper.kt
@@ -0,0 +1,140 @@
+package com.gh.common.util
+
+import android.annotation.SuppressLint
+import android.annotation.TargetApi
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.OPSTR_GET_USAGE_STATS
+import android.app.usage.UsageStats
+import android.app.usage.UsageStatsManager
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Process
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import com.gh.gamecenter.retrofit.BiResponse
+import com.gh.gamecenter.retrofit.RetrofitManager
+import com.halo.assistant.HaloApp
+import com.lightgame.utils.Utils
+import io.reactivex.schedulers.Schedulers
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import okhttp3.ResponseBody
+import org.json.JSONArray
+import org.json.JSONObject
+import java.util.*
+
+/**
+ * 参考资料: https://developer.android.com/reference/android/app/usage/UsageStatsManager
+ */
+object UsageStatsHelper {
+
+ const val USAGE_STATUS_SP_KEY = "usage_status_sp_key"
+
+ private var mUsageStatsManager: UsageStatsManager? = null
+
+ private val mApi = RetrofitManager.getInstance(HaloApp.getInstance().application).api
+
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+ private fun getUsageStatsList(intervalStyle: Int, beginTime: Long): List? {
+ if (mUsageStatsManager == null) {
+ mUsageStatsManager = HaloApp
+ .getInstance().application
+ .getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+ }
+
+ val usageStatsList = mUsageStatsManager?.queryUsageStats(
+ intervalStyle,
+ beginTime,
+ System.currentTimeMillis())
+
+ return if (null == usageStatsList || usageStatsList.size == 0) {
+ null
+ } else usageStatsList
+ }
+
+ /**
+ * 第一次统计默认起始时间
+ */
+ private fun getDefaultBeginTime(): Long {
+ val calendar = Calendar.getInstance()
+ calendar.add(Calendar.YEAR, -5)
+ return calendar.timeInMillis
+ }
+
+ @SuppressLint("CheckResult")
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
+ private fun postUsageStats(beginTime: Long) {
+ val usageStatsList = getUsageStatsList(UsageStatsManager.INTERVAL_YEARLY, beginTime)
+ ?: return
+
+ val pkgAndTime = HashMap()
+ for (usageStats in usageStatsList) {
+ val time = pkgAndTime[usageStats.packageName]
+ if (time == null) {
+ pkgAndTime[usageStats.packageName] = usageStats.totalTimeInForeground
+ } else {
+ pkgAndTime[usageStats.packageName] = usageStats.totalTimeInForeground + time
+ }
+ }
+
+ val postBody = JSONArray()
+ for (entry in pkgAndTime) {
+ val jsonObject = JSONObject()
+ jsonObject.put("package", entry.key)
+ jsonObject.put("duration", entry.value)
+ postBody.put(jsonObject)
+ }
+
+ val body = RequestBody.create(MediaType.parse("application/json"), postBody.toString())
+ mApi.postUsageStatus(body)
+ .subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.io())
+ .subscribe(object : BiResponse() {
+ override fun onSuccess(data: ResponseBody) {
+
+ }
+ })
+ }
+
+ @JvmStatic
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
+ fun checkForPermission(): Boolean {
+ val appOps = HaloApp
+ .getInstance().application
+ .getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
+ val mode = appOps.checkOpNoThrow(
+ OPSTR_GET_USAGE_STATS,
+ Process.myUid(),
+ HaloApp.getInstance().application.packageName)
+
+ return mode == MODE_ALLOWED
+ }
+
+ @JvmStatic
+ @SuppressLint("CheckResult")
+ fun readyPostUsageStats() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 || !checkForPermission()) return
+ mApi.usageStatusUpdateTime
+ .subscribeOn(Schedulers.io())
+ .observeOn(Schedulers.io())
+ .subscribe(object : BiResponse() {
+ override fun onSuccess(data: ResponseBody) {
+ val body = JSONObject(data.string())
+ var beginTime = body.getLong("update_time")
+ if (beginTime == 0L) beginTime = getDefaultBeginTime()
+ postUsageStats(beginTime)
+ }
+ })
+ }
+
+ @JvmStatic
+ fun skipToUsageStats(context: Context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
+ } else {
+ Utils.toast(context, "当前设备不支持查看应用使用情况")
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
index b3c9fad8b0..30c0084ac8 100644
--- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
+++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java
@@ -2148,4 +2148,16 @@ public interface ApiService {
@GET("home/navbar")
Single getHomeNavBar(@Query("channel") String channel);
+ /**
+ * 获取更新时间(上一次提交的时间)
+ */
+ @GET("devices/{device_id}/played_update_time")
+ Single getUsageStatusUpdateTime();
+
+ /**
+ * 批量新增游戏时长
+ */
+ @POST("users/{user_id}/played_times:batch_create")
+ Single postUsageStatus(@Body RequestBody body);
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java b/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java
index 6b400d1ba7..2196535787 100644
--- a/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java
+++ b/app/src/main/java/com/halo/assistant/fragment/SettingsFragment.java
@@ -6,6 +6,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
@@ -30,6 +31,7 @@ import com.gh.common.util.MtaHelper;
import com.gh.common.util.PackageUtils;
import com.gh.common.util.PermissionHelper;
import com.gh.common.util.StringUtils;
+import com.gh.common.util.UsageStatsHelper;
import com.gh.gamecenter.AboutActivity;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.MainActivity;
@@ -74,6 +76,8 @@ public class SettingsFragment extends NormalFragment {
SwitchButton mSettingAutoinstallSb;
@BindView(R.id.setting_sb_concerngame)
SwitchButton mSettingConcerngameSb;
+ @BindView(R.id.setting_sb_usage_stats)
+ SwitchButton mSettingUsageStatsSb;
@BindView(R.id.setting_tv_cache)
TextView mSettingCacheTv;
@BindView(R.id.setting_tv_size)
@@ -144,6 +148,36 @@ public class SettingsFragment extends NormalFragment {
fontTextSize(checkSizeIndex);
initLoginStatus();
+
+ initUsageStats();
+ }
+
+ private void initUsageStats() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
+ return;
+ }
+
+ if (UsageStatsHelper.checkForPermission() && sp.getBoolean(UsageStatsHelper.USAGE_STATUS_SP_KEY, false)) {
+ mSettingUsageStatsSb.setChecked(true);
+ }
+
+ mSettingUsageStatsSb.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ if (UsageStatsHelper.checkForPermission()) {
+ UsageStatsHelper.skipToUsageStats(requireContext());
+ sp.edit().putBoolean(UsageStatsHelper.USAGE_STATUS_SP_KEY, true).apply();
+ } else {
+ DialogUtils.showUsageStatsDialog(getContext(),
+ () -> {
+ UsageStatsHelper.skipToUsageStats(requireContext());
+ mSettingUsageStatsSb.setChecked(false);
+ },
+ () -> mSettingUsageStatsSb.setChecked(false));
+ }
+ } else {
+ sp.edit().putBoolean(UsageStatsHelper.USAGE_STATUS_SP_KEY, false).apply();
+ }
+ });
}
private void initLoginStatus() {
@@ -233,7 +267,7 @@ public class SettingsFragment extends NormalFragment {
R.id.setting_rl_concerngame, R.id.setting_rl_about,
R.id.setting_logout_rl, R.id.setting_network_diagnosis,
R.id.setting_privacy_policy, R.id.setting_user_protocol,
- R.id.setting_game_submission
+ R.id.setting_game_submission, R.id.setting_usage_stats
})
@Override
public void onClick(final View v) {
@@ -313,7 +347,7 @@ public class SettingsFragment extends NormalFragment {
MtaHelper.onEvent("我的光环_设置", "设置功能", "游戏投稿");
CheckLoginUtils.checkLogin(getContext(), "设置-游戏投稿-请先登录", () -> {
PermissionHelper.checkReadPhoneStateAndStoragePermissionBeforeAction(getContext(), () -> {
- startActivity(GameSubmissionActivity.getIntent(getContext(),mEntrance,"游戏上传"));
+ startActivity(GameSubmissionActivity.getIntent(getContext(), mEntrance, "游戏上传"));
});
});
break;
@@ -321,6 +355,9 @@ public class SettingsFragment extends NormalFragment {
MtaHelper.onEvent("我的光环_设置", "设置功能", "用户协议");
startActivity(WebActivity.getWebIntent(getContext()));
break;
+ case R.id.setting_usage_stats:
+ mSettingUsageStatsSb.performClick();
+ break;
default:
break;
}
diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml
index 670e6fd14f..1280171a73 100644
--- a/app/src/main/res/layout/fragment_setting.xml
+++ b/app/src/main/res/layout/fragment_setting.xml
@@ -276,6 +276,44 @@
android:textColor="@color/title"
android:textSize="15sp" />
+
+
+
+
+
+
+
+
+
+
一键修复
下载完成自动安装游戏
安装完成自动关注游戏
+ 统计游戏时长
+ 开启后可以帮你记录玩过每款游戏的时长
安装完成自动删除安装包
…/GH-KC
…/gh-download