From 8ee97d8525276929d0bb246a5196e567b47bfb36 Mon Sep 17 00:00:00 2001 From: Yatik Date: Thu, 20 Jul 2023 17:35:42 +0530 Subject: [PATCH 1/2] Refactored MainActivity and AppDataUsageFragment to Kotlin, introduced DataUsageViewModel and UsageDataHelperImpl for single responsibility, optimized UsageDataAdapter with AsyncListDiffer and click listeners in AppDataUsageFragment, replaced AsyncTask with Kotlin coroutines, and added debug suffix for debug package. --- app/build.gradle | 26 +- .../datamonitor/adapters/UsageDataAdapter.kt | 103 ++ .../adapters/data/DataUsageViewModel.kt | 44 + .../ui/activities/AppPickerActivity.java | 3 +- .../ui/activities/ContainerActivity.java | 3 +- .../ui/activities/CrashReportActivity.java | 3 +- .../ui/activities/MainActivity.java | 984 ------------------ .../datamonitor/ui/activities/MainActivity.kt | 448 ++++++++ .../ui/fragments/AppDataUsageFragment.java | 668 ------------ .../ui/fragments/AppDataUsageFragment.kt | 393 +++++++ .../ui/fragments/HomeFragment.java | 6 +- .../ui/fragments/SystemDataUsageFragment.java | 37 +- .../datamonitor/utils/ScreenTimeUtils.kt | 150 +++ .../datamonitor/utils/helpers/ThemeHelper.kt | 18 + .../utils/helpers/UsageDataHelper.kt | 9 + .../utils/helpers/UsageDataHelperImpl.kt | 365 +++++++ 16 files changed, 1569 insertions(+), 1691 deletions(-) create mode 100644 app/src/main/java/com/drnoob/datamonitor/adapters/UsageDataAdapter.kt create mode 100644 app/src/main/java/com/drnoob/datamonitor/adapters/data/DataUsageViewModel.kt delete mode 100644 app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.java create mode 100644 app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.kt delete mode 100644 app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.java create mode 100644 app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.kt create mode 100644 app/src/main/java/com/drnoob/datamonitor/utils/ScreenTimeUtils.kt create mode 100644 app/src/main/java/com/drnoob/datamonitor/utils/helpers/ThemeHelper.kt create mode 100644 app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelper.kt create mode 100644 app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt diff --git a/app/build.gradle b/app/build.gradle index 6a11e647..9b5caea9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,6 +26,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { + applicationIdSuffix ".debug" minifyEnabled true shrinkResources false } @@ -48,21 +49,24 @@ android { dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.navigation:navigation-fragment:2.5.3' - implementation 'androidx.navigation:navigation-ui:2.5.3' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' - implementation 'androidx.core:core-splashscreen:1.0.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.6.0' + implementation 'androidx.core:core-splashscreen:1.0.1' implementation 'com.android.volley:volley:1.2.1' - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + // Lifecycle Components + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' /** */ implementation 'com.google.code.gson:gson:2.9.0' @@ -72,6 +76,6 @@ dependencies { implementation 'com.github.antonKozyriatskyi:CircularProgressIndicator:1.3.0' implementation 'fr.bmartel:jspeedtest:1.32.1' implementation 'io.ipinfo:ipinfo-api:2.1' - implementation 'com.squareup.okhttp3:okhttp:4.9.3' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.github.bumptech.glide:glide:4.15.1' } \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/adapters/UsageDataAdapter.kt b/app/src/main/java/com/drnoob/datamonitor/adapters/UsageDataAdapter.kt new file mode 100644 index 00000000..834cc00f --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/adapters/UsageDataAdapter.kt @@ -0,0 +1,103 @@ +package com.drnoob.datamonitor.adapters + +import android.content.Context +import android.content.pm.PackageManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.drnoob.datamonitor.Common +import com.drnoob.datamonitor.R +import com.drnoob.datamonitor.adapters.data.AppDataUsageModel +import com.drnoob.datamonitor.utils.NetworkStatsHelper +import com.skydoves.progressview.ProgressView + +class UsageDataAdapter(private val context: Context) : + RecyclerView.Adapter() { + + inner class UsageDataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) + + private val differCallbacks = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: AppDataUsageModel, + newItem: AppDataUsageModel + ): Boolean { + return oldItem.packageName == newItem.packageName + } + + override fun areContentsTheSame( + oldItem: AppDataUsageModel, + newItem: AppDataUsageModel + ): Boolean { + return oldItem.totalDataUsage == newItem.totalDataUsage + } + } + + val differ = AsyncListDiffer(this, differCallbacks) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): UsageDataAdapter.UsageDataViewHolder { + return UsageDataViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.app_data_usage_item, parent, false) + ) + } + + override fun onBindViewHolder(holder: UsageDataAdapter.UsageDataViewHolder, position: Int) { + + val model = differ.currentList[position] + val itemView = holder.itemView + + val mAppIcon = itemView.findViewById(R.id.app_icon) + val mAppName = itemView.findViewById(R.id.app_name) + val mDataUsage = itemView.findViewById(R.id.data_usage) + val mProgress = itemView.findViewById(R.id.progress) + + try { + if (model.packageName == "com.android.tethering") + mAppIcon.setImageResource(R.drawable.hotspot) + else if (model.packageName == "com.android.deleted") + mAppIcon.setImageResource(R.drawable.deleted_apps) + else + if (Common.isAppInstalled(context, model.packageName)) + mAppIcon.setImageDrawable( + context.packageManager.getApplicationIcon(model.packageName) + ) + else mAppIcon.setImageResource(R.drawable.deleted_apps) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + + val totalDataUsage = + NetworkStatsHelper.formatData(model.sentMobile, model.receivedMobile)[2] + + if (model.progress > 0) mProgress.progress = model.progress.toFloat() + else mProgress.progress = 1F + + mAppName.text = model.appName + mDataUsage.text = totalDataUsage + + itemView.setOnClickListener { + onItemClickListener?.let { + if (model != null) it(model) + } + } + + } + + override fun getItemCount(): Int = + differ.currentList.size + + private var onItemClickListener: ((AppDataUsageModel) -> Unit)? = null + + fun setOnItemClickListener(listener: (AppDataUsageModel) -> Unit) { + onItemClickListener = listener + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/adapters/data/DataUsageViewModel.kt b/app/src/main/java/com/drnoob/datamonitor/adapters/data/DataUsageViewModel.kt new file mode 100644 index 00000000..c2ff113f --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/adapters/data/DataUsageViewModel.kt @@ -0,0 +1,44 @@ +package com.drnoob.datamonitor.adapters.data + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.drnoob.datamonitor.utils.helpers.UsageDataHelper +import kotlinx.coroutines.launch + +class DataUsageViewModel(private val usageDataHelper: UsageDataHelper) : ViewModel() { + + private val _userAppsList: MutableLiveData> = MutableLiveData() + val userAppsList: LiveData> + get() = _userAppsList + + private val _systemAppsList: MutableLiveData> = MutableLiveData() + val systemAppsList: LiveData> + get() = _userAppsList + + fun fetchApps() = viewModelScope.launch { + usageDataHelper.fetchApps() + } + + fun loadUserAppsData(session: Int, type: Int) = viewModelScope.launch { + _userAppsList.postValue(usageDataHelper.loadUserAppsData(session, type)) + } + + fun loadSystemAppsData(session: Int, type: Int) = viewModelScope.launch { + _systemAppsList.postValue(usageDataHelper.loadSystemAppsData(session, type)) + } + +} + +class DataUsageViewModelFactory(private val usageDataHelper: UsageDataHelper) : + ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(DataUsageViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return DataUsageViewModel(usageDataHelper) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/activities/AppPickerActivity.java b/app/src/main/java/com/drnoob/datamonitor/ui/activities/AppPickerActivity.java index f4d988c8..97d7171a 100644 --- a/app/src/main/java/com/drnoob/datamonitor/ui/activities/AppPickerActivity.java +++ b/app/src/main/java/com/drnoob/datamonitor/ui/activities/AppPickerActivity.java @@ -50,6 +50,7 @@ import com.drnoob.datamonitor.databinding.ActivityAppPickerBinding; import com.drnoob.datamonitor.utils.CrashReporter; import com.drnoob.datamonitor.utils.SharedPreferences; +import com.drnoob.datamonitor.utils.helpers.ThemeHelperKt; import com.google.android.material.elevation.SurfaceColors; import java.util.ArrayList; @@ -80,7 +81,7 @@ public static void setData(Intent data) { @Override protected void onCreate(Bundle savedInstanceState) { - MainActivity.setTheme(AppPickerActivity.this); + ThemeHelperKt.setTheme(AppPickerActivity.this); Thread.setDefaultUncaughtExceptionHandler(new CrashReporter(AppPickerActivity.this)); String languageCode = SharedPreferences.getUserPrefs(this).getString(APP_LANGUAGE_CODE, "null"); String countryCode = SharedPreferences.getUserPrefs(this).getString(APP_COUNTRY_CODE, ""); diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/activities/ContainerActivity.java b/app/src/main/java/com/drnoob/datamonitor/ui/activities/ContainerActivity.java index af4d4e93..10915d6a 100644 --- a/app/src/main/java/com/drnoob/datamonitor/ui/activities/ContainerActivity.java +++ b/app/src/main/java/com/drnoob/datamonitor/ui/activities/ContainerActivity.java @@ -79,6 +79,7 @@ import com.drnoob.datamonitor.ui.fragments.SystemDataUsageFragment; import com.drnoob.datamonitor.utils.CrashReporter; import com.drnoob.datamonitor.utils.SharedPreferences; +import com.drnoob.datamonitor.utils.helpers.ThemeHelperKt; import com.google.android.material.elevation.SurfaceColors; import org.jetbrains.annotations.NotNull; @@ -97,7 +98,7 @@ public class ContainerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - MainActivity.setTheme(ContainerActivity.this); + ThemeHelperKt.setTheme(ContainerActivity.this); Thread.setDefaultUncaughtExceptionHandler(new CrashReporter(ContainerActivity.this)); String languageCode = SharedPreferences.getUserPrefs(this).getString(APP_LANGUAGE_CODE, "null"); String countryCode = SharedPreferences.getUserPrefs(this).getString(APP_COUNTRY_CODE, ""); diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/activities/CrashReportActivity.java b/app/src/main/java/com/drnoob/datamonitor/ui/activities/CrashReportActivity.java index 39212164..ac26659e 100644 --- a/app/src/main/java/com/drnoob/datamonitor/ui/activities/CrashReportActivity.java +++ b/app/src/main/java/com/drnoob/datamonitor/ui/activities/CrashReportActivity.java @@ -48,6 +48,7 @@ import com.drnoob.datamonitor.core.base.Preference; import com.drnoob.datamonitor.databinding.ActivityCrashReportBinding; import com.drnoob.datamonitor.utils.SharedPreferences; +import com.drnoob.datamonitor.utils.helpers.ThemeHelperKt; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.elevation.SurfaceColors; import com.google.android.material.snackbar.Snackbar; @@ -71,7 +72,7 @@ public class CrashReportActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - MainActivity.setTheme(CrashReportActivity.this); + ThemeHelperKt.setTheme(CrashReportActivity.this); String languageCode = SharedPreferences.getUserPrefs(this).getString(APP_LANGUAGE_CODE, "null"); String countryCode = SharedPreferences.getUserPrefs(this).getString(APP_COUNTRY_CODE, ""); if (languageCode.equals("null")) { diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.java b/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.java deleted file mode 100644 index 2216049d..00000000 --- a/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.java +++ /dev/null @@ -1,984 +0,0 @@ -/* - * Copyright (C) 2021 Dr.NooB - * - * This file is a part of Data Monitor - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.drnoob.datamonitor.ui.activities; - -import static com.drnoob.datamonitor.Common.isAppInstalled; -import static com.drnoob.datamonitor.Common.isReadPhoneStateGranted; -import static com.drnoob.datamonitor.Common.isUsageAccessGranted; -import static com.drnoob.datamonitor.Common.refreshService; -import static com.drnoob.datamonitor.Common.setLanguage; -import static com.drnoob.datamonitor.Common.showAlarmPermissionDeniedDialog; -import static com.drnoob.datamonitor.core.Values.ALARM_PERMISSION_DENIED; -import static com.drnoob.datamonitor.core.Values.APP_COUNTRY_CODE; -import static com.drnoob.datamonitor.core.Values.APP_DATA_USAGE_WARNING_CHANNEL_ID; -import static com.drnoob.datamonitor.core.Values.APP_DATA_USAGE_WARNING_CHANNEL_NAME; -import static com.drnoob.datamonitor.core.Values.APP_LANGUAGE_CODE; -import static com.drnoob.datamonitor.core.Values.APP_THEME; -import static com.drnoob.datamonitor.core.Values.BOTTOM_NAVBAR_ITEM_SETTINGS; -import static com.drnoob.datamonitor.core.Values.DATA_RESET_DATE; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_NOTIFICATION_CHANNEL_ID; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_NOTIFICATION_CHANNEL_NAME; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_SYSTEM; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_VALUE; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_WARNING_CHANNEL_ID; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_WARNING_CHANNEL_NAME; -import static com.drnoob.datamonitor.core.Values.DISABLE_BATTERY_OPTIMISATION_FRAGMENT; -import static com.drnoob.datamonitor.core.Values.GENERAL_FRAGMENT_ID; -import static com.drnoob.datamonitor.core.Values.NETWORK_SIGNAL_CHANNEL_ID; -import static com.drnoob.datamonitor.core.Values.NETWORK_SIGNAL_CHANNEL_NAME; -import static com.drnoob.datamonitor.core.Values.OTHER_NOTIFICATION_CHANNEL_ID; -import static com.drnoob.datamonitor.core.Values.OTHER_NOTIFICATION_CHANNEL_NAME; -import static com.drnoob.datamonitor.core.Values.READ_PHONE_STATE_DISABLED; -import static com.drnoob.datamonitor.core.Values.REQUEST_POST_NOTIFICATIONS; -import static com.drnoob.datamonitor.core.Values.SESSION_TODAY; -import static com.drnoob.datamonitor.core.Values.SETUP_COMPLETED; -import static com.drnoob.datamonitor.core.Values.SETUP_VALUE; -import static com.drnoob.datamonitor.core.Values.SHOULD_SHOW_BATTERY_OPTIMISATION_ERROR; -import static com.drnoob.datamonitor.core.Values.TYPE_MOBILE_DATA; -import static com.drnoob.datamonitor.core.Values.UPDATE_NOTIFICATION_CHANNEL; -import static com.drnoob.datamonitor.core.Values.UPDATE_VERSION; -import static com.drnoob.datamonitor.core.Values.USAGE_ACCESS_DISABLED; -import static com.drnoob.datamonitor.ui.fragments.AppDataUsageFragment.getAppContext; -import static com.drnoob.datamonitor.ui.fragments.AppDataUsageFragment.onDataLoaded; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getAppMobileDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getAppWifiDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeletedAppsMobileDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeletedAppsWifiDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeviceMobileDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeviceWifiDataUsage; -import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getTetheringDataUsage; - -import android.Manifest; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.app.Activity; -import android.app.AlarmManager; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.media.AudioAttributes; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.PowerManager; -import android.os.RemoteException; -import android.provider.Settings; -import android.text.Spannable; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.RemoteViews; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.app.NotificationCompat; -import androidx.navigation.NavController; -import androidx.navigation.NavDestination; -import androidx.navigation.fragment.NavHostFragment; -import androidx.navigation.ui.NavigationUI; -import androidx.preference.PreferenceManager; - -import com.drnoob.datamonitor.BuildConfig; -import com.drnoob.datamonitor.R; -import com.drnoob.datamonitor.Widget.DataUsageWidget; -import com.drnoob.datamonitor.adapters.data.AppDataUsageModel; -import com.drnoob.datamonitor.core.task.DatabaseHandler; -import com.drnoob.datamonitor.databinding.ActivityMainBinding; -import com.drnoob.datamonitor.utils.CrashReporter; -import com.drnoob.datamonitor.utils.SharedPreferences; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.elevation.SurfaceColors; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public class MainActivity extends AppCompatActivity { - private static final String TAG = MainActivity.class.getSimpleName(); - ActivityMainBinding binding; - - public static List mAppsList = new ArrayList<>(); - public static List mUserAppsList = new ArrayList<>(); - public static List mSystemAppsList = new ArrayList<>(); - public static int value; - public static String themeSwitch; - private static Boolean isDataLoading = false; - private static Boolean refreshAppDataUsage = false; - - public static Boolean getRefreshAppDataUsage() { - return refreshAppDataUsage; - } - - public static void setRefreshAppDataUsage(Boolean refreshAppDataUsage) { - MainActivity.refreshAppDataUsage = refreshAppDataUsage; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - MainActivity.setTheme(MainActivity.this); - Thread.setDefaultUncaughtExceptionHandler(new CrashReporter(MainActivity.this)); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - if (!isReadPhoneStateGranted(MainActivity.this)) { - startActivity(new Intent(this, SetupActivity.class) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) - .putExtra(SETUP_VALUE, READ_PHONE_STATE_DISABLED)); - finish(); - } - } - super.onCreate(savedInstanceState); - String languageCode = SharedPreferences.getUserPrefs(this).getString(APP_LANGUAGE_CODE, "null"); - String countryCode = SharedPreferences.getUserPrefs(this).getString(APP_COUNTRY_CODE, ""); - if (languageCode.equals("null")) { - setLanguage(this, "en", countryCode); - } else { - setLanguage(this, languageCode, countryCode); - } - - try { - refreshService(this); - } catch (Exception e) { - e.printStackTrace(); - } - try { - if (isUsageAccessGranted(MainActivity.this)) { - - binding = ActivityMainBinding.inflate(getLayoutInflater()); - setTheme(R.style.Theme_DataMonitor); - setContentView(binding.getRoot()); - setSupportActionBar(binding.mainToolbar); - binding.mainToolbar.setBackgroundColor(SurfaceColors.SURFACE_2.getColor(this)); - getWindow().setStatusBarColor(SurfaceColors.SURFACE_2.getColor(this)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { - getWindow().setNavigationBarColor(SurfaceColors.SURFACE_2.getColor(this)); - } - - SharedPreferences.getUserPrefs(this).edit().putBoolean(SETUP_COMPLETED, true).apply(); - - if (binding.bottomNavigationView.getSelectedItemId() == R.id.bottom_menu_home) { - getSupportActionBar().setTitle(getString(R.string.app_name)); - } - - NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_nav_host_fragment); - NavController controller = navHostFragment.getNavController(); - controller.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() { - @Override - public void onDestinationChanged(@NotNull NavController navController, @NotNull NavDestination navDestination, @Nullable Bundle bundle) { - changeBanner(navDestination); - } - }); // working - - NavigationUI.setupWithNavController(binding.bottomNavigationView, controller); - - DatabaseHandler databaseHandler = new DatabaseHandler(MainActivity.this); - if (databaseHandler.getUsageList() != null && databaseHandler.getUsageList().size() > 0) { - - } else { - MainActivity.FetchApps fetchApps = new MainActivity.FetchApps(this); - fetchApps.execute(); - - } - - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel(); - } - - value = getIntent().getIntExtra(DATA_USAGE_VALUE, 0); - - if (value == DATA_USAGE_SYSTEM) { - binding.bottomNavigationView.setVisibility(View.GONE); - binding.bottomNavigationView.setSelectedItemId(R.id.bottom_menu_app_data_usage); - getSupportActionBar().setTitle(R.string.system_data_usage); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow); - controller.navigate(R.id.system_data_usage); // >> working - } - if (value != DATA_USAGE_SYSTEM) { - if (!isDataLoading()) { - MainActivity.LoadData loadData = new MainActivity.LoadData(MainActivity.this, SESSION_TODAY, TYPE_MOBILE_DATA); - loadData.execute(); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - NotificationManager notificationManager = getSystemService(NotificationManager.class); - if (!notificationManager.areNotificationsEnabled()) { - requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_POST_NOTIFICATIONS); - } - } - } else { - onResume(); - } - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == REQUEST_POST_NOTIFICATIONS) { - if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { - new MaterialAlertDialogBuilder(MainActivity.this) - .setTitle(R.string.label_permission_denied) - .setMessage(R.string.notification_permission_denied_body) - .setPositiveButton(R.string.action_grant, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - Intent intent = new Intent(); - intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra("app_package", getPackageName()); - intent.putExtra("app_uid", getApplicationInfo().uid); - intent.putExtra("android.provider.extra.APP_PACKAGE", getPackageName()); - - startActivity(intent); - } - }) - .setNegativeButton(R.string.action_cancel, null) - .show(); - } - } - } - - private void checkBatteryOptimisationState() { - PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); - if (powerManager.isIgnoringBatteryOptimizations(getPackageName())) { - // Battery optimisation is disabled - Log.d(TAG, "checkBatteryOptimisationState: Disabled"); - } else { - // Battery optimisation is enabled - Log.d(TAG, "checkBatteryOptimisationState: Enabled"); - if (SharedPreferences.getUserPrefs(this).getBoolean(SHOULD_SHOW_BATTERY_OPTIMISATION_ERROR, true)) { - new MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.label_battery_optimisation)) - .setMessage(getString(R.string.battery_optimisation_enabled_info)) - .setPositiveButton(getString(R.string.disable_battery_optimisation), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - startActivity(new Intent(MainActivity.this, ContainerActivity.class) - .putExtra(GENERAL_FRAGMENT_ID, DISABLE_BATTERY_OPTIMISATION_FRAGMENT)); - } - }) - .setNegativeButton(getString(R.string.label_do_not_show_again), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences.getUserPrefs(MainActivity.this).edit() - .putBoolean(SHOULD_SHOW_BATTERY_OPTIMISATION_ERROR, false) - .apply(); - dialog.dismiss(); - } - }) - .setNeutralButton(getString(R.string.action_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .show(); - } - } - } - - private void initializebottomNavigationViewBar() { - NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_nav_host_fragment); - NavController controller = navHostFragment.getNavController(); - controller.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() { - @Override - public void onDestinationChanged(@NotNull NavController navController, @NotNull NavDestination navDestination, @Nullable Bundle bundle) { - changeBanner(navDestination); - } - }); // working - -// NavigationUI.setupWithNavController(binding.bottomNavigationViewigationView, controller); // working - } - - - private void changeBanner(NavDestination navDestination) { - String destination = navDestination.getLabel().toString(); - Spannable banner; - - if (destination.equalsIgnoreCase(getString(R.string.home))) { - // Home Fragment - getSupportActionBar().setTitle(getString(R.string.app_name)); - } else if (destination.equalsIgnoreCase(getString(R.string.setup))) { - // Setup Fragment - getSupportActionBar().setTitle(getString(R.string.setup)); - } else if (destination.equalsIgnoreCase(getString(R.string.app_data_usage))) { - // App data usage Fragment - getSupportActionBar().setTitle(getString(R.string.app_data_usage)); - } else if (destination.equalsIgnoreCase(getString(R.string.network_diagnostics))) { - // Network diagnostics Fragment - getSupportActionBar().setTitle(getString(R.string.network_diagnostics)); - } else { - // Unknown Fragment - } - - } - - - @Override - protected void onStart() { - super.onStart(); - verifyAppVersion(); -// initializebottomNavigationViewBar(); - - if (!PreferenceManager.getDefaultSharedPreferences(MainActivity.this) - .getBoolean(ALARM_PERMISSION_DENIED, false)) { - AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (!alarmManager.canScheduleExactAlarms()) { - new MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.error_alarm_permission_denied)) - .setMessage(getString(R.string.error_alarm_permission_denied_dialog_summary)) - .setCancelable(false) - .setPositiveButton(getString(R.string.action_grant), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - dialog.dismiss(); - startActivity(intent); - } - }) - .setNegativeButton(getString(R.string.action_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } - }) - .setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - PreferenceManager.getDefaultSharedPreferences(MainActivity.this).edit() - .putBoolean(ALARM_PERMISSION_DENIED, true) - .apply(); - } - }) - .show(); - } - } - } - } - - @Override - public void onBackPressed() { - if (value == DATA_USAGE_SYSTEM) { - value = 0; - finish(); - } else { - super.onBackPressed(); - binding.bottomNavigationView.setSelectedItemId(R.id.bottom_menu_home); - } - } - - @Override - protected void onResume() { - super.onResume(); - try { - if (!isUsageAccessGranted(MainActivity.this)) { - startActivity(new Intent(this, SetupActivity.class) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) - .putExtra(SETUP_VALUE, USAGE_ACCESS_DISABLED)); - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - if (!isReadPhoneStateGranted(MainActivity.this)) { - startActivity(new Intent(this, SetupActivity.class) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) - .putExtra(SETUP_VALUE, READ_PHONE_STATE_DISABLED)); - } - } - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - try { - checkBatteryOptimisationState(); - } catch (Exception e) { - e.printStackTrace(); - } - - // Action bar title resets while changing theme in settings, setting current title -// NavController controller = Navigation.findNavController(this, R.id.main_nav_host_fragment); -// if (controller.getCurrentDestination().getId() == R.id.bottom_menu_settings) { -// getSupportActionBar().setTitle(getString(R.string.settings)); -// } - - } - - @Override - protected void onDestroy() { - Intent intent = new Intent(MainActivity.this, DataUsageWidget.class); - intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - int[] ids = AppWidgetManager.getInstance(MainActivity.this) - .getAppWidgetIds(new ComponentName(MainActivity.this, DataUsageWidget.class)); - AppWidgetManager.getInstance(this).updateAppWidget(ids, new RemoteViews(getPackageName(), R.layout.data_usage_widget)); - super.onDestroy(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.toolbar_menu, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(@NonNull @NotNull MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - if (value == DATA_USAGE_SYSTEM) { - value = 0; - finish(); - } - break; - - case R.id.toolbar_settings: - startActivity(new Intent(MainActivity.this, ContainerActivity.class) - .putExtra(GENERAL_FRAGMENT_ID, BOTTOM_NAVBAR_ITEM_SETTINGS)); -// startActivity(new Intent(MainActivity.this, MainActivity.class)); - break; - } - return super.onOptionsItemSelected(item); - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private void createNotificationChannel() { - NotificationChannel usageChannel = new NotificationChannel(DATA_USAGE_NOTIFICATION_CHANNEL_ID, - DATA_USAGE_NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_LOW); - NotificationChannel warningChannel = new NotificationChannel(DATA_USAGE_WARNING_CHANNEL_ID, DATA_USAGE_WARNING_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - NotificationChannel appWarningChannel = new NotificationChannel(APP_DATA_USAGE_WARNING_CHANNEL_ID, APP_DATA_USAGE_WARNING_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - NotificationChannel networkSignalChannel = new NotificationChannel(NETWORK_SIGNAL_CHANNEL_ID, NETWORK_SIGNAL_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - NotificationChannel otherChannel = new NotificationChannel(OTHER_NOTIFICATION_CHANNEL_ID, OTHER_NOTIFICATION_CHANNEL_NAME, - NotificationManager.IMPORTANCE_HIGH); - warningChannel.enableVibration(true); - warningChannel.enableLights(true); - appWarningChannel.enableVibration(true); - appWarningChannel.enableLights(true); - Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getPackageName() + "/" + R.raw.silent); - AudioAttributes attributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build(); -// networkSignalChannel.setSound(sound, attributes); - networkSignalChannel.setSound(Uri.EMPTY, null); - networkSignalChannel.setShowBadge(false); - networkSignalChannel.enableVibration(false); - networkSignalChannel.enableLights(false); - networkSignalChannel.setBypassDnd(true); - otherChannel.enableVibration(true); - otherChannel.enableLights(true); - - List channels = new ArrayList<>(); - channels.add(usageChannel); - channels.add(warningChannel); - channels.add(appWarningChannel); - channels.add(networkSignalChannel); - channels.add(otherChannel); - - - NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - if (PreferenceManager.getDefaultSharedPreferences(MainActivity.this) - .getBoolean(UPDATE_NOTIFICATION_CHANNEL, true)) { - notificationManager.deleteNotificationChannel("NetworkSignal.Notification"); - PreferenceManager.getDefaultSharedPreferences(MainActivity.this).edit() - .putBoolean(UPDATE_NOTIFICATION_CHANNEL, false) - .apply(); - } - notificationManager.createNotificationChannels(channels); - } - - protected void disableSelectedItem(int selectedItemIndex) { - for (int i = 0; i <= 3; i++) { - if (i == selectedItemIndex) { - binding.bottomNavigationView.getMenu().getItem(selectedItemIndex).setEnabled(false); - } else { - binding.bottomNavigationView.getMenu().getItem(i).setEnabled(true); - } - } - } - - private static class FetchApps extends AsyncTask { - private final Context mContext; - - public FetchApps(Context mContext) { - this.mContext = mContext; - } - - @Override - protected Object doInBackground(Object[] objects) { - Log.d(TAG, "doInBackground: checking applications"); - PackageManager packageManager = mContext.getPackageManager(); - List allApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); - List modelList = new ArrayList<>(); - AppDataUsageModel model = null; - DatabaseHandler databaseHandler = new DatabaseHandler(mContext); - - for (ApplicationInfo applicationInfo : allApps) { - if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 1) { - // System app - modelList.add(new AppDataUsageModel(packageManager.getApplicationLabel(applicationInfo).toString(), - applicationInfo.packageName, - applicationInfo.uid, - true)); - } else { - // User app - modelList.add(new AppDataUsageModel(packageManager.getApplicationLabel(applicationInfo).toString(), - applicationInfo.packageName, - applicationInfo.uid, - false)); - } - } - - for (int i = 0; i < modelList.size(); i++) { - model = new AppDataUsageModel(); - model.setAppName(modelList.get(i).getAppName()); - model.setPackageName(modelList.get(i).getPackageName()); - model.setUid(modelList.get(i).getUid()); - model.setIsSystemApp(modelList.get(i).isSystemApp()); - - databaseHandler.addData(model); - } - - return null; - } - } - - public static List getAppsList() { - return mAppsList; - } - - public static Boolean isDataLoading() { - return isDataLoading; - } - - public static void setIsDataLoading(Boolean isDataLoading) { - MainActivity.isDataLoading = isDataLoading; - } - - public static class LoadData extends AsyncTask { - private final Context mContext; - private final int session; - private final int type; - private int date; - - public LoadData(Context mContext, int session, int type) { - this.mContext = mContext; - this.session = session; - this.type = type; - } - - - @Override - protected void onPreExecute() { - super.onPreExecute(); - isDataLoading = true; - mUserAppsList.clear(); - mSystemAppsList.clear(); - Log.d(TAG, "onPreExecute: load data"); - } - - @Override - protected Object doInBackground(Object[] objects) { - Long sent = 0L, - systemSent = 0L, - received = 0L, - systemReceived = 0L, - totalSystemSent = 0L, - totalSystemReceived = 0L, - totalTetheringSent = 0L, - totalTetheringReceived = 0L, - totalDeletedAppsSent = 0L, - totalDeletedAppsReceived = 0L, - tetheringTotal = 0L, - deletedAppsTotal = 0L; - - date = PreferenceManager.getDefaultSharedPreferences(mContext).getInt(DATA_RESET_DATE, 1); - - DatabaseHandler handler = new DatabaseHandler(mContext); - List list = handler.getUsageList(); - AppDataUsageModel model = null; - - for (int i = 0; i < list.size(); i++) { - AppDataUsageModel currentData = list.get(i); - if (currentData.isSystemApp()) { - if (type == TYPE_MOBILE_DATA) { - try { - sent = getAppMobileDataUsage(mContext, currentData.getUid(), session)[0]; - received = getAppMobileDataUsage(mContext, currentData.getUid(), session)[1]; - totalSystemSent = totalSystemSent + sent; - totalSystemReceived = totalSystemReceived + received; - - if (sent > 0 || received > 0) { - model = new AppDataUsageModel(); - model.setAppName(currentData.getAppName()); - model.setPackageName(currentData.getPackageName()); - model.setUid(currentData.getUid()); - model.setSentMobile(sent); - model.setReceivedMobile(received); - model.setSession(session); - model.setType(type); - - Long total = sent + received; - Long deviceTotal = getDeviceMobileDataUsage(mContext, session, date)[2]; - - // multiplied by 2 just to increase progress a bit. - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - mSystemAppsList.add(model); - } - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else { - try { - sent = getAppWifiDataUsage(mContext, currentData.getUid(), session)[0]; - received = getAppWifiDataUsage(mContext, currentData.getUid(), session)[1]; - totalSystemSent = totalSystemSent + sent; - totalSystemReceived = totalSystemReceived + received; - - if (sent > 0 || received > 0) { - model = new AppDataUsageModel(); - model.setAppName(currentData.getAppName()); - model.setPackageName(currentData.getPackageName()); - model.setUid(currentData.getUid()); - model.setSentMobile(sent); - model.setReceivedMobile(received); - model.setSession(session); - model.setType(type); - - Long total = sent + received; - Long deviceTotal = getDeviceWifiDataUsage(mContext, session)[2]; - - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - mSystemAppsList.add(model); - } - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } else { - if (isAppInstalled(mContext, currentData.getPackageName())) { - if (type == TYPE_MOBILE_DATA) { - try { - sent = getAppMobileDataUsage(mContext, currentData.getUid(), session)[0]; - received = getAppMobileDataUsage(mContext, currentData.getUid(), session)[1]; - - if (sent > 0 || received > 0) { - model = new AppDataUsageModel(); - model.setAppName(currentData.getAppName()); - model.setPackageName(currentData.getPackageName()); - model.setUid(currentData.getUid()); - model.setSentMobile(sent); - model.setReceivedMobile(received); - model.setSession(session); - model.setType(type); - - Long total = sent + received; - Long deviceTotal = getDeviceMobileDataUsage(mContext, session, date)[2]; - - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - mUserAppsList.add(model); - } - - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else { - try { - sent = getAppWifiDataUsage(mContext, currentData.getUid(), session)[0]; - received = getAppWifiDataUsage(mContext, currentData.getUid(), session)[1]; - - if (sent > 0 || received > 0) { - model = new AppDataUsageModel(); - model.setAppName(currentData.getAppName()); - model.setPackageName(currentData.getPackageName()); - model.setUid(currentData.getUid()); - model.setSentMobile(sent); - model.setReceivedMobile(received); - model.setSession(session); - model.setType(type); - - Long total = sent + received; - Long deviceTotal = getDeviceWifiDataUsage(mContext, session)[2]; - - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - mUserAppsList.add(model); - } - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - } - } - - model = new AppDataUsageModel(); - model.setAppName(mContext.getString(R.string.label_system_apps)); - model.setPackageName(mContext.getString(R.string.package_system)); - model.setSentMobile(totalSystemSent); - model.setReceivedMobile(totalSystemReceived); - model.setSession(session); - model.setType(type); - - Long total = totalSystemSent + totalSystemReceived; - - Long deviceTotal = null; - if (type == TYPE_MOBILE_DATA) { - try { - deviceTotal = getDeviceMobileDataUsage(mContext, session, date)[2]; - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } else { - try { - deviceTotal = getDeviceWifiDataUsage(mContext, session)[2]; - Double progress = ((total.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int progressInt; - if (progress != null) { - progressInt = progress.intValue(); - } else { - progressInt = 0; - } - model.setProgress(progressInt); - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - if (deviceTotal > 0) { - mUserAppsList.add(model); - } - - try { - if (type == TYPE_MOBILE_DATA) { - totalTetheringSent = getTetheringDataUsage(mContext, session)[0]; - totalTetheringReceived = getTetheringDataUsage(mContext, session)[1]; - tetheringTotal = totalTetheringSent + totalTetheringReceived; - - Double tetheringProgress = ((tetheringTotal.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int tetheringProgressInt; - if (tetheringProgress != null) { - tetheringProgressInt = tetheringProgress.intValue(); - } else { - tetheringProgressInt = 0; - } - - model = new AppDataUsageModel(); - model.setAppName(mContext.getString(R.string.label_tethering)); - model.setPackageName(mContext.getString(R.string.package_tethering)); - model.setSentMobile(totalTetheringSent); - model.setReceivedMobile(totalTetheringReceived); - model.setSession(session); - model.setType(type); - model.setProgress(tetheringProgressInt); - - if (tetheringTotal > 0) { - mUserAppsList.add(model); - } - - - totalDeletedAppsSent = getDeletedAppsMobileDataUsage(mContext, session)[0]; - totalDeletedAppsReceived = getDeletedAppsMobileDataUsage(mContext, session)[1]; - } else { - totalDeletedAppsSent = getDeletedAppsWifiDataUsage(mContext, session)[0]; - totalDeletedAppsReceived = getDeletedAppsWifiDataUsage(mContext, session)[1]; - } - deletedAppsTotal = totalDeletedAppsSent + totalDeletedAppsReceived; - - Double deletedProgress = ((deletedAppsTotal.doubleValue() / deviceTotal.doubleValue()) * 100) * 2; - int deletedProgressInt; - if (deletedProgress != null) { - deletedProgressInt = deletedProgress.intValue(); - } else { - deletedProgressInt = 0; - } - - model = new AppDataUsageModel(); - model.setAppName(mContext.getString(R.string.label_removed)); - model.setPackageName(mContext.getString(R.string.package_removed)); - model.setSentMobile(totalDeletedAppsSent); - model.setReceivedMobile(totalDeletedAppsReceived); - model.setSession(session); - model.setType(type); - model.setProgress(deletedProgressInt); - - if (deletedAppsTotal > 0) { - mUserAppsList.add(model); - } - - Collections.sort(mUserAppsList, new Comparator() { - @Override - public int compare(AppDataUsageModel o1, AppDataUsageModel o2) { - o1.setMobileTotal((o1.getSentMobile() + o1.getReceivedMobile()) / 1024f); - o2.setMobileTotal((o2.getSentMobile() + o2.getReceivedMobile()) / 1024f); - return o1.getMobileTotal().compareTo(o2.getMobileTotal()); - } - }); - - Collections.reverse(mUserAppsList); - - Collections.sort(mSystemAppsList, new Comparator() { - @Override - public int compare(AppDataUsageModel o1, AppDataUsageModel o2) { - o1.setMobileTotal((o1.getSentMobile() + o1.getReceivedMobile()) / 1024f); - o2.setMobileTotal((o2.getSentMobile() + o2.getReceivedMobile()) / 1024f); - return o1.getMobileTotal().compareTo(o2.getMobileTotal()); - } - }); - - Collections.reverse(mSystemAppsList); - - - } catch (ParseException e) { - e.printStackTrace(); - } catch (RemoteException e) { - e.printStackTrace(); - } - - return null; - } - - @Override - protected void onPostExecute(Object o) { - super.onPostExecute(o); - isDataLoading = false; - if (getAppContext() != null) { - onDataLoaded(getAppContext()); - } else { - - } - MainActivity.FetchApps fetchApps = new MainActivity.FetchApps(mContext); - fetchApps.execute(); - } - - } - - private void verifyAppVersion() { - String updateVersion = SharedPreferences.getAppPrefs(MainActivity.this) - .getString(UPDATE_VERSION, BuildConfig.VERSION_NAME); - if (updateVersion.equalsIgnoreCase(BuildConfig.VERSION_NAME)) { - SharedPreferences.getAppPrefs(MainActivity.this) - .edit().remove(UPDATE_VERSION).apply(); - } - } - - public static void setTheme(Activity activity) { - String theme = PreferenceManager.getDefaultSharedPreferences(activity).getString(APP_THEME, "system"); - switch (theme) { - case "dark": - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - break; - - case "light": - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - break; - - case "system": - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - break; - - default: - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - break; - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.kt b/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.kt new file mode 100644 index 00000000..a9a6402f --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/ui/activities/MainActivity.kt @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2021 Dr.NooB + * + * This file is a part of Data Monitor + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drnoob.datamonitor.ui.activities + +import android.Manifest +import android.app.AlarmManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.ContentResolver +import android.content.Intent +import android.content.pm.PackageManager +import android.media.AudioAttributes +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.RemoteViews +import androidx.activity.OnBackPressedCallback +import androidx.activity.viewModels +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavDestination +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.NavigationUI.setupWithNavController +import androidx.preference.PreferenceManager +import com.drnoob.datamonitor.BuildConfig +import com.drnoob.datamonitor.Common +import com.drnoob.datamonitor.R +import com.drnoob.datamonitor.Widget.DataUsageWidget +import com.drnoob.datamonitor.adapters.data.DataUsageViewModel +import com.drnoob.datamonitor.adapters.data.DataUsageViewModelFactory +import com.drnoob.datamonitor.core.Values +import com.drnoob.datamonitor.core.task.DatabaseHandler +import com.drnoob.datamonitor.databinding.ActivityMainBinding +import com.drnoob.datamonitor.utils.CrashReporter +import com.drnoob.datamonitor.utils.SharedPreferences +import com.drnoob.datamonitor.utils.helpers.UsageDataHelperImpl +import com.drnoob.datamonitor.utils.helpers.setTheme +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.elevation.SurfaceColors + +class MainActivity : AppCompatActivity() { + + lateinit var binding: ActivityMainBinding + private val viewModel: DataUsageViewModel by viewModels { + DataUsageViewModelFactory(UsageDataHelperImpl(this)) + } + + private val onBackPressedCallback: OnBackPressedCallback = + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + if (navController.currentDestination?.id == R.id.bottom_menu_home) { + finish() + } else { + binding.bottomNavigationView.selectedItemId = R.id.bottom_menu_home + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(this) + Thread.setDefaultUncaughtExceptionHandler(CrashReporter(this@MainActivity)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if (!Common.isReadPhoneStateGranted(this@MainActivity)) { + startActivity( + Intent(this, SetupActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + .putExtra(Values.SETUP_VALUE, Values.READ_PHONE_STATE_DISABLED) + ) + finish() + } + } + super.onCreate(savedInstanceState) + val languageCode = + SharedPreferences.getUserPrefs(this).getString(Values.APP_LANGUAGE_CODE, "null") + val countryCode = + SharedPreferences.getUserPrefs(this).getString(Values.APP_COUNTRY_CODE, "") + if (languageCode == "null") { + Common.setLanguage(this, "en", countryCode) + } else { + Common.setLanguage(this, languageCode, countryCode) + } + try { + Common.refreshService(this) + } catch (e: Exception) { + e.printStackTrace() + } + try { + if (Common.isUsageAccessGranted(this@MainActivity)) { + binding = ActivityMainBinding.inflate( + layoutInflater + ) + setTheme(R.style.Theme_DataMonitor) + setContentView(binding.root) + setSupportActionBar(binding.mainToolbar) + binding.mainToolbar.setBackgroundColor(SurfaceColors.SURFACE_2.getColor(this)) + window.statusBarColor = SurfaceColors.SURFACE_2.getColor(this) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + window.navigationBarColor = SurfaceColors.SURFACE_2.getColor(this) + } + SharedPreferences.getUserPrefs(this).edit().putBoolean(Values.SETUP_COMPLETED, true) + .apply() + if (binding.bottomNavigationView.selectedItemId == R.id.bottom_menu_home) { + supportActionBar!!.title = getString(R.string.app_name) + } + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment? + val controller = navHostFragment!!.navController + controller.addOnDestinationChangedListener { _, navDestination, _ -> + changeBanner( + navDestination + ) + } // working + setupWithNavController(binding.bottomNavigationView, controller) + val databaseHandler = DatabaseHandler(this@MainActivity) + if (databaseHandler.usageList == null || databaseHandler.usageList.size <= 0) { + viewModel.fetchApps() + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createNotificationChannel() + } + value = intent.getIntExtra(Values.DATA_USAGE_VALUE, 0) + if (value == Values.DATA_USAGE_SYSTEM) { + binding.bottomNavigationView.visibility = View.GONE + binding.bottomNavigationView.selectedItemId = R.id.bottom_menu_app_data_usage + supportActionBar!!.setTitle(R.string.system_data_usage) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + supportActionBar!!.setHomeAsUpIndicator(R.drawable.ic_arrow) + controller.navigate(R.id.system_data_usage) // >> working + } +// else if (!viewModel.isDataLoading) { +// viewModel.isDataLoading = true +// viewModel.loadData(Values.SESSION_TODAY, Values.TYPE_MOBILE_DATA) +// viewModel.isDataLoading = false +// viewModel.fetchApps() +// if (AppDataUsageFragment.appContext != null) { +// viewModel.callOnDataLoaded.value = true +// } +// } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val notificationManager = getSystemService( + NotificationManager::class.java + ) + if (!notificationManager.areNotificationsEnabled()) { + requestPermissions( + arrayOf(Manifest.permission.POST_NOTIFICATIONS), + Values.REQUEST_POST_NOTIFICATIONS + ) + } + } + } else { + onResume() + } + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } catch (e: Exception) { + e.printStackTrace() + } + + this@MainActivity.onBackPressedDispatcher + .addCallback(this, onBackPressedCallback) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == Values.REQUEST_POST_NOTIFICATIONS) { + if (grantResults.isNotEmpty() && grantResults[0] != PackageManager.PERMISSION_GRANTED) { + MaterialAlertDialogBuilder(this@MainActivity) + .setTitle(R.string.label_permission_denied) + .setMessage(R.string.notification_permission_denied_body) + .setPositiveButton(R.string.action_grant) { _, _ -> + val intent = Intent() + intent.action = "android.settings.APP_NOTIFICATION_SETTINGS" + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra("app_package", packageName) + intent.putExtra("app_uid", applicationInfo.uid) + intent.putExtra("android.provider.extra.APP_PACKAGE", packageName) + startActivity(intent) + } + .setNegativeButton(R.string.action_cancel, null) + .show() + } + } + } + + private fun checkBatteryOptimisationState() { + val powerManager = getSystemService(POWER_SERVICE) as PowerManager + if (powerManager.isIgnoringBatteryOptimizations(packageName)) { + // Battery optimisation is disabled + Log.d(TAG, "checkBatteryOptimisationState: Disabled") + } else { + // Battery optimisation is enabled + Log.d(TAG, "checkBatteryOptimisationState: Enabled") + if (SharedPreferences.getUserPrefs(this) + .getBoolean(Values.SHOULD_SHOW_BATTERY_OPTIMISATION_ERROR, true) + ) { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.label_battery_optimisation)) + .setMessage(getString(R.string.battery_optimisation_enabled_info)) + .setPositiveButton(getString(R.string.disable_battery_optimisation)) { dialog, _ -> + dialog.dismiss() + startActivity( + Intent(this@MainActivity, ContainerActivity::class.java) + .putExtra( + Values.GENERAL_FRAGMENT_ID, + Values.DISABLE_BATTERY_OPTIMISATION_FRAGMENT + ) + ) + } + .setNegativeButton(getString(R.string.label_do_not_show_again)) { dialog, _ -> + SharedPreferences.getUserPrefs(this@MainActivity).edit() + .putBoolean(Values.SHOULD_SHOW_BATTERY_OPTIMISATION_ERROR, false) + .apply() + dialog.dismiss() + } + .setNeutralButton(getString(R.string.action_cancel)) { dialog, _ -> dialog.dismiss() } + .show() + } + } + } + + private fun initializeBottomNavigationViewBar() { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment? + val controller = navHostFragment!!.navController + controller.addOnDestinationChangedListener { _, navDestination, _ -> + changeBanner( + navDestination + ) + } // working + +// NavigationUI.setupWithNavController(binding.bottomNavigationView, controller); // working + } + + private fun changeBanner(navDestination: NavDestination) { + val destination = navDestination.label.toString() + if (destination.equals(getString(R.string.home), ignoreCase = true)) { + // Home Fragment + supportActionBar!!.title = getString(R.string.app_name) + } else if (destination.equals(getString(R.string.setup), ignoreCase = true)) { + // Setup Fragment + supportActionBar!!.title = getString(R.string.setup) + } else if (destination.equals(getString(R.string.app_data_usage), ignoreCase = true)) { + // App data usage Fragment + supportActionBar!!.title = getString(R.string.app_data_usage) + } else if (destination.equals(getString(R.string.network_diagnostics), ignoreCase = true)) { + // Network diagnostics Fragment + supportActionBar!!.title = getString(R.string.network_diagnostics) + } else { + // Unknown Fragment + } + } + + override fun onStart() { + super.onStart() + verifyAppVersion() + // initializeBottomNavigationViewBar(); + if (!PreferenceManager.getDefaultSharedPreferences(this@MainActivity) + .getBoolean(Values.ALARM_PERMISSION_DENIED, false) + ) { + val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (!alarmManager.canScheduleExactAlarms()) { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.error_alarm_permission_denied)) + .setMessage(getString(R.string.error_alarm_permission_denied_dialog_summary)) + .setCancelable(false) + .setPositiveButton(getString(R.string.action_grant)) { dialog, _ -> + val intent = Intent() + intent.action = Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + dialog.dismiss() + startActivity(intent) + } + .setNegativeButton(getString(R.string.action_cancel)) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + PreferenceManager.getDefaultSharedPreferences(this@MainActivity).edit() + .putBoolean(Values.ALARM_PERMISSION_DENIED, true) + .apply() + } + .show() + } + } + } + } + + override fun onResume() { + super.onResume() + try { + if (!Common.isUsageAccessGranted(this@MainActivity)) { + startActivity( + Intent(this, SetupActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + .putExtra(Values.SETUP_VALUE, Values.USAGE_ACCESS_DISABLED) + ) + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + if (!Common.isReadPhoneStateGranted(this@MainActivity)) { + startActivity( + Intent(this, SetupActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + .putExtra(Values.SETUP_VALUE, Values.READ_PHONE_STATE_DISABLED) + ) + } + } + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + try { + checkBatteryOptimisationState() + } catch (e: Exception) { + e.printStackTrace() + } + } + + override fun onDestroy() { + val intent = Intent(this@MainActivity, DataUsageWidget::class.java) + intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + val ids = AppWidgetManager.getInstance(this@MainActivity) + .getAppWidgetIds(ComponentName(this@MainActivity, DataUsageWidget::class.java)) + AppWidgetManager.getInstance(this) + .updateAppWidget(ids, RemoteViews(packageName, R.layout.data_usage_widget)) + super.onDestroy() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.toolbar_menu, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> if (value == Values.DATA_USAGE_SYSTEM) { + value = 0 + finish() + } + + R.id.toolbar_settings -> startActivity( + Intent(this@MainActivity, ContainerActivity::class.java) + .putExtra(Values.GENERAL_FRAGMENT_ID, Values.BOTTOM_NAVBAR_ITEM_SETTINGS) + ) + } + return super.onOptionsItemSelected(item) + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private fun createNotificationChannel() { + val usageChannel = NotificationChannel( + Values.DATA_USAGE_NOTIFICATION_CHANNEL_ID, + Values.DATA_USAGE_NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW + ) + val warningChannel = NotificationChannel( + Values.DATA_USAGE_WARNING_CHANNEL_ID, Values.DATA_USAGE_WARNING_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ) + val appWarningChannel = NotificationChannel( + Values.APP_DATA_USAGE_WARNING_CHANNEL_ID, Values.APP_DATA_USAGE_WARNING_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ) + val networkSignalChannel = NotificationChannel( + Values.NETWORK_SIGNAL_CHANNEL_ID, Values.NETWORK_SIGNAL_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ) + val otherChannel = NotificationChannel( + Values.OTHER_NOTIFICATION_CHANNEL_ID, Values.OTHER_NOTIFICATION_CHANNEL_NAME, + NotificationManager.IMPORTANCE_HIGH + ) + warningChannel.enableVibration(true) + warningChannel.enableLights(true) + appWarningChannel.enableVibration(true) + appWarningChannel.enableLights(true) + val sound = + Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + packageName + "/" + R.raw.silent) + val attributes = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build() + // networkSignalChannel.setSound(sound, attributes); + networkSignalChannel.setSound(Uri.EMPTY, null) + networkSignalChannel.setShowBadge(false) + networkSignalChannel.enableVibration(false) + networkSignalChannel.enableLights(false) + networkSignalChannel.setBypassDnd(true) + otherChannel.enableVibration(true) + otherChannel.enableLights(true) + val channels: MutableList = ArrayList() + channels.add(usageChannel) + channels.add(warningChannel) + channels.add(appWarningChannel) + channels.add(networkSignalChannel) + channels.add(otherChannel) + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + if (PreferenceManager.getDefaultSharedPreferences(this@MainActivity) + .getBoolean(Values.UPDATE_NOTIFICATION_CHANNEL, true) + ) { + notificationManager.deleteNotificationChannel("NetworkSignal.Notification") + PreferenceManager.getDefaultSharedPreferences(this@MainActivity).edit() + .putBoolean(Values.UPDATE_NOTIFICATION_CHANNEL, false) + .apply() + } + notificationManager.createNotificationChannels(channels) + } + + private fun verifyAppVersion() { + val updateVersion = SharedPreferences.getAppPrefs(this@MainActivity) + .getString(Values.UPDATE_VERSION, BuildConfig.VERSION_NAME) + if (updateVersion.equals(BuildConfig.VERSION_NAME, ignoreCase = true)) { + SharedPreferences.getAppPrefs(this@MainActivity) + .edit().remove(Values.UPDATE_VERSION).apply() + } + } + + + companion object { + private val TAG = MainActivity::class.java.simpleName + var value = 0 + var refreshAppDataUsage = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.java b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.java deleted file mode 100644 index 5a49b85d..00000000 --- a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.java +++ /dev/null @@ -1,668 +0,0 @@ -/* - * Copyright (C) 2021 Dr.NooB - * - * This file is a part of Data Monitor - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.drnoob.datamonitor.ui.fragments; - -import static com.drnoob.datamonitor.core.Values.DAILY_DATA_HOME_ACTION; -import static com.drnoob.datamonitor.core.Values.DATA_RESET; -import static com.drnoob.datamonitor.core.Values.DATA_RESET_CUSTOM; -import static com.drnoob.datamonitor.core.Values.DATA_RESET_DATE; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_SESSION; -import static com.drnoob.datamonitor.core.Values.DATA_USAGE_TYPE; -import static com.drnoob.datamonitor.core.Values.EXTRA_IS_WEEK_DAY_VIEW; -import static com.drnoob.datamonitor.core.Values.EXTRA_WEEK_DAY; -import static com.drnoob.datamonitor.core.Values.SESSION_ALL_TIME; -import static com.drnoob.datamonitor.core.Values.SESSION_CUSTOM; -import static com.drnoob.datamonitor.core.Values.SESSION_LAST_MONTH; -import static com.drnoob.datamonitor.core.Values.SESSION_THIS_MONTH; -import static com.drnoob.datamonitor.core.Values.SESSION_THIS_YEAR; -import static com.drnoob.datamonitor.core.Values.SESSION_TODAY; -import static com.drnoob.datamonitor.core.Values.SESSION_YESTERDAY; -import static com.drnoob.datamonitor.core.Values.TYPE_MOBILE_DATA; -import static com.drnoob.datamonitor.core.Values.TYPE_WIFI; -import static com.drnoob.datamonitor.ui.activities.MainActivity.getRefreshAppDataUsage; -import static com.drnoob.datamonitor.ui.activities.MainActivity.isDataLoading; -import static com.drnoob.datamonitor.ui.activities.MainActivity.mSystemAppsList; -import static com.drnoob.datamonitor.ui.activities.MainActivity.mUserAppsList; -import static com.drnoob.datamonitor.ui.activities.MainActivity.setRefreshAppDataUsage; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.RemoteException; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import com.drnoob.datamonitor.R; -import com.drnoob.datamonitor.adapters.AppDataUsageAdapter; -import com.drnoob.datamonitor.adapters.data.AppDataUsageModel; -import com.drnoob.datamonitor.adapters.data.FragmentViewModel; -import com.drnoob.datamonitor.ui.activities.MainActivity; -import com.drnoob.datamonitor.utils.NetworkStatsHelper; -import com.drnoob.datamonitor.utils.VibrationUtils; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.chip.Chip; -import com.google.android.material.chip.ChipGroup; -import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.List; - -public class AppDataUsageFragment extends Fragment { - private static final String TAG = AppDataUsageFragment.class.getSimpleName(); - public static RecyclerView mAppsView; - public static AppDataUsageAdapter mAdapter; - public static List mList = new ArrayList<>(); - public static List mSystemList = new ArrayList<>(); - private static LinearLayout mLoading; - private static Context mContext; - private static Activity mActivity; - private static SwipeRefreshLayout mDataRefresh; - private static TextView mEmptyList; - private FragmentViewModel viewModel; - private ExtendedFloatingActionButton mFilter; - private static TextView mTotalUsage; - private static boolean fromHome; - private static boolean isWeekDayView; - private static String totalDataUsage; - private static int selectedSession, selectedType; - - public AppDataUsageFragment() { - - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - mContext = context; - mActivity = getActivity(); - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_app_data_usage, container, false); - - viewModel = new ViewModelProvider(getActivity()).get(FragmentViewModel.class); - - mAppsView = view.findViewById(R.id.app_data_usage_recycler); - mLoading = view.findViewById(R.id.layout_list_loading); - mDataRefresh = view.findViewById(R.id.refresh_data_usage); - mEmptyList = view.findViewById(R.id.empty_list); - mTotalUsage = view.findViewById(R.id.current_session_total); - mFilter = view.findViewById(R.id.filter_app_usage); - - mAdapter = new AppDataUsageAdapter(mList, mContext); - mAdapter.setActivity(getActivity()); - - int session = getActivity().getIntent().getIntExtra(DATA_USAGE_SESSION, SESSION_TODAY); - int type = getActivity().getIntent().getIntExtra(DATA_USAGE_TYPE, TYPE_MOBILE_DATA); - fromHome = getActivity().getIntent().getBooleanExtra(DAILY_DATA_HOME_ACTION, false); - isWeekDayView = getActivity().getIntent().getBooleanExtra(EXTRA_IS_WEEK_DAY_VIEW, false); - - if (getActivity().getIntent() != null) { - if (fromHome) { - type = getActivity().getIntent().getIntExtra(DATA_USAGE_TYPE, TYPE_MOBILE_DATA); - setType(type); - refreshData(); -// mTopBar.setVisibility(View.GONE); - mFilter.setVisibility(View.GONE); - mAppsView.setPadding(0, 130, 0, 0); - } - else if (isWeekDayView) { - String weekDay = getActivity().getIntent().getStringExtra(EXTRA_WEEK_DAY); - } - } - - setSession(session); - setType(type); - mTotalUsage.setText("..."); - - Log.e(TAG, "onCreateView: " + getRefreshAppDataUsage() ); - if (getRefreshAppDataUsage()) { - refreshData(); - } - - mList = mUserAppsList; - mSystemList = mSystemAppsList; - - if (!MainActivity.isDataLoading()) { - mLoading.setAlpha(0.0f); - mAppsView.setAlpha(1.0f); - onDataLoaded(getContext()); - } - else { - mDataRefresh.setRefreshing(true); - } - - mFilter.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (isDataLoading()) { - return; - } - BottomSheetDialog dialog = new BottomSheetDialog(getContext(), R.style.BottomSheet); - View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.layout_app_usage_filter, null); - - ChipGroup sessionGroup = dialogView.findViewById(R.id.session_group); - ChipGroup typeGroup = dialogView.findViewById(R.id.type_group); - - ConstraintLayout footer = dialogView.findViewById(R.id.footer); - TextView cancel = footer.findViewById(R.id.cancel); - TextView ok = footer.findViewById(R.id.ok); - - Chip sessionCurrentPlan = sessionGroup.findViewById(R.id.session_current_plan); - - if (PreferenceManager.getDefaultSharedPreferences(getContext()) - .getString(DATA_RESET, "null") - .equals(DATA_RESET_CUSTOM)) { - sessionCurrentPlan.setVisibility(View.VISIBLE); - } - else { - sessionCurrentPlan.setVisibility(View.GONE); - } - - sessionGroup.setOnCheckedStateChangeListener(new ChipGroup.OnCheckedStateChangeListener() { - @Override - public void onCheckedChanged(@NonNull ChipGroup group, @NonNull List checkedIds) { - if (!PreferenceManager.getDefaultSharedPreferences(getContext()) - .getBoolean("disable_haptics", false)) { - VibrationUtils.hapticMinor(getContext()); - } - } - }); - - typeGroup.setOnCheckedStateChangeListener(new ChipGroup.OnCheckedStateChangeListener() { - @Override - public void onCheckedChanged(@NonNull ChipGroup group, @NonNull List checkedIds) { - if (!PreferenceManager.getDefaultSharedPreferences(getContext()) - .getBoolean("disable_haptics", false)) { - VibrationUtils.hapticMinor(getContext()); - } - } - }); - - switch (getSession()) { - case SESSION_TODAY: - sessionGroup.check(R.id.session_today); - break; - - case SESSION_YESTERDAY: - sessionGroup.check(R.id.session_yesterday); - break; - - case SESSION_THIS_MONTH: - sessionGroup.check(R.id.session_this_month); - break; - - case SESSION_LAST_MONTH: - sessionGroup.check(R.id.session_last_month); - break; - - case SESSION_THIS_YEAR: - sessionGroup.check(R.id.session_this_year); - break; - - case SESSION_ALL_TIME: - sessionGroup.check(R.id.session_all_time); - break; - - case SESSION_CUSTOM: - sessionGroup.check(R.id.session_current_plan); - break; - - } - - switch (getType()) { - case TYPE_MOBILE_DATA: - typeGroup.check(R.id.type_mobile); - break; - - case TYPE_WIFI: - typeGroup.check(R.id.type_wifi); - break; - } - - cancel.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dialog.dismiss(); - } - }); - - ok.setOnClickListener(new View.OnClickListener() { - @SuppressLint("NonConstantResourceId") - @Override - public void onClick(View v) { - switch (sessionGroup.getCheckedChipId()) { - case R.id.session_yesterday: - selectedSession = SESSION_YESTERDAY; - break; - - case R.id.session_this_month: - selectedSession = SESSION_THIS_MONTH; - break; - - case R.id.session_last_month: - selectedSession = SESSION_LAST_MONTH; - break; - - case R.id.session_this_year: - selectedSession = SESSION_THIS_YEAR; - break; - - case R.id.session_all_time: - selectedSession = SESSION_ALL_TIME; - break; - - case R.id.session_current_plan: - selectedSession = SESSION_CUSTOM; - break; - case R.id.session_today: - - default: - selectedSession = SESSION_TODAY; - break; - } - - switch (typeGroup.getCheckedChipId()) { - case R.id.type_wifi: - selectedType = TYPE_WIFI; - break; - - case R.id.type_mobile: - - default: - selectedType = TYPE_MOBILE_DATA; - break; - } - - if (!MainActivity.isDataLoading()) { - refreshData(); - } - dialog.dismiss(); - } - }); - - dialog.setContentView(dialogView); - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialogInterface) { - BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; - FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); - BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED); - } - }); - dialog.show(); - - } - }); - - mDataRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - refreshData(); - } - }); - -// mSession.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// if (isDataLoading()) { -// return; -// } -// BottomSheetDialog dialog = new BottomSheetDialog(getContext(), R.style.BottomSheet); -// View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.data_usage_session, null); -// -// RadioGroup sessions = dialogView.findViewById(R.id.session_group); -// ConstraintLayout footer = dialogView.findViewById(R.id.footer); -// TextView cancel = footer.findViewById(R.id.cancel); -// TextView ok = footer.findViewById(R.id.ok); -// -// switch (getSession(getContext())) { -// case SESSION_TODAY: -// sessions.check(R.id.session_today); -// break; -// -// case SESSION_YESTERDAY: -// sessions.check(R.id.session_yesterday); -// break; -// -// case SESSION_THIS_MONTH: -// sessions.check(R.id.session_this_month); -// break; -// -// case SESSION_LAST_MONTH: -// sessions.check(R.id.session_last_month); -// break; -// -// case SESSION_THIS_YEAR: -// sessions.check(R.id.session_this_year); -// break; -// -// case SESSION_ALL_TIME: -// sessions.check(R.id.session_all_time); -// break; -// -// } -// -// cancel.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// dialog.dismiss(); -// } -// }); -// -// ok.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// String session = null; -// switch (sessions.getCheckedRadioButtonId()) { -// case R.id.session_today: -// session = getString(R.string.label_today); -// break; -// -// case R.id.session_yesterday: -// session = getString(R.string.label_yesterday); -// break; -// -// case R.id.session_this_month: -// session = getString(R.string.label_this_month); -// break; -// -// case R.id.session_last_month: -// session = getString(R.string.label_last_month); -// break; -// -// case R.id.session_this_year: -// session = getString(R.string.label_this_year); -// break; -// -// case R.id.session_all_time: -// session = getString(R.string.label_all_time); -// break; -// } -// mSession.setText(session); -// if (!MainActivity.isDataLoading()) { -// refreshData(); -// } -// dialog.dismiss(); -// } -// }); -// -// dialog.setContentView(dialogView); -// dialog.setOnShowListener(new DialogInterface.OnShowListener() { -// @Override -// public void onShow(DialogInterface dialogInterface) { -// BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; -// FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); -// BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED); -// } -// }); -// dialog.show(); -// } -// }); -// -// mType.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// if (isDataLoading()) { -// return; -// } -// BottomSheetDialog dialog = new BottomSheetDialog(getContext(), R.style.BottomSheet); -// View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.data_usage_type, null); -// -// RadioGroup types = dialogView.findViewById(R.id.type_group); -// ConstraintLayout footer = dialogView.findViewById(R.id.footer); -// TextView cancel = footer.findViewById(R.id.cancel); -// TextView ok = footer.findViewById(R.id.ok); -// -// switch (getType(getContext())) { -// case TYPE_MOBILE_DATA: -// types.check(R.id.type_mobile); -// break; -// -// case TYPE_WIFI: -// types.check(R.id.type_wifi); -// break; -// } -// -// cancel.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// dialog.dismiss(); -// } -// }); -// -// ok.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// String type = null; -// switch (types.getCheckedRadioButtonId()) { -// case R.id.type_mobile: -// type = getString(R.string.label_mobile_data); -// break; -// -// case R.id.type_wifi: -// type = getString(R.string.label_wifi); -// break; -// } -// mType.setText(type); -// -// if (!MainActivity.isDataLoading()) { -// refreshData(); -// } -// dialog.dismiss(); -// } -// }); -// -// dialog.setContentView(dialogView); -// dialog.setOnShowListener(new DialogInterface.OnShowListener() { -// @Override -// public void onShow(DialogInterface dialogInterface) { -// BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; -// FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); -// BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED); -// } -// }); -// dialog.show(); -// } -// }); - - /* - Shrink or expand the FAB according to user scroll - */ - mAppsView.setOnScrollChangeListener(new View.OnScrollChangeListener() { - @Override - public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { - if (oldScrollY < -15 && mFilter.isExtended()) { - mFilter.shrink(); - } - else if (oldScrollY > 15 && !mFilter.isExtended()) { - mFilter.extend(); - } - else if (mAppsView.computeVerticalScrollOffset() == 0 && !mFilter.isExtended()) { - mFilter.extend(); - } - } - }); - - - return view; - } - - @Override - public void onStart() { - super.onStart(); - if (mList.size() > 0) { - setSession(mList.get(0).getSession()); - setType(mList.get(0).getType()); - } - else { - if (viewModel.getCurrentSession().getValue() != null && - viewModel.getCurrentType().getValue() != null) { - setSession(viewModel.getCurrentSession().getValue()); - setType(viewModel.getCurrentType().getValue()); - } - - } - if (!PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getString(DATA_RESET, "null") - .equals(DATA_RESET_CUSTOM)) { - if (getSession() == SESSION_CUSTOM) { - setSession(SESSION_TODAY); - refreshData(); - } - } - } - - @Override - public void onPause() { - viewModel.setCurrentSession(getSession()); - viewModel.setCurrentType(getType()); - super.onPause(); - } - - public static Context getAppContext() { - return mContext; - } - - private static void refreshData() { - mLoading.animate().alpha(1.0f); - mAppsView.animate().alpha(0.0f); - mEmptyList.animate().alpha(0.0f); - mDataRefresh.setRefreshing(true); - mAppsView.removeAllViews(); - mList.clear(); - mSystemList.clear(); - totalDataUsage = ""; - mTotalUsage.setText("..."); - - - MainActivity.LoadData loadData = new MainActivity.LoadData(mContext, getSession(), - getType()); - if (!isDataLoading()) { - loadData.execute(); - } - - } - - public static void onDataLoaded(Context context) { - try { - if (totalDataUsage == null || totalDataUsage.isEmpty()) { - totalDataUsage = getTotalDataUsage(context); - } - mTotalUsage.setText(context.getString(R.string.total_usage, totalDataUsage)); - - } - catch (ParseException | RemoteException e) { - e.printStackTrace(); - } - Log.d(TAG, "onDataLoaded: " + mSystemList.size() + " system"); - Log.d(TAG, "onDataLoaded: " + mList.size() + " user"); - mAdapter = new AppDataUsageAdapter(mList, mContext); - mAdapter.setActivity(mActivity); - mAdapter.setFromHome(fromHome); - mAppsView.setAdapter(mAdapter); - mAppsView.setLayoutManager(new LinearLayoutManager(mContext)); - mLoading.animate().alpha(0.0f); - mAppsView.animate().alpha(1.0f); - mDataRefresh.setRefreshing(false); - if (mList.size() <= 0) { - mEmptyList.animate().alpha(1.0f); - } - else { - setSession(mList.get(0).getSession()); - setType(mList.get(0).getType()); - } - if (!fromHome) { - setRefreshAppDataUsage(false); - } - } - - private static String getTotalDataUsage(Context context) throws ParseException, RemoteException { - int date = PreferenceManager.getDefaultSharedPreferences(context).getInt(DATA_RESET_DATE, -1); - String totalUsage; - int type = getType(); - if (type == TYPE_MOBILE_DATA) { - totalUsage = NetworkStatsHelper.formatData( - NetworkStatsHelper.getTotalAppMobileDataUsage(context, getSession(), date)[0], - NetworkStatsHelper.getTotalAppMobileDataUsage(context, getSession(), date)[1] - )[2]; - } - else if (type == TYPE_WIFI) { - totalUsage = NetworkStatsHelper.formatData( - NetworkStatsHelper.getTotalAppWifiDataUsage(context, getSession())[0], - NetworkStatsHelper.getTotalAppWifiDataUsage(context, getSession())[1] - )[2]; - } - else { - totalUsage = context.getString(R.string.label_unknown); - } - return totalUsage; - } - - public static int getSession() { - if (selectedSession == 0) { - selectedSession = SESSION_TODAY; - } - - return selectedSession; - } - - public static int getType() { - if (selectedType == 0) { - selectedType = TYPE_MOBILE_DATA; - } - return selectedType; - } - - private static void setSession(int session) { - selectedSession = session; - } - - private static void setType(int type) { - selectedType = type; - } - -} diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.kt b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.kt new file mode 100644 index 00000000..d5e6dbd8 --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/AppDataUsageFragment.kt @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2021 Dr.NooB + * + * This file is a part of Data Monitor + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.drnoob.datamonitor.ui.fragments + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.os.RemoteException +import android.provider.Settings +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.drnoob.datamonitor.Common +import com.drnoob.datamonitor.R +import com.drnoob.datamonitor.adapters.UsageDataAdapter +import com.drnoob.datamonitor.adapters.data.AppDataUsageModel +import com.drnoob.datamonitor.adapters.data.DataUsageViewModel +import com.drnoob.datamonitor.adapters.data.DataUsageViewModelFactory +import com.drnoob.datamonitor.core.Values +import com.drnoob.datamonitor.databinding.FragmentAppDataUsageBinding +import com.drnoob.datamonitor.ui.activities.ContainerActivity +import com.drnoob.datamonitor.utils.NetworkStatsHelper +import com.drnoob.datamonitor.utils.VibrationUtils +import com.drnoob.datamonitor.utils.helpers.UsageDataHelperImpl +import com.drnoob.datamonitor.utils.loadScreenTime +import com.drnoob.datamonitor.utils.setBoldSpan +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipGroup +import kotlinx.coroutines.launch +import java.text.ParseException + +class AppDataUsageFragment : Fragment() { + + private var _binding: FragmentAppDataUsageBinding? = null + private val binding get() = _binding!! + + private var session: Int = Values.SESSION_TODAY + private var type: Int = Values.TYPE_MOBILE_DATA + private var totalDataUsage: String? = null + private var fromHome: Boolean = false + + private val dataUsageViewModel: DataUsageViewModel by activityViewModels { + DataUsageViewModelFactory(UsageDataHelperImpl(requireActivity())) + } + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: UsageDataAdapter + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentAppDataUsageBinding + .inflate(inflater, container, false) + return binding.root + } + + @SuppressLint("InflateParams") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + session = + requireActivity().intent.getIntExtra(Values.DATA_USAGE_SESSION, Values.SESSION_TODAY) + type = requireActivity().intent.getIntExtra(Values.DATA_USAGE_TYPE, Values.TYPE_MOBILE_DATA) + fromHome = requireActivity().intent.getBooleanExtra(Values.DAILY_DATA_HOME_ACTION, false) + if (fromHome) binding.filterAppUsage.visibility = View.GONE + + binding.currentSessionTotal.text = "..." + + refreshData() + recyclerView = binding.appDataUsageRecycler + recyclerView.layoutManager = LinearLayoutManager(context) + adapter = UsageDataAdapter(requireContext()) + recyclerView.adapter = adapter + + binding.filterAppUsage.setOnClickListener { + val dialog = BottomSheetDialog(requireContext(), R.style.BottomSheet) + val dialogView = + LayoutInflater.from(context).inflate(R.layout.layout_app_usage_filter, null) + val sessionGroup = dialogView.findViewById(R.id.session_group) + val typeGroup = dialogView.findViewById(R.id.type_group) + val footer = dialogView.findViewById(R.id.footer) + val cancel = footer.findViewById(R.id.cancel) + val ok = footer.findViewById(R.id.ok) + val sessionCurrentPlan = sessionGroup.findViewById(R.id.session_current_plan) + if (PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getString(Values.DATA_RESET, "null") + == Values.DATA_RESET_CUSTOM + ) { + sessionCurrentPlan.visibility = View.VISIBLE + } else { + sessionCurrentPlan.visibility = View.GONE + } + sessionGroup.setOnCheckedStateChangeListener { _, _ -> + if (!PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getBoolean("disable_haptics", false) + ) { + VibrationUtils.hapticMinor(context) + } + } + typeGroup.setOnCheckedStateChangeListener { _, _ -> + if (!PreferenceManager.getDefaultSharedPreferences(requireContext()) + .getBoolean("disable_haptics", false) + ) { + VibrationUtils.hapticMinor(context) + } + } + when (session) { + Values.SESSION_TODAY -> sessionGroup.check(R.id.session_today) + Values.SESSION_YESTERDAY -> sessionGroup.check(R.id.session_yesterday) + Values.SESSION_THIS_MONTH -> sessionGroup.check(R.id.session_this_month) + Values.SESSION_LAST_MONTH -> sessionGroup.check(R.id.session_last_month) + Values.SESSION_THIS_YEAR -> sessionGroup.check(R.id.session_this_year) + Values.SESSION_ALL_TIME -> sessionGroup.check(R.id.session_all_time) + Values.SESSION_CUSTOM -> sessionGroup.check(R.id.session_current_plan) + } + when (type) { + Values.TYPE_MOBILE_DATA -> typeGroup.check(R.id.type_mobile) + Values.TYPE_WIFI -> typeGroup.check(R.id.type_wifi) + } + cancel.setOnClickListener { dialog.dismiss() } + ok.setOnClickListener { + session = when (sessionGroup.checkedChipId) { + R.id.session_yesterday -> Values.SESSION_YESTERDAY + R.id.session_this_month -> Values.SESSION_THIS_MONTH + R.id.session_last_month -> Values.SESSION_LAST_MONTH + R.id.session_this_year -> Values.SESSION_THIS_YEAR + R.id.session_all_time -> Values.SESSION_ALL_TIME + R.id.session_current_plan -> Values.SESSION_CUSTOM + R.id.session_today -> Values.SESSION_TODAY + else -> Values.SESSION_TODAY + } + type = when (typeGroup.checkedChipId) { + R.id.type_wifi -> Values.TYPE_WIFI + R.id.type_mobile -> Values.TYPE_MOBILE_DATA + else -> Values.TYPE_MOBILE_DATA + } + refreshData() + dialog.dismiss() + } + dialog.setContentView(dialogView) + dialog.setOnShowListener { dialogInterface -> + val bottomSheetDialog = dialogInterface as BottomSheetDialog + val bottomSheet = + bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) + bottomSheet?.let { + BottomSheetBehavior.from(it).state = BottomSheetBehavior.STATE_EXPANDED + } + } + dialog.show() + } + binding.refreshDataUsage.setOnRefreshListener { refreshData() } + + // Shrink or expand the FAB according to user scroll + binding.appDataUsageRecycler.setOnScrollChangeListener { _, _, _, _, oldScrollY -> + if (oldScrollY < -15 && binding.filterAppUsage.isExtended) { + binding.filterAppUsage.shrink() + } else if (oldScrollY > 15 && !binding.filterAppUsage.isExtended) { + binding.filterAppUsage.extend() + } else if (binding.appDataUsageRecycler.computeVerticalScrollOffset() == 0 && !binding.filterAppUsage.isExtended) { + binding.filterAppUsage.extend() + } + } + + dataUsageViewModel.userAppsList.observe(viewLifecycleOwner) { userAppsList -> + onDataLoaded(userAppsList) + adapter.differ.submitList(userAppsList) + } + + adapter.setOnItemClickListener { model -> + if (model.packageName == requireContext().getString(R.string.package_system)) { + val intent = Intent(context, ContainerActivity::class.java) + // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra( + Values.GENERAL_FRAGMENT_ID, + Values.DATA_USAGE_SYSTEM + ) + intent.putExtra( + Values.DATA_USAGE_SESSION, + model.session + ) + intent.putExtra(Values.DATA_USAGE_TYPE, model.type) + requireContext().startActivity(intent) + } else setDataUsageSheetDialog(model) + } + } + + @Throws(ParseException::class, RemoteException::class) + private fun getTotalDataUsage(context: Context?): String { + val date = PreferenceManager.getDefaultSharedPreferences( + requireContext() + ).getInt(Values.DATA_RESET_DATE, -1) + return when (type) { + Values.TYPE_MOBILE_DATA -> NetworkStatsHelper.formatData( + NetworkStatsHelper.getTotalAppMobileDataUsage(context, session, date)[0], + NetworkStatsHelper.getTotalAppMobileDataUsage(context, session, date)[1] + )[2] + + Values.TYPE_WIFI -> NetworkStatsHelper.formatData( + NetworkStatsHelper.getTotalAppWifiDataUsage(context, session)[0], + NetworkStatsHelper.getTotalAppWifiDataUsage(context, session)[1] + )[2] + + else -> requireContext().getString(R.string.label_unknown) + } + } + + private fun onDataLoaded(userAppsList: List) { + try { + if (totalDataUsage?.isEmpty() == true) + totalDataUsage = getTotalDataUsage(context) + + binding.currentSessionTotal.text = + requireContext().getString(R.string.total_usage, totalDataUsage) + } catch (e: ParseException) { + e.printStackTrace() + } catch (e: RemoteException) { + e.printStackTrace() + } + binding.refreshDataUsage.isRefreshing = false + binding.layoutListLoading.root.animate().alpha(0.0f) + binding.appDataUsageRecycler.animate().alpha(1.0f) + if (userAppsList.isEmpty()) { + binding.layoutListLoading.root.animate().alpha(0.0F) + binding.emptyList.animate().alpha(1.0F) + } else { + session = userAppsList[0]?.session ?: 0 + type = userAppsList[0]?.type ?: 0 + } + } + + private fun refreshData() { + binding.layoutListLoading.root.animate().alpha(1.0f) + binding.appDataUsageRecycler.animate().alpha(0.0f) + binding.emptyList.animate().alpha(0.0f) + binding.refreshDataUsage.isRefreshing = true + binding.appDataUsageRecycler.removeAllViews() + totalDataUsage = "" + binding.currentSessionTotal.text = "..." + dataUsageViewModel.loadUserAppsData(session, type) + } + + @SuppressLint("InflateParams") + fun setDataUsageSheetDialog(model: AppDataUsageModel) { + val dialog = BottomSheetDialog(requireContext(), R.style.BottomSheet) + val dialogView = + LayoutInflater.from(context).inflate(R.layout.app_detail_view, null) + dialog.setContentView(dialogView) + val appIcon = dialogView.findViewById(R.id.icon) + val appName = dialogView.findViewById(R.id.name) + val dataSent = dialogView.findViewById(R.id.data_sent) + val dataReceived = dialogView.findViewById(R.id.data_received) + val appPackage = dialogView.findViewById(R.id.app_package) + val appUid = dialogView.findViewById(R.id.app_uid) + val appScreenTime = dialogView.findViewById(R.id.app_screen_time) + val appBackgroundTime = dialogView.findViewById(R.id.app_background_time) + val appCombinedTotal = dialogView.findViewById(R.id.app_combined_total) + val appSettings = dialogView.findViewById(R.id.app_open_settings) + appName.text = model.appName + val packageName: String = requireContext().resources.getString( + R.string.app_label_package_name, + model.packageName + ) + val uid: String = requireContext().resources.getString( + R.string.app_label_uid, + model.uid + ) + appPackage.text = setBoldSpan(packageName, model.packageName) + appUid.text = setBoldSpan(uid, model.uid.toString()) + if (model.packageName !== requireContext().getString(R.string.package_tethering)) { + val screenTime: String = requireContext().getString( + R.string.app_label_screen_time, + requireContext().getString(R.string.label_loading) + ) + val backgroundTime: String = requireContext().getString( + R.string.app_label_background_time, + requireContext().getString(R.string.label_loading) + ) + appScreenTime.text = setBoldSpan( + screenTime, + requireContext().getString(R.string.label_loading) + ) + appBackgroundTime.text = setBoldSpan( + backgroundTime, + requireContext().getString(R.string.label_loading) + ) + lifecycleScope.launch { + loadScreenTime(requireContext(), model, appScreenTime, appBackgroundTime) + } + } else { + appScreenTime.visibility = View.GONE + appBackgroundTime.visibility = View.GONE + } + val total = + model.sentMobile + model.receivedMobile + model.sentWifi + model.receivedWifi + val combinedTotal = NetworkStatsHelper.formatData(0L, total)[2] + dataSent.text = + NetworkStatsHelper.formatData(model.sentMobile, model.receivedMobile)[0] + dataReceived.text = + NetworkStatsHelper.formatData(model.sentMobile, model.receivedMobile)[1] + appCombinedTotal.text = setBoldSpan( + requireContext().getString(R.string.app_label_combined_total, combinedTotal), + combinedTotal + ) + appSettings.setOnClickListener { + val intent = + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val uri = Uri.fromParts("package", model.packageName, null) + intent.data = uri + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + requireContext().startActivity(intent) + } + try { + if (model.packageName == requireContext().getString(R.string.package_tethering)) { + appIcon.setImageResource(R.drawable.hotspot) + appPackage.visibility = View.GONE + appUid.visibility = View.GONE + appSettings.visibility = View.GONE + appCombinedTotal.visibility = View.GONE + } else if (model.packageName == requireContext().getString(R.string.package_removed)) { + appIcon.setImageResource(R.drawable.deleted_apps) + appPackage.visibility = View.GONE + appUid.visibility = View.GONE + appSettings.visibility = View.GONE + } else { + if (Common.isAppInstalled(context, model.packageName)) { + appIcon.setImageDrawable( + requireContext().packageManager.getApplicationIcon(model.packageName) + ) + } else { + appIcon.setImageResource(R.drawable.deleted_apps) + } + } + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + dialog.setOnShowListener { dialogInterface -> + val bottomSheetDialog = dialogInterface as BottomSheetDialog + val bottomSheet = + bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) + bottomSheet?.let { + val behavior: BottomSheetBehavior<*> = + BottomSheetBehavior.from(bottomSheet) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + behavior.skipCollapsed = true + } + } + dialog.show() + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } + + companion object { + private val TAG = AppDataUsageFragment::class.java.simpleName + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/HomeFragment.java b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/HomeFragment.java index 4f3feacd..2fde166d 100644 --- a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/HomeFragment.java +++ b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/HomeFragment.java @@ -44,7 +44,6 @@ import static com.drnoob.datamonitor.core.Values.SHOW_ADD_PLAN_BANNER; import static com.drnoob.datamonitor.core.Values.TYPE_MOBILE_DATA; import static com.drnoob.datamonitor.core.Values.TYPE_WIFI; -import static com.drnoob.datamonitor.ui.activities.MainActivity.setRefreshAppDataUsage; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.formatData; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeviceMobileDataUsage; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeviceWifiDataUsage; @@ -84,6 +83,7 @@ import com.drnoob.datamonitor.Widget.DataUsageWidget; import com.drnoob.datamonitor.adapters.data.OverviewModel; import com.drnoob.datamonitor.ui.activities.ContainerActivity; +import com.drnoob.datamonitor.ui.activities.MainActivity; import com.drnoob.datamonitor.utils.NotificationService; import com.drnoob.datamonitor.utils.VibrationUtils; import com.google.android.material.button.MaterialButton; @@ -362,7 +362,7 @@ public void onClick(View v) { intent.putExtra(DATA_USAGE_SESSION, SESSION_TODAY); intent.putExtra(DATA_USAGE_TYPE, TYPE_MOBILE_DATA); intent.putExtra(DAILY_DATA_HOME_ACTION, true); - setRefreshAppDataUsage(true); + MainActivity.Companion.setRefreshAppDataUsage(true); startActivity(intent); } }); @@ -375,7 +375,7 @@ public void onClick(View v) { intent.putExtra(DATA_USAGE_SESSION, SESSION_TODAY); intent.putExtra(DATA_USAGE_TYPE, TYPE_WIFI); intent.putExtra(DAILY_DATA_HOME_ACTION, true); - setRefreshAppDataUsage(true); + MainActivity.Companion.setRefreshAppDataUsage(true); startActivity(intent); } }); diff --git a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/SystemDataUsageFragment.java b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/SystemDataUsageFragment.java index 3d97cbf7..ef6efd84 100644 --- a/app/src/main/java/com/drnoob/datamonitor/ui/fragments/SystemDataUsageFragment.java +++ b/app/src/main/java/com/drnoob/datamonitor/ui/fragments/SystemDataUsageFragment.java @@ -22,14 +22,8 @@ import static com.drnoob.datamonitor.core.Values.DAILY_DATA_HOME_ACTION; import static com.drnoob.datamonitor.core.Values.DATA_USAGE_SESSION; import static com.drnoob.datamonitor.core.Values.DATA_USAGE_TYPE; -import static com.drnoob.datamonitor.core.Values.SESSION_ALL_TIME; -import static com.drnoob.datamonitor.core.Values.SESSION_LAST_MONTH; -import static com.drnoob.datamonitor.core.Values.SESSION_THIS_MONTH; -import static com.drnoob.datamonitor.core.Values.SESSION_THIS_YEAR; import static com.drnoob.datamonitor.core.Values.SESSION_TODAY; -import static com.drnoob.datamonitor.core.Values.SESSION_YESTERDAY; import static com.drnoob.datamonitor.core.Values.TYPE_MOBILE_DATA; -import static com.drnoob.datamonitor.core.Values.TYPE_WIFI; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getAppMobileDataUsage; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getAppWifiDataUsage; import static com.drnoob.datamonitor.utils.NetworkStatsHelper.getDeviceMobileDataUsage; @@ -45,12 +39,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; -import android.widget.RadioGroup; import android.widget.TextView; import androidx.annotation.NonNull; -import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -58,10 +53,13 @@ import com.drnoob.datamonitor.R; import com.drnoob.datamonitor.adapters.AppDataUsageAdapter; import com.drnoob.datamonitor.adapters.data.AppDataUsageModel; +import com.drnoob.datamonitor.adapters.data.DataUsageViewModel; +import com.drnoob.datamonitor.adapters.data.DataUsageViewModelFactory; import com.drnoob.datamonitor.core.task.DatabaseHandler; -import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.drnoob.datamonitor.utils.helpers.UsageDataHelperImpl; import java.text.ParseException; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -76,7 +74,7 @@ public class SystemDataUsageFragment extends Fragment { private static SwipeRefreshLayout mDataRefresh; private static TextView mSession, mType, mEmptyList; public static LinearLayout mTopBar; - private static List mList; + private static List mList = new ArrayList<>(); private static AppDataUsageAdapter mAdapter; public SystemDataUsageFragment() { @@ -99,8 +97,12 @@ public void onAttach(@NonNull Context context) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - // Inflate the layout for this fragment - View view = inflater.inflate(R.layout.fragment_system_data_usage, container, false); + return inflater.inflate(R.layout.fragment_system_data_usage, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); mAppsView = view.findViewById(R.id.app_data_usage_recycler); mLoading = view.findViewById(R.id.layout_list_loading); @@ -109,7 +111,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // mType = view.findViewById(R.id.data_usage_type); mTopBar = view.findViewById(R.id.top_bar); mEmptyList = view.findViewById(R.id.empty_list); - mList = AppDataUsageFragment.mSystemList; mAdapter = new AppDataUsageAdapter(mList, mContext); mAdapter.setActivity(getActivity()); mAppsView = view.findViewById(R.id.app_data_usage_recycler); @@ -125,12 +126,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mAppsView.setPadding(0, 20, 0, 0); } } - -// setSession(session); -// setType(type); - - - if (mList.size() > 0) { + if (mList != null && mList.size() > 0) { mLoading.setAlpha(0.0f); onDataLoaded(); } else { @@ -145,8 +141,6 @@ public void onRefresh() { loadData.execute(); } }); - - return view; } private static void onDataLoaded() { @@ -234,7 +228,6 @@ protected Object doInBackground(Object[] objects) { } model.setProgress(progress); - mList.add(model); } diff --git a/app/src/main/java/com/drnoob/datamonitor/utils/ScreenTimeUtils.kt b/app/src/main/java/com/drnoob/datamonitor/utils/ScreenTimeUtils.kt new file mode 100644 index 00000000..cbd0c9c8 --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/utils/ScreenTimeUtils.kt @@ -0,0 +1,150 @@ +package com.drnoob.datamonitor.utils + +import android.app.usage.UsageEvents +import android.app.usage.UsageStatsManager +import android.content.Context +import android.graphics.Typeface +import android.net.ParseException +import android.os.Build +import android.text.Spannable +import android.text.SpannableString +import android.text.style.StyleSpan +import android.view.View +import android.widget.TextView +import com.drnoob.datamonitor.R +import com.drnoob.datamonitor.adapters.data.AppDataUsageModel +import kotlin.math.roundToInt + +fun loadScreenTime( + context: Context, + model: AppDataUsageModel, + appScreenTime: TextView, + appBackgroundTime: TextView +) { + if (model.packageName !== context.getString(R.string.package_tethering)) { + val usageTime = getUsageTime(context, model.packageName, model.session) + if (usageTime[1] == -1) { + // If value is -1, build version is below Q + val screenTime: String = context.getString( + R.string.app_label_screen_time, + formatTime(context, usageTime[0] / 60f) + ) + appScreenTime.text = setBoldSpan(screenTime, formatTime(context, usageTime[0] / 60f)) + appBackgroundTime.visibility = View.GONE + } else { + val screenTime: String = context.getString( + R.string.app_label_screen_time, + formatTime(context, usageTime[0] / 60f) + ) + val backgroundTime: String = context.getString( + R.string.app_label_background_time, + formatTime(context, usageTime[0] / 60f) + ) + appScreenTime.text = setBoldSpan(screenTime, formatTime(context, usageTime[0] / 60f)) + appBackgroundTime.text = + setBoldSpan(backgroundTime, formatTime(context, usageTime[0] / 60f)) + } + } else { + appScreenTime.visibility = View.GONE + appBackgroundTime.visibility = View.GONE + } +} + +fun setBoldSpan(textString: String, spanText: String): SpannableString { + val boldSpan = SpannableString(textString) + val start = textString.indexOf(spanText).takeIf { it >= 0 } ?: 0 + val end = start + spanText.length + boldSpan.setSpan(StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE) + return boldSpan +} + +/** + * Returns app usage time as an array like [screenTime, backgroundTime] + * ScreenTime source credit: https://stackoverflow.com/questions/61677505/how-to-count-app-usage-time-while-app-is-on-foreground + */ +private fun getUsageTime(context: Context, packageName: String, session: Int): IntArray { + + val allEvents: MutableList = mutableListOf() + val appScreenTime = HashMap().apply { put(packageName, 0) } + val appBackgroundTime = HashMap().apply { put(packageName, -1) } + val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager + val timePeriod = NetworkStatsHelper.getTimePeriod(context, session, 1) + + try { + val usageEvents = usageStatsManager.queryEvents(timePeriod[0], timePeriod[1]) + val usageStats = usageStatsManager.queryUsageStats( + UsageStatsManager.INTERVAL_DAILY, + timePeriod[0], timePeriod[1] + ) + + while (usageEvents.hasNextEvent()) { + val currentEvent = UsageEvents.Event() + usageEvents.getNextEvent(currentEvent) + if (currentEvent.packageName == packageName && currentEvent.eventType in setOf( + UsageEvents.Event.ACTIVITY_RESUMED, + UsageEvents.Event.ACTIVITY_PAUSED, + UsageEvents.Event.FOREGROUND_SERVICE_START, + UsageEvents.Event.FOREGROUND_SERVICE_STOP + ) + ) { + allEvents.add(currentEvent) + } + } + + if (allEvents.size > 0) { + allEvents.zipWithNext { event, nextEvent -> + if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED && + nextEvent.eventType == UsageEvents.Event.ACTIVITY_PAUSED && + event.className == nextEvent.className + ) { + val diff = (nextEvent.timeStamp - event.timeStamp).toInt() / 1000 + val prev = appScreenTime[event.packageName] ?: 0 + appScreenTime[event.packageName] = prev + diff + } + } + val lastEvent = allEvents.last() + if (lastEvent.eventType == UsageEvents.Event.ACTIVITY_RESUMED) { + val diff = (System.currentTimeMillis() - lastEvent.timeStamp) / 1000 + val prev = appScreenTime[lastEvent.packageName] ?: 0 + appScreenTime[lastEvent.packageName] = prev + diff.toInt() + } + } else { + appScreenTime[packageName] = 0 + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (!usageStats.isNullOrEmpty()) { + for (stats in usageStats) { + if (stats.packageName == packageName) { + val backgroundTime = stats.totalTimeForegroundServiceUsed.toInt() / 1000 + appBackgroundTime[packageName] = backgroundTime + break + } + } + } else { + appBackgroundTime[packageName] = 0 + } + } else { + appBackgroundTime[packageName] = 0 + } + } catch (e: ParseException) { + e.printStackTrace() + } + + return intArrayOf(appScreenTime[packageName]!!, appBackgroundTime[packageName]!!) +} + +private fun formatTime(context: Context, minutes: Float): String { + + if (minutes < 1 && minutes > 0) return "Less than a minute" + if (minutes >= 60) { + val hours = (minutes / 60).toInt() + val mins = (minutes % 60).toInt() + val hourLabel: String = if (hours > 1) "hours" else "hour" + val minuteLabel: String = if (mins == 1) "minute" else "minutes" + return context.getString(R.string.usage_time_label, hours, hourLabel, mins, minuteLabel) + } + return if (minutes == 1f) { + minutes.roundToInt().toString() + " minute" + } else minutes.roundToInt().toString() + " minutes" +} diff --git a/app/src/main/java/com/drnoob/datamonitor/utils/helpers/ThemeHelper.kt b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/ThemeHelper.kt new file mode 100644 index 00000000..ead5250e --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/ThemeHelper.kt @@ -0,0 +1,18 @@ +package com.drnoob.datamonitor.utils.helpers + +import android.app.Activity +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.PreferenceManager +import com.drnoob.datamonitor.core.Values + +fun setTheme(activity: Activity?) { + val theme = PreferenceManager.getDefaultSharedPreferences( + activity!! + ).getString(Values.APP_THEME, "system") + when (theme) { + "dark" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + "light" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + "system" -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelper.kt b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelper.kt new file mode 100644 index 00000000..623ab6ea --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelper.kt @@ -0,0 +1,9 @@ +package com.drnoob.datamonitor.utils.helpers + +import com.drnoob.datamonitor.adapters.data.AppDataUsageModel + +interface UsageDataHelper { + suspend fun fetchApps() + suspend fun loadUserAppsData(session: Int, type: Int): MutableList + suspend fun loadSystemAppsData(session: Int, type: Int): MutableList +} \ No newline at end of file diff --git a/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt new file mode 100644 index 00000000..304393c8 --- /dev/null +++ b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt @@ -0,0 +1,365 @@ +package com.drnoob.datamonitor.utils.helpers + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.os.RemoteException +import androidx.preference.PreferenceManager +import com.drnoob.datamonitor.Common +import com.drnoob.datamonitor.R +import com.drnoob.datamonitor.adapters.data.AppDataUsageModel +import com.drnoob.datamonitor.core.Values +import com.drnoob.datamonitor.core.task.DatabaseHandler +import com.drnoob.datamonitor.utils.NetworkStatsHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.text.ParseException + +class UsageDataHelperImpl(val context: Context) : UsageDataHelper { + + override suspend fun fetchApps() = withContext(Dispatchers.IO) { + val packageManager = context.packageManager + val allApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA) + val modelList: MutableList = ArrayList() + var model: AppDataUsageModel? + val databaseHandler = DatabaseHandler(context) + for (applicationInfo in allApps) { + if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1) { + // System app + modelList.add( + AppDataUsageModel( + packageManager.getApplicationLabel(applicationInfo).toString(), + applicationInfo.packageName, + applicationInfo.uid, + true + ) + ) + } else { + // User app + modelList.add( + AppDataUsageModel( + packageManager.getApplicationLabel(applicationInfo).toString(), + applicationInfo.packageName, + applicationInfo.uid, + false + ) + ) + } + } + for (i in modelList.indices) { + model = AppDataUsageModel().apply { + this.appName = modelList[i].appName + this.packageName = modelList[i].packageName + this.uid = modelList[i].uid + this.setIsSystemApp(modelList[i].isSystemApp) + } + databaseHandler.addData(model) + } + } + + override suspend fun loadUserAppsData( + session: Int, + type: Int + ): MutableList = withContext(Dispatchers.IO) { + val dataList = mutableListOf() + var totalSystemSent = 0L + var totalSystemReceived = 0L + val date = PreferenceManager.getDefaultSharedPreferences(context) + .getInt(Values.DATA_RESET_DATE, 1) + val handler = DatabaseHandler(context) + val list = handler.usageList + + for (currentData in list) { + if (!Common.isAppInstalled( + context, + currentData.packageName + ) || currentData.isSystemApp + ) continue + + val model: AppDataUsageModel? + val (sent, received) = when (type) { + Values.TYPE_MOBILE_DATA -> { + try { + val mobileDataUsage = NetworkStatsHelper.getAppMobileDataUsage( + context, + currentData.uid, + session + ) + mobileDataUsage[0] to mobileDataUsage[1] + } catch (e: ParseException) { + e.printStackTrace() + continue + } catch (e: RemoteException) { + e.printStackTrace() + continue + } + } + + else -> { + try { + val wifiDataUsage = NetworkStatsHelper.getAppWifiDataUsage( + context, + currentData.uid, + session + ) + wifiDataUsage[0] to wifiDataUsage[1] + } catch (e: ParseException) { + e.printStackTrace() + continue + } catch (e: RemoteException) { + e.printStackTrace() + continue + } + } + } + + if (sent <= 0 && received <= 0) continue + + model = AppDataUsageModel().apply { + appName = currentData.appName + packageName = currentData.packageName + uid = currentData.uid + sentMobile = sent + receivedMobile = received + this.session = session + this.type = type + + val total = sent + received + val deviceTotal = when (type) { + Values.TYPE_MOBILE_DATA -> NetworkStatsHelper.getDeviceMobileDataUsage( + context, + session, + date + )[2] + + else -> NetworkStatsHelper.getDeviceWifiDataUsage(context, session)[2] + } + + progress = ((total.toDouble() / deviceTotal.toDouble()) * 100 * 2).toInt() + + totalSystemSent += sent + totalSystemReceived += received + } + dataList.add(model) + } + + return@withContext modifyDataList( + dataList, + session, type, + totalSystemSent, + totalSystemReceived + ) + } + + + override suspend fun loadSystemAppsData( + session: Int, + type: Int + ): MutableList = withContext(Dispatchers.IO) { + val dataList = mutableListOf() + var totalSystemSent = 0L + var totalSystemReceived = 0L + val date = PreferenceManager.getDefaultSharedPreferences(context) + .getInt(Values.DATA_RESET_DATE, 1) + val handler = DatabaseHandler(context) + val list = handler.usageList + + for (currentData in list) { + val model: AppDataUsageModel? + val (sent, received) = when (type) { + Values.TYPE_MOBILE_DATA -> { + try { + val mobileDataUsage = NetworkStatsHelper.getAppMobileDataUsage( + context, + currentData.uid, + session + ) + mobileDataUsage[0] to mobileDataUsage[1] + } catch (e: ParseException) { + e.printStackTrace() + continue + } catch (e: RemoteException) { + e.printStackTrace() + continue + } + } + + else -> { + try { + val wifiDataUsage = NetworkStatsHelper.getAppWifiDataUsage( + context, + currentData.uid, + session + ) + wifiDataUsage[0] to wifiDataUsage[1] + } catch (e: ParseException) { + e.printStackTrace() + continue + } catch (e: RemoteException) { + e.printStackTrace() + continue + } + } + } + + if (sent <= 0 && received <= 0) continue + + model = AppDataUsageModel().apply { + appName = currentData.appName + packageName = currentData.packageName + uid = currentData.uid + sentMobile = sent + receivedMobile = received + this.session = session + this.type = type + + val total = sent + received + val deviceTotal = when (type) { + Values.TYPE_MOBILE_DATA -> NetworkStatsHelper.getDeviceMobileDataUsage( + context, + session, + date + )[2] + + else -> NetworkStatsHelper.getDeviceWifiDataUsage(context, session)[2] + } + + progress = ((total.toDouble() / deviceTotal.toDouble()) * 100 * 2).toInt() + + totalSystemSent += sent + totalSystemReceived += received + } + dataList.add(model) + } + + return@withContext modifyDataList( + dataList, + session, type, + totalSystemSent, + totalSystemReceived + ) + } + + private fun loadData( + session: Int, + type: Int, + totalSystemSent: Long, + totalSystemReceived: Long + ): MutableList { + val dataList = mutableListOf() + val totalTetheringSent: Long + val totalTetheringReceived: Long + val totalDeletedAppsSent: Long + val totalDeletedAppsReceived: Long + val tetheringTotal: Long + val deletedAppsTotal: Long + var deviceTotal = 0L + val date = PreferenceManager.getDefaultSharedPreferences(context) + .getInt(Values.DATA_RESET_DATE, 1) + + var model = AppDataUsageModel().apply { + this.appName = context.getString(R.string.label_system_apps) + this.packageName = context.getString(R.string.package_system) + this.sentMobile = totalSystemSent + this.receivedMobile = totalSystemReceived + this.session = session + this.type = type + val total = totalSystemSent + totalSystemReceived + if (type == Values.TYPE_MOBILE_DATA) { + try { + deviceTotal = + NetworkStatsHelper.getDeviceMobileDataUsage(context, session, date)[2] + this.progress = (total.toDouble() / deviceTotal.toDouble() * 100 * 2).toInt() + } catch (e: ParseException) { + e.printStackTrace() + } catch (e: RemoteException) { + e.printStackTrace() + } + } else { + try { + deviceTotal = NetworkStatsHelper.getDeviceWifiDataUsage(context, session)[2] + this.progress = (total.toDouble() / deviceTotal.toDouble() * 100 * 2).toInt() + } catch (e: ParseException) { + e.printStackTrace() + } catch (e: RemoteException) { + e.printStackTrace() + } + } + } + if (deviceTotal > 0) dataList.add(model) + try { + if (type == Values.TYPE_MOBILE_DATA) { + totalTetheringSent = NetworkStatsHelper.getTetheringDataUsage(context, session)[0] + totalTetheringReceived = + NetworkStatsHelper.getTetheringDataUsage(context, session)[1] + tetheringTotal = totalTetheringSent + totalTetheringReceived + val tetheringProgress = tetheringTotal.toDouble() / deviceTotal.toDouble() * 100 * 2 + val tetheringProgressInt: Int = tetheringProgress.toInt() + model = AppDataUsageModel().apply { + this.appName = context.getString(R.string.label_tethering) + this.packageName = context.getString(R.string.package_tethering) + this.sentMobile = totalTetheringSent + this.receivedMobile = totalTetheringReceived + this.session = session + this.type = type + this.progress = tetheringProgressInt + } + if (tetheringTotal > 0) dataList.add(model) + totalDeletedAppsSent = + NetworkStatsHelper.getDeletedAppsMobileDataUsage(context, session)[0] + totalDeletedAppsReceived = NetworkStatsHelper.getDeletedAppsMobileDataUsage( + context, session + )[1] + } else { + totalDeletedAppsSent = + NetworkStatsHelper.getDeletedAppsWifiDataUsage(context, session)[0] + totalDeletedAppsReceived = NetworkStatsHelper.getDeletedAppsWifiDataUsage( + context, session + )[1] + } + deletedAppsTotal = totalDeletedAppsSent + totalDeletedAppsReceived + val deletedProgress = deletedAppsTotal.toDouble() / deviceTotal.toDouble() * 100 * 2 + val deletedProgressInt: Int = deletedProgress.toInt() + + model = AppDataUsageModel().apply { + this.packageName = context.getString(R.string.package_removed) + this.appName = context.getString(R.string.label_removed) + this.sentMobile = totalDeletedAppsSent + this.receivedMobile = totalDeletedAppsReceived + this.session = session + this.type = type + this.progress = deletedProgressInt + } + if (deletedAppsTotal > 0) dataList.add(model) + } catch (e: ParseException) { + e.printStackTrace() + } catch (e: RemoteException) { + e.printStackTrace() + } + return dataList + } + + private fun modifyDataList( + dataList: MutableList, + session: Int, + type: Int, + totalSystemSent: Long, + totalSystemReceived: Long + ): MutableList { + dataList.addAll(loadData(session, type, totalSystemSent, totalSystemReceived)) + dataList.sortWith { o1, o2 -> + o1!!.mobileTotal = (o1.sentMobile + o1.receivedMobile) / 1024f + o2!!.mobileTotal = (o2.sentMobile + o2.receivedMobile) / 1024f + o1.mobileTotal.compareTo(o2.mobileTotal) + } + dataList.reverse() + dataList.sortWith { o1, o2 -> + o1!!.mobileTotal = (o1.sentMobile + o1.receivedMobile) / 1024f + o2!!.mobileTotal = (o2.sentMobile + o2.receivedMobile) / 1024f + o1.mobileTotal.compareTo(o2.mobileTotal) + } + dataList.reverse() + + return dataList + } +} \ No newline at end of file From f91bd6d881b52780a403464d167d28a370c2e7cd Mon Sep 17 00:00:00 2001 From: Yatik Date: Fri, 21 Jul 2023 12:17:50 +0530 Subject: [PATCH 2/2] Fixed bug where newly installed applications were not showing in App Usage --- .../core/task/DatabaseHandler.java | 28 +++++++- .../utils/helpers/UsageDataHelperImpl.kt | 71 +++++++++++-------- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/drnoob/datamonitor/core/task/DatabaseHandler.java b/app/src/main/java/com/drnoob/datamonitor/core/task/DatabaseHandler.java index cd04919f..a97d6cbe 100644 --- a/app/src/main/java/com/drnoob/datamonitor/core/task/DatabaseHandler.java +++ b/app/src/main/java/com/drnoob/datamonitor/core/task/DatabaseHandler.java @@ -76,7 +76,6 @@ public void addData(AppDataUsageModel model) { finally { database.close(); } - } public List getUsageList() { @@ -155,4 +154,31 @@ public List getAppMonitorList() { return mList; } + + /** + * Returns total number of apps fetched and saved in database. + * Update the database if total number of apps in database is different from total number of apps currently installed in device. + * */ + public int getUsageListSize() { + SQLiteDatabase db = this.getReadableDatabase(); + int count = 0; + Cursor cursor = null; + + try { + cursor = db.rawQuery("SELECT COUNT(*) FROM app_data_usage", null); + if (cursor.moveToFirst()) { + count = cursor.getInt(0); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + db.close(); + } + + return count; + } + } diff --git a/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt index 304393c8..bee131b9 100644 --- a/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt +++ b/app/src/main/java/com/drnoob/datamonitor/utils/helpers/UsageDataHelperImpl.kt @@ -19,41 +19,36 @@ class UsageDataHelperImpl(val context: Context) : UsageDataHelper { override suspend fun fetchApps() = withContext(Dispatchers.IO) { val packageManager = context.packageManager - val allApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA) - val modelList: MutableList = ArrayList() - var model: AppDataUsageModel? + val allApps = + packageManager.getInstalledApplications(PackageManager.GET_META_DATA).removeDuplicates() + val databaseHandler = DatabaseHandler(context) + if (allApps.size == databaseHandler.usageListSize) return@withContext + for (applicationInfo in allApps) { - if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1) { - // System app - modelList.add( - AppDataUsageModel( - packageManager.getApplicationLabel(applicationInfo).toString(), - applicationInfo.packageName, - applicationInfo.uid, - true - ) + val model = if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1) { + AppDataUsageModel( + packageManager.getApplicationLabel(applicationInfo).toString(), + applicationInfo.packageName, + applicationInfo.uid, + true ) } else { // User app - modelList.add( - AppDataUsageModel( - packageManager.getApplicationLabel(applicationInfo).toString(), - applicationInfo.packageName, - applicationInfo.uid, - false - ) + AppDataUsageModel( + packageManager.getApplicationLabel(applicationInfo).toString(), + applicationInfo.packageName, + applicationInfo.uid, + false ) } - } - for (i in modelList.indices) { - model = AppDataUsageModel().apply { - this.appName = modelList[i].appName - this.packageName = modelList[i].packageName - this.uid = modelList[i].uid - this.setIsSystemApp(modelList[i].isSystemApp) - } - databaseHandler.addData(model) + databaseHandler.addData( + AppDataUsageModel().apply { + this.appName = model.appName + this.packageName = model.packageName + this.uid = model.uid + this.setIsSystemApp(model.isSystemApp) + }) } } @@ -66,6 +61,9 @@ class UsageDataHelperImpl(val context: Context) : UsageDataHelper { var totalSystemReceived = 0L val date = PreferenceManager.getDefaultSharedPreferences(context) .getInt(Values.DATA_RESET_DATE, 1) + + fetchApps() + val handler = DatabaseHandler(context) val list = handler.usageList @@ -151,7 +149,6 @@ class UsageDataHelperImpl(val context: Context) : UsageDataHelper { ) } - override suspend fun loadSystemAppsData( session: Int, type: Int @@ -362,4 +359,20 @@ class UsageDataHelperImpl(val context: Context) : UsageDataHelper { return dataList } + + /** + * Removes the [ApplicationInfo] objects from list which have duplicate [ApplicationInfo.uid] + * */ + private fun MutableList.removeDuplicates(): List { + val uniqueUid = HashSet() + val iterator = this.iterator() + + while (iterator.hasNext()) { + val appInfo = iterator.next() + if (!uniqueUid.add(appInfo.uid)) { + iterator.remove() + } + } + return this + } } \ No newline at end of file