2020-01-09 10:44:32 +01:00
/ * Copyright ( C ) 2016 - 2020 Andreas Shimokawa , Carsten Pfeiffer , Daniele
2019-11-23 21:52:46 +01:00
Gobbetti , Johannes Tysiak , Taavi Eomäe , vanous
2017-03-16 17:36:15 +01:00
This file is part of Gadgetbridge .
Gadgetbridge is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
Gadgetbridge 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 Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>. */
2016-10-21 13:01:30 +02:00
package nodomain.freeyourgadget.gadgetbridge.activities ;
2023-06-26 13:00:05 +02:00
import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast ;
2016-10-21 13:01:30 +02:00
import android.Manifest ;
import android.annotation.TargetApi ;
2022-05-18 14:22:24 +02:00
import android.app.AlertDialog ;
import android.app.Dialog ;
2020-07-27 18:15:33 +02:00
import android.app.NotificationManager ;
2022-07-25 16:08:53 +02:00
import android.content.ActivityNotFoundException ;
2016-10-21 13:01:30 +02:00
import android.content.BroadcastReceiver ;
import android.content.Context ;
2022-05-18 14:22:24 +02:00
import android.content.DialogInterface ;
2016-10-21 13:01:30 +02:00
import android.content.Intent ;
import android.content.IntentFilter ;
import android.content.pm.PackageManager ;
2017-08-18 21:51:12 +02:00
import android.net.Uri ;
2016-10-21 13:01:30 +02:00
import android.os.Build ;
import android.os.Bundle ;
2022-10-09 14:53:04 +02:00
import android.provider.Settings ;
2019-09-21 10:07:58 +02:00
import android.telephony.PhoneStateListener ;
import android.telephony.TelephonyManager ;
2016-10-21 13:01:30 +02:00
import android.view.MenuItem ;
import android.view.View ;
2019-10-21 21:08:09 +02:00
import android.widget.Toast ;
2016-10-21 13:01:30 +02:00
2022-10-09 14:53:04 +02:00
import androidx.activity.result.ActivityResultLauncher ;
import androidx.activity.result.contract.ActivityResultContracts ;
2019-01-26 15:52:40 +01:00
import androidx.annotation.NonNull ;
2022-10-09 14:53:04 +02:00
import androidx.annotation.RequiresApi ;
2019-01-26 15:52:40 +01:00
import androidx.appcompat.app.ActionBarDrawerToggle ;
import androidx.appcompat.app.AppCompatActivity ;
import androidx.appcompat.app.AppCompatDelegate ;
2022-06-13 09:38:05 +02:00
import androidx.appcompat.view.menu.MenuItemImpl ;
2019-01-26 15:52:40 +01:00
import androidx.appcompat.widget.Toolbar ;
import androidx.core.app.ActivityCompat ;
2020-07-27 18:42:36 +02:00
import androidx.core.app.NotificationManagerCompat ;
2019-01-26 15:52:40 +01:00
import androidx.core.content.ContextCompat ;
import androidx.core.view.GravityCompat ;
import androidx.drawerlayout.widget.DrawerLayout ;
2022-05-18 14:22:24 +02:00
import androidx.fragment.app.DialogFragment ;
2019-01-26 15:52:40 +01:00
import androidx.localbroadcastmanager.content.LocalBroadcastManager ;
import androidx.recyclerview.widget.LinearLayoutManager ;
import androidx.recyclerview.widget.RecyclerView ;
2019-09-08 12:07:10 +02:00
import com.google.android.material.floatingactionbutton.FloatingActionButton ;
import com.google.android.material.navigation.NavigationView ;
2023-06-26 13:00:05 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2021-08-08 11:11:05 +02:00
import java.io.Serializable ;
2019-09-08 12:07:10 +02:00
import java.util.ArrayList ;
2021-11-18 12:22:43 +01:00
import java.util.Calendar ;
import java.util.GregorianCalendar ;
import java.util.HashMap ;
2020-07-28 04:17:29 +02:00
import java.util.HashSet ;
2019-09-08 12:07:10 +02:00
import java.util.List ;
import java.util.Locale ;
import java.util.Objects ;
2020-07-31 14:05:45 +02:00
import java.util.Set ;
2019-09-08 12:07:10 +02:00
2016-10-21 13:01:30 +02:00
import de.cketti.library.changelog.ChangeLog ;
import nodomain.freeyourgadget.gadgetbridge.GBApplication ;
import nodomain.freeyourgadget.gadgetbridge.R ;
2022-03-31 11:36:26 +02:00
import nodomain.freeyourgadget.gadgetbridge.BuildConfig ;
2016-10-21 13:01:30 +02:00
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2 ;
2021-11-18 12:22:43 +01:00
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess ;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler ;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator ;
2016-10-21 13:01:30 +02:00
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager ;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice ;
2021-08-08 11:11:05 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample ;
2021-11-18 12:22:43 +01:00
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals ;
2021-08-08 11:11:05 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService ;
2017-07-31 22:49:05 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils ;
2021-11-18 12:22:43 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper ;
2016-10-21 13:01:30 +02:00
import nodomain.freeyourgadget.gadgetbridge.util.GB ;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs ;
2017-09-03 01:02:31 +02:00
//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
2016-10-21 13:01:30 +02:00
public class ControlCenterv2 extends AppCompatActivity
2017-09-03 01:02:31 +02:00
implements NavigationView . OnNavigationItemSelectedListener , GBActivity {
2016-10-21 13:01:30 +02:00
2023-06-26 13:00:05 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( ControlCenterv2 . class ) ;
2020-08-01 21:20:53 +02:00
public static final int MENU_REFRESH_CODE = 1 ;
2022-05-20 09:39:47 +02:00
public static final String ACTION_REQUEST_PERMISSIONS
= " nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions " ;
2023-06-26 13:00:05 +02:00
public static final String ACTION_REQUEST_LOCATION_PERMISSIONS
= " nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestlocationpermissions " ;
2020-08-01 21:20:53 +02:00
private static PhoneStateListener fakeStateListener ;
2016-10-24 17:41:56 +02:00
//needed for KK compatibility
static {
AppCompatDelegate . setCompatVectorFromResourcesEnabled ( true ) ;
}
2016-10-21 13:01:30 +02:00
private DeviceManager deviceManager ;
private GBDeviceAdapterv2 mGBDeviceAdapter ;
2016-10-24 17:41:56 +02:00
private RecyclerView deviceListView ;
2019-09-17 14:02:35 +02:00
private FloatingActionButton fab ;
2017-09-07 23:26:24 +02:00
private boolean isLanguageInvalid = false ;
2021-11-18 12:22:43 +01:00
List < GBDevice > deviceList ;
private HashMap < String , long [ ] > deviceActivityHashMap = new HashMap ( ) ;
2016-10-21 13:01:30 +02:00
private final BroadcastReceiver mReceiver = new BroadcastReceiver ( ) {
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ( ) ;
2018-03-31 16:21:25 +02:00
switch ( Objects . requireNonNull ( action ) ) {
2017-07-31 22:49:05 +02:00
case GBApplication . ACTION_LANGUAGE_CHANGE :
2017-09-03 01:02:31 +02:00
setLanguage ( GBApplication . getLanguage ( ) , true ) ;
2017-07-31 22:49:05 +02:00
break ;
2016-10-21 13:01:30 +02:00
case GBApplication . ACTION_QUIT :
finish ( ) ;
break ;
case DeviceManager . ACTION_DEVICES_CHANGED :
2021-11-18 12:22:43 +01:00
case GBApplication . ACTION_NEW_DATA :
createRefreshTask ( " get activity data " , getApplication ( ) ) . execute ( ) ;
2022-06-23 23:12:08 +02:00
mGBDeviceAdapter . rebuildFolders ( ) ;
2016-10-21 13:01:30 +02:00
refreshPairedDevices ( ) ;
break ;
2021-08-08 11:11:05 +02:00
case DeviceService . ACTION_REALTIME_SAMPLES :
handleRealtimeSample ( intent . getSerializableExtra ( DeviceService . EXTRA_REALTIME_SAMPLE ) ) ;
break ;
2022-05-20 09:39:47 +02:00
case ACTION_REQUEST_PERMISSIONS :
2023-06-26 13:00:05 +02:00
checkAndRequestPermissions ( ) ;
2022-05-20 09:39:47 +02:00
break ;
2023-06-26 13:00:05 +02:00
case ACTION_REQUEST_LOCATION_PERMISSIONS :
checkAndRequestLocationPermissions ( ) ;
break ;
2016-10-21 13:01:30 +02:00
}
}
} ;
2020-08-01 21:20:53 +02:00
private boolean pesterWithPermissions = true ;
2021-08-08 11:11:05 +02:00
private ActivitySample currentHRSample ;
public ActivitySample getCurrentHRSample ( ) {
return currentHRSample ;
}
private void setCurrentHRSample ( ActivitySample sample ) {
if ( HeartRateUtils . getInstance ( ) . isValidHeartRateValue ( sample . getHeartRate ( ) ) ) {
currentHRSample = sample ;
refreshPairedDevices ( ) ;
}
}
private void handleRealtimeSample ( Serializable extra ) {
if ( extra instanceof ActivitySample ) {
ActivitySample sample = ( ActivitySample ) extra ;
setCurrentHRSample ( sample ) ;
}
}
2016-10-21 13:01:30 +02:00
@Override
protected void onCreate ( Bundle savedInstanceState ) {
2017-09-03 01:02:31 +02:00
AbstractGBActivity . init ( this , AbstractGBActivity . NO_ACTIONBAR ) ;
2016-10-21 23:01:10 +02:00
2016-10-21 13:01:30 +02:00
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_controlcenterv2 ) ;
2018-03-31 16:21:25 +02:00
Toolbar toolbar = findViewById ( R . id . toolbar ) ;
2016-10-21 13:01:30 +02:00
setSupportActionBar ( toolbar ) ;
2018-03-31 16:21:25 +02:00
DrawerLayout drawer = findViewById ( R . id . drawer_layout ) ;
2016-10-21 13:01:30 +02:00
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle (
this , drawer , toolbar , R . string . controlcenter_navigation_drawer_open , R . string . controlcenter_navigation_drawer_close ) ;
drawer . setDrawerListener ( toggle ) ;
toggle . syncState ( ) ;
2022-06-13 09:38:05 +02:00
/ * This sucks but for the play store we ' re not allowed a donation link . Instead for
the Bangle . js Play Store app we put a message in the About dialog via @string / about_description * /
if ( BuildConfig . FLAVOR = = " banglejs " ) {
MenuItemImpl v = ( MenuItemImpl ) ( ( NavigationView ) drawer . getChildAt ( 1 ) ) . getMenu ( ) . findItem ( R . id . donation_link ) ;
if ( v ! = null ) v . setVisible ( false ) ;
}
2018-03-31 16:21:25 +02:00
NavigationView navigationView = findViewById ( R . id . nav_view ) ;
2016-10-21 13:01:30 +02:00
navigationView . setNavigationItemSelectedListener ( this ) ;
//end of material design boilerplate
2016-12-15 20:59:55 +01:00
deviceManager = ( ( GBApplication ) getApplication ( ) ) . getDeviceManager ( ) ;
2016-10-24 17:41:56 +02:00
2018-03-31 16:21:25 +02:00
deviceListView = findViewById ( R . id . deviceListView ) ;
2016-10-24 17:41:56 +02:00
deviceListView . setHasFixedSize ( true ) ;
deviceListView . setLayoutManager ( new LinearLayoutManager ( this ) ) ;
2016-10-21 13:01:30 +02:00
2021-11-18 12:22:43 +01:00
deviceList = deviceManager . getDevices ( ) ;
mGBDeviceAdapter = new GBDeviceAdapterv2 ( this , deviceList , deviceActivityHashMap ) ;
2022-06-28 21:31:58 +02:00
mGBDeviceAdapter . setHasStableIds ( true ) ;
2021-11-18 12:22:43 +01:00
// get activity data asynchronously, this fills the deviceActivityHashMap
// and calls refreshPairedDevices() → notifyDataSetChanged
createRefreshTask ( " get activity data " , getApplication ( ) ) . execute ( ) ;
2016-10-24 17:41:56 +02:00
2016-10-21 13:01:30 +02:00
deviceListView . setAdapter ( this . mGBDeviceAdapter ) ;
2016-10-24 17:41:56 +02:00
2019-09-17 14:02:35 +02:00
fab = findViewById ( R . id . fab ) ;
2019-09-08 12:07:10 +02:00
fab . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
launchDiscoveryActivity ( ) ;
}
} ) ;
2019-09-17 14:02:35 +02:00
showFabIfNeccessary ( ) ;
2019-09-08 12:07:10 +02:00
2018-03-31 16:21:25 +02:00
/ * uncomment to enable fixed - swipe to reveal more actions
2016-10-24 17:41:56 +02:00
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper ( new ItemTouchHelper . SimpleCallback (
ItemTouchHelper . LEFT , ItemTouchHelper . RIGHT ) {
@Override
public void onChildDraw ( Canvas c , RecyclerView recyclerView , RecyclerView . ViewHolder viewHolder , float dX , float dY , int actionState , boolean isCurrentlyActive ) {
if ( dX > 50 )
dX = 50 ;
super . onChildDraw ( c , recyclerView , viewHolder , dX , dY , actionState , isCurrentlyActive ) ;
}
@Override
public boolean onMove ( RecyclerView recyclerView , RecyclerView . ViewHolder viewHolder , RecyclerView . ViewHolder target ) {
GB . toast ( getBaseContext ( ) , " onMove " , Toast . LENGTH_LONG , GB . ERROR ) ;
return false ;
}
@Override
public void onSwiped ( RecyclerView . ViewHolder viewHolder , int direction ) {
GB . toast ( getBaseContext ( ) , " onSwiped " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
2016-10-21 13:01:30 +02:00
@Override
2016-10-24 17:41:56 +02:00
public void onChildDrawOver ( Canvas c , RecyclerView recyclerView ,
RecyclerView . ViewHolder viewHolder , float dX , float dY ,
int actionState , boolean isCurrentlyActive ) {
2016-10-21 13:01:30 +02:00
}
} ) ;
2018-03-31 16:21:25 +02:00
swipeToDismissTouchHelper . attachToRecyclerView ( deviceListView ) ;
* /
2016-10-24 17:41:56 +02:00
2016-10-21 13:01:30 +02:00
registerForContextMenu ( deviceListView ) ;
IntentFilter filterLocal = new IntentFilter ( ) ;
2017-07-31 22:49:05 +02:00
filterLocal . addAction ( GBApplication . ACTION_LANGUAGE_CHANGE ) ;
2016-10-21 13:01:30 +02:00
filterLocal . addAction ( GBApplication . ACTION_QUIT ) ;
2021-11-18 12:22:43 +01:00
filterLocal . addAction ( GBApplication . ACTION_NEW_DATA ) ;
2016-10-21 13:01:30 +02:00
filterLocal . addAction ( DeviceManager . ACTION_DEVICES_CHANGED ) ;
2021-08-08 11:11:05 +02:00
filterLocal . addAction ( DeviceService . ACTION_REALTIME_SAMPLES ) ;
2022-05-20 09:39:47 +02:00
filterLocal . addAction ( ACTION_REQUEST_PERMISSIONS ) ;
2023-06-26 13:00:05 +02:00
filterLocal . addAction ( ACTION_REQUEST_LOCATION_PERMISSIONS ) ;
2016-10-21 13:01:30 +02:00
LocalBroadcastManager . getInstance ( this ) . registerReceiver ( mReceiver , filterLocal ) ;
refreshPairedDevices ( ) ;
/ *
* Ask for permission to intercept notifications on first run .
* /
Prefs prefs = GBApplication . getPrefs ( ) ;
2020-08-01 16:27:01 +02:00
pesterWithPermissions = prefs . getBoolean ( " permission_pestering " , true ) ;
2020-07-27 18:42:36 +02:00
2023-06-26 13:00:05 +02:00
boolean displayPermissionDialog = ! prefs . getBoolean ( " permission_dialog_displayed " , false ) ;
prefs . getPreferences ( ) . edit ( ) . putBoolean ( " permission_dialog_displayed " , true ) . apply ( ) ;
2020-07-27 18:42:36 +02:00
Set < String > set = NotificationManagerCompat . getEnabledListenerPackages ( this ) ;
2020-08-01 16:27:01 +02:00
if ( pesterWithPermissions ) {
if ( ! set . contains ( this . getPackageName ( ) ) ) { // If notification listener access hasn't been granted
2022-05-18 14:22:24 +02:00
// Put up a dialog explaining why we need permissions (Polite, but also Play Store policy)
// When accepted, we open the Activity for Notification access
DialogFragment dialog = new NotifyListenerPermissionsDialogFragment ( ) ;
2022-05-20 09:39:47 +02:00
dialog . show ( getSupportFragmentManager ( ) , " NotifyListenerPermissionsDialogFragment " ) ;
2020-08-01 16:27:01 +02:00
}
2016-10-21 13:01:30 +02:00
}
2020-07-27 18:42:36 +02:00
2023-06-26 13:00:05 +02:00
/ * We not put up dialogs explaining why we need permissions ( Polite , but also Play Store policy ) .
Rather than chaining the calls , we just open a bunch of dialogs . Last in this list = first
on the page , and as they are accepted the permissions are requested in turn .
When accepted , we request it or open the Activity for permission to display over other apps . * /
2016-10-21 13:01:30 +02:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
2022-05-18 14:22:24 +02:00
/ * In order to be able to set ringer mode to silent in GB ' s PhoneCallReceiver
the permission to access notifications is needed above Android M
ACCESS_NOTIFICATION_POLICY is also needed in the manifest * /
if ( pesterWithPermissions ) {
if ( ! ( ( NotificationManager ) this . getSystemService ( Context . NOTIFICATION_SERVICE ) ) . isNotificationPolicyAccessGranted ( ) ) {
// Put up a dialog explaining why we need permissions (Polite, but also Play Store policy)
// When accepted, we open the Activity for Notification access
DialogFragment dialog = new NotifyPolicyPermissionsDialogFragment ( ) ;
2022-05-20 09:39:47 +02:00
dialog . show ( getSupportFragmentManager ( ) , " NotifyPolicyPermissionsDialogFragment " ) ;
2022-05-18 14:22:24 +02:00
}
}
2022-07-19 14:04:29 +02:00
2022-10-09 14:53:04 +02:00
if ( ! Settings . canDrawOverlays ( getApplicationContext ( ) ) ) {
2022-07-19 14:04:29 +02:00
// If diplay over other apps access hasn't been granted
// Put up a dialog explaining why we need permissions (Polite, but also Play Store policy)
// When accepted, we open the Activity for permission to display over other apps.
if ( pesterWithPermissions ) {
DialogFragment dialog = new DisplayOverOthersPermissionsDialogFragment ( ) ;
dialog . show ( getSupportFragmentManager ( ) , " DisplayOverOthersPermissionsDialogFragment " ) ;
}
}
2023-06-26 13:00:05 +02:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . Q & &
ContextCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . ACCESS_BACKGROUND_LOCATION ) = = PackageManager . PERMISSION_DENIED ) {
if ( pesterWithPermissions ) {
DialogFragment dialog = new LocationPermissionsDialogFragment ( ) ;
dialog . show ( getSupportFragmentManager ( ) , " LocationPermissionsDialogFragment " ) ;
}
}
2022-10-09 14:53:04 +02:00
2022-05-18 14:22:24 +02:00
// Check all the other permissions that we need to for Android M + later
2023-06-26 13:00:05 +02:00
if ( getWantedPermissions ( ) . isEmpty ( ) )
displayPermissionDialog = false ;
if ( displayPermissionDialog & & pesterWithPermissions ) {
DialogFragment dialog = new PermissionsDialogFragment ( ) ;
dialog . show ( getSupportFragmentManager ( ) , " PermissionsDialogFragment " ) ;
// when 'ok' clicked, checkAndRequestPermissions() is called
} else
checkAndRequestPermissions ( ) ;
2016-10-21 13:01:30 +02:00
}
2017-08-14 21:35:34 +02:00
ChangeLog cl = createChangeLog ( ) ;
2016-10-21 13:01:30 +02:00
if ( cl . isFirstRun ( ) ) {
2019-10-21 21:08:09 +02:00
try {
cl . getLogDialog ( ) . show ( ) ;
2020-07-27 18:15:33 +02:00
} catch ( Exception ignored ) {
2019-10-21 21:08:09 +02:00
GB . toast ( getBaseContext ( ) , " Error showing Changelog " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
2016-10-21 13:01:30 +02:00
}
GBApplication . deviceService ( ) . start ( ) ;
if ( GB . isBluetoothEnabled ( ) & & deviceList . isEmpty ( ) & & Build . VERSION . SDK_INT < Build . VERSION_CODES . M ) {
startActivity ( new Intent ( this , DiscoveryActivity . class ) ) ;
} else {
GBApplication . deviceService ( ) . requestDeviceInfo ( ) ;
}
}
2017-09-07 23:26:24 +02:00
@Override
protected void onResume ( ) {
super . onResume ( ) ;
if ( isLanguageInvalid ) {
isLanguageInvalid = false ;
recreate ( ) ;
}
}
2017-05-28 00:19:24 +02:00
@Override
protected void onDestroy ( ) {
unregisterForContextMenu ( deviceListView ) ;
LocalBroadcastManager . getInstance ( this ) . unregisterReceiver ( mReceiver ) ;
super . onDestroy ( ) ;
}
2016-10-21 13:01:30 +02:00
@Override
public void onBackPressed ( ) {
2018-03-31 16:21:25 +02:00
DrawerLayout drawer = findViewById ( R . id . drawer_layout ) ;
2016-10-21 13:01:30 +02:00
if ( drawer . isDrawerOpen ( GravityCompat . START ) ) {
drawer . closeDrawer ( GravityCompat . START ) ;
} else {
super . onBackPressed ( ) ;
}
}
2019-09-16 17:44:59 +02:00
@Override
protected void onActivityResult ( int requestCode , int resultCode , Intent data ) {
super . onActivityResult ( requestCode , resultCode , data ) ;
if ( requestCode = = MENU_REFRESH_CODE ) {
2019-09-17 14:02:35 +02:00
showFabIfNeccessary ( ) ;
2019-09-16 17:44:59 +02:00
}
}
2016-10-21 13:01:30 +02:00
@Override
2017-03-16 17:20:18 +01:00
public boolean onNavigationItemSelected ( @NonNull MenuItem item ) {
2016-10-21 13:01:30 +02:00
2018-03-31 16:21:25 +02:00
DrawerLayout drawer = findViewById ( R . id . drawer_layout ) ;
2016-10-25 17:49:21 +02:00
drawer . closeDrawer ( GravityCompat . START ) ;
2016-10-21 13:01:30 +02:00
switch ( item . getItemId ( ) ) {
case R . id . action_settings :
Intent settingsIntent = new Intent ( this , SettingsActivity . class ) ;
2019-09-16 17:44:59 +02:00
startActivityForResult ( settingsIntent , MENU_REFRESH_CODE ) ;
2022-06-04 11:26:45 +02:00
return false ; //we do not want the drawer menu item to get selected
2016-10-21 13:01:30 +02:00
case R . id . action_debug :
Intent debugIntent = new Intent ( this , DebugActivity . class ) ;
startActivity ( debugIntent ) ;
2022-06-04 11:26:45 +02:00
return false ;
2021-01-02 16:57:41 +01:00
case R . id . action_data_management :
Intent dbIntent = new Intent ( this , DataManagementActivity . class ) ;
2016-10-21 13:01:30 +02:00
startActivity ( dbIntent ) ;
2022-06-04 11:26:45 +02:00
return false ;
2021-09-26 21:59:11 +02:00
case R . id . action_notification_management :
Intent blIntent = new Intent ( this , NotificationManagementActivity . class ) ;
2018-06-19 18:26:06 +02:00
startActivity ( blIntent ) ;
2022-06-04 11:26:45 +02:00
return false ;
2019-09-08 12:07:10 +02:00
case R . id . device_action_discover :
launchDiscoveryActivity ( ) ;
2022-06-04 11:26:45 +02:00
return false ;
2016-10-21 13:01:30 +02:00
case R . id . action_quit :
GBApplication . quit ( ) ;
2022-06-04 11:26:45 +02:00
return false ;
2017-08-18 21:51:12 +02:00
case R . id . donation_link :
Intent i = new Intent ( Intent . ACTION_VIEW , Uri . parse ( " https://liberapay.com/Gadgetbridge " ) ) ; //TODO: centralize if ever used somewhere else
i . setFlags ( Intent . FLAG_ACTIVITY_CLEAR_TASK | Intent . FLAG_ACTIVITY_NEW_TASK ) ;
startActivity ( i ) ;
2022-06-04 11:26:45 +02:00
return false ;
2017-03-11 17:10:51 +01:00
case R . id . external_changelog :
2017-08-14 21:35:34 +02:00
ChangeLog cl = createChangeLog ( ) ;
2019-10-21 21:12:07 +02:00
try {
cl . getLogDialog ( ) . show ( ) ;
} catch ( Exception ignored ) {
GB . toast ( getBaseContext ( ) , " Error showing Changelog " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
2022-06-04 11:26:45 +02:00
return false ;
2020-07-11 17:04:29 +02:00
case R . id . about :
Intent aboutIntent = new Intent ( this , AboutActivity . class ) ;
startActivity ( aboutIntent ) ;
2022-06-04 11:26:45 +02:00
return false ;
2016-10-21 13:01:30 +02:00
}
2022-06-04 11:26:45 +02:00
return false ;
2016-10-21 13:01:30 +02:00
}
2017-08-14 21:35:34 +02:00
private ChangeLog createChangeLog ( ) {
String css = ChangeLog . DEFAULT_CSS ;
css + = " body { "
+ " color: " + AndroidUtils . getTextColorHex ( getBaseContext ( ) ) + " ; "
+ " background-color: " + AndroidUtils . getBackgroundColorHex ( getBaseContext ( ) ) + " ; " +
" } " ;
2019-09-17 14:02:35 +02:00
return new ChangeLog ( this , css ) ;
}
2016-10-21 13:01:30 +02:00
private void launchDiscoveryActivity ( ) {
startActivity ( new Intent ( this , DiscoveryActivity . class ) ) ;
}
private void refreshPairedDevices ( ) {
mGBDeviceAdapter . notifyDataSetChanged ( ) ;
}
2019-09-17 14:02:35 +02:00
private void showFabIfNeccessary ( ) {
if ( GBApplication . getPrefs ( ) . getBoolean ( " display_add_device_fab " , true ) ) {
fab . show ( ) ;
} else {
2020-11-01 11:19:20 +01:00
if ( deviceManager . getDevices ( ) . size ( ) < 1 ) {
2019-09-17 14:02:35 +02:00
fab . show ( ) ;
} else {
fab . hide ( ) ;
}
}
}
2023-06-26 13:00:05 +02:00
private void checkAndRequestLocationPermissions ( ) {
if ( ActivityCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . ACCESS_BACKGROUND_LOCATION ) ! = PackageManager . PERMISSION_GRANTED ) {
LOG . error ( " No permission to access background location! " ) ;
toast ( ControlCenterv2 . this , getString ( R . string . error_no_location_access ) , Toast . LENGTH_SHORT , GB . ERROR ) ;
ActivityCompat . requestPermissions ( this , new String [ ] { Manifest . permission . ACCESS_BACKGROUND_LOCATION } , 0 ) ;
}
}
2016-10-21 13:01:30 +02:00
@TargetApi ( Build . VERSION_CODES . M )
2023-06-26 13:00:05 +02:00
private List < String > getWantedPermissions ( ) {
2016-10-21 13:01:30 +02:00
List < String > wantedPermissions = new ArrayList < > ( ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . BLUETOOTH ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . BLUETOOTH ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . BLUETOOTH_ADMIN ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . BLUETOOTH_ADMIN ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_CONTACTS ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_CONTACTS ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . CALL_PHONE ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . CALL_PHONE ) ;
2018-09-05 20:41:01 +02:00
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_CALL_LOG ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_CALL_LOG ) ;
2016-10-21 13:01:30 +02:00
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_PHONE_STATE ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_PHONE_STATE ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . PROCESS_OUTGOING_CALLS ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . PROCESS_OUTGOING_CALLS ) ;
2018-03-17 15:58:44 +01:00
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . RECEIVE_SMS ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . RECEIVE_SMS ) ;
2016-10-21 13:01:30 +02:00
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_SMS ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_SMS ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . SEND_SMS ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . SEND_SMS ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_EXTERNAL_STORAGE ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_EXTERNAL_STORAGE ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . READ_CALENDAR ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . READ_CALENDAR ) ;
2020-08-01 21:20:53 +02:00
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . ACCESS_FINE_LOCATION ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . ACCESS_FINE_LOCATION ) ;
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . ACCESS_COARSE_LOCATION ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . ACCESS_COARSE_LOCATION ) ;
2020-07-28 11:27:59 +02:00
2018-04-07 00:24:38 +02:00
try {
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . MEDIA_CONTENT_CONTROL ) = = PackageManager . PERMISSION_DENIED )
wantedPermissions . add ( Manifest . permission . MEDIA_CONTENT_CONTROL ) ;
2020-07-27 18:15:33 +02:00
} catch ( Exception ignored ) {
2018-04-07 00:24:38 +02:00
}
2016-10-21 13:01:30 +02:00
2020-07-28 11:27:59 +02:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . O ) {
2020-08-01 16:27:01 +02:00
if ( pesterWithPermissions ) {
if ( ContextCompat . checkSelfPermission ( this , Manifest . permission . ANSWER_PHONE_CALLS ) = = PackageManager . PERMISSION_DENIED ) {
wantedPermissions . add ( Manifest . permission . ANSWER_PHONE_CALLS ) ;
}
}
2020-07-28 11:27:59 +02:00
}
2022-10-09 14:53:04 +02:00
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . R ) {
if ( ActivityCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . QUERY_ALL_PACKAGES ) = = PackageManager . PERMISSION_DENIED ) {
wantedPermissions . add ( Manifest . permission . QUERY_ALL_PACKAGES ) ;
}
}
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . S ) {
if ( ActivityCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . BLUETOOTH_SCAN ) = = PackageManager . PERMISSION_DENIED ) {
wantedPermissions . add ( Manifest . permission . BLUETOOTH_SCAN ) ;
}
if ( ActivityCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . BLUETOOTH_CONNECT ) = = PackageManager . PERMISSION_DENIED ) {
wantedPermissions . add ( Manifest . permission . BLUETOOTH_CONNECT ) ;
}
}
2022-03-31 11:36:26 +02:00
if ( BuildConfig . INTERNET_ACCESS ) {
if ( ActivityCompat . checkSelfPermission ( getApplicationContext ( ) , Manifest . permission . INTERNET ) = = PackageManager . PERMISSION_DENIED ) {
wantedPermissions . add ( Manifest . permission . INTERNET ) ;
}
}
2023-06-26 13:00:05 +02:00
return wantedPermissions ;
}
@TargetApi ( Build . VERSION_CODES . M )
private void checkAndRequestPermissions ( ) {
List < String > wantedPermissions = getWantedPermissions ( ) ;
2020-07-28 04:17:29 +02:00
if ( ! wantedPermissions . isEmpty ( ) ) {
Prefs prefs = GBApplication . getPrefs ( ) ;
// If this is not the first run, we can rely on
// shouldShowRequestPermissionRationale(String permission)
// and ignore permissions that shouldn't or can't be requested again
if ( prefs . getBoolean ( " permissions_asked " , false ) ) {
// Don't request permissions that we shouldn't show a prompt for
// e.g. permissions that are "Never" granted by the user or never granted by the system
Set < String > shouldNotAsk = new HashSet < > ( ) ;
for ( String wantedPermission : wantedPermissions ) {
if ( ! shouldShowRequestPermissionRationale ( wantedPermission ) ) {
shouldNotAsk . add ( wantedPermission ) ;
}
}
wantedPermissions . removeAll ( shouldNotAsk ) ;
2023-06-26 13:00:05 +02:00
} else {
2020-07-28 04:17:29 +02:00
// Permissions have not been asked yet, but now will be
prefs . getPreferences ( ) . edit ( ) . putBoolean ( " permissions_asked " , true ) . apply ( ) ;
}
if ( ! wantedPermissions . isEmpty ( ) ) {
2023-06-26 13:00:05 +02:00
GB . toast ( this , getString ( R . string . permission_granting_mandatory ) , Toast . LENGTH_LONG , GB . ERROR ) ;
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . S ) {
ActivityCompat . requestPermissions ( this , wantedPermissions . toArray ( new String [ 0 ] ) , 0 ) ;
2022-05-20 09:39:47 +02:00
} else {
2023-06-26 13:00:05 +02:00
requestMultiplePermissionsLauncher . launch ( wantedPermissions . toArray ( new String [ 0 ] ) ) ;
2022-05-20 09:39:47 +02:00
}
2020-07-28 04:17:29 +02:00
}
}
2019-09-21 10:07:58 +02:00
2022-10-09 14:53:04 +02:00
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . S ) { // The enclosed hack in it's current state cause crash on Banglejs builds tarkgetSDK=31 on a Android 13 device.
// HACK: On Lineage we have to do this so that the permission dialog pops up
if ( fakeStateListener = = null ) {
fakeStateListener = new PhoneStateListener ( ) ;
TelephonyManager telephonyManager = ( TelephonyManager ) getSystemService ( TELEPHONY_SERVICE ) ;
telephonyManager . listen ( fakeStateListener , PhoneStateListener . LISTEN_CALL_STATE ) ;
telephonyManager . listen ( fakeStateListener , PhoneStateListener . LISTEN_NONE ) ;
}
2019-09-21 10:07:58 +02:00
}
2016-10-21 13:01:30 +02:00
}
2017-09-07 23:26:24 +02:00
public void setLanguage ( Locale language , boolean invalidateLanguage ) {
if ( invalidateLanguage ) {
isLanguageInvalid = true ;
}
AndroidUtils . setLanguage ( this , language ) ;
2017-07-31 22:49:05 +02:00
}
2021-11-18 12:22:43 +01:00
private long [ ] getSteps ( GBDevice device , DBHandler db ) {
Calendar day = GregorianCalendar . getInstance ( ) ;
DailyTotals ds = new DailyTotals ( ) ;
return ds . getDailyTotalsForDevice ( device , day , db ) ;
}
protected RefreshTask createRefreshTask ( String task , Context context ) {
return new RefreshTask ( task , context ) ;
}
public class RefreshTask extends DBAccess {
public RefreshTask ( String task , Context context ) {
super ( task , context ) ;
}
@Override
protected void doInBackground ( DBHandler db ) {
for ( GBDevice gbDevice : deviceList ) {
final DeviceCoordinator coordinator = DeviceHelper . getInstance ( ) . getCoordinator ( gbDevice ) ;
2021-11-18 16:00:46 +01:00
if ( coordinator . supportsActivityTracking ( ) ) {
2021-12-05 10:32:35 +01:00
long [ ] stepsAndSleepData = getSteps ( gbDevice , db ) ;
deviceActivityHashMap . put ( gbDevice . getAddress ( ) , stepsAndSleepData ) ;
2021-11-18 12:22:43 +01:00
}
}
}
@Override
protected void onPostExecute ( Object o ) {
refreshPairedDevices ( ) ;
}
}
2022-05-18 14:22:24 +02:00
/// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity
public static class NotifyPolicyPermissionsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog ( Bundle savedInstanceState ) {
// Use the Builder class for convenient dialog construction
AlertDialog . Builder builder = new AlertDialog . Builder ( getActivity ( ) ) ;
2022-07-25 16:08:53 +02:00
final Context context = getContext ( ) ;
2022-05-18 14:22:24 +02:00
builder . setMessage ( context . getString ( R . string . permission_notification_policy_access ,
getContext ( ) . getString ( R . string . app_name ) ,
getContext ( ) . getString ( R . string . ok ) ) )
. setPositiveButton ( R . string . ok , new DialogInterface . OnClickListener ( ) {
2022-10-09 14:53:04 +02:00
@RequiresApi ( api = Build . VERSION_CODES . M )
2022-05-18 14:22:24 +02:00
public void onClick ( DialogInterface dialog , int id ) {
2022-07-25 16:08:53 +02:00
try {
startActivity ( new Intent ( android . provider . Settings . ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS ) ) ;
} catch ( ActivityNotFoundException e ) {
GB . toast ( context , " 'Notification Policy' activity not found " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
2022-05-18 14:22:24 +02:00
}
} ) ;
return builder . create ( ) ;
}
}
/// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity
public static class NotifyListenerPermissionsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog ( Bundle savedInstanceState ) {
// Use the Builder class for convenient dialog construction
AlertDialog . Builder builder = new AlertDialog . Builder ( getActivity ( ) ) ;
2022-07-25 16:08:53 +02:00
final Context context = getContext ( ) ;
2022-05-18 14:22:24 +02:00
builder . setMessage ( context . getString ( R . string . permission_notification_listener ,
getContext ( ) . getString ( R . string . app_name ) ,
getContext ( ) . getString ( R . string . ok ) ) )
. setPositiveButton ( R . string . ok , new DialogInterface . OnClickListener ( ) {
public void onClick ( DialogInterface dialog , int id ) {
2022-07-25 16:08:53 +02:00
try {
startActivity ( new Intent ( " android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS " ) ) ;
} catch ( ActivityNotFoundException e ) {
GB . toast ( context , " 'Notification Listener Settings' activity not found " , Toast . LENGTH_LONG , GB . ERROR ) ;
}
2022-05-18 14:22:24 +02:00
}
} ) ;
return builder . create ( ) ;
}
}
2022-07-19 14:04:29 +02:00
/// Called from onCreate - this puts up a dialog explaining we need permissions, and goes to the correct Activity
public static class DisplayOverOthersPermissionsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog ( Bundle savedInstanceState ) {
// Use the Builder class for convenient dialog construction
AlertDialog . Builder builder = new AlertDialog . Builder ( getActivity ( ) ) ;
Context context = getContext ( ) ;
builder . setMessage ( context . getString ( R . string . permission_display_over_other_apps ,
getContext ( ) . getString ( R . string . app_name ) ,
getContext ( ) . getString ( R . string . ok ) ) )
. setPositiveButton ( R . string . ok , new DialogInterface . OnClickListener ( ) {
2022-10-09 14:53:04 +02:00
@RequiresApi ( api = Build . VERSION_CODES . M )
2022-07-19 14:04:29 +02:00
public void onClick ( DialogInterface dialog , int id ) {
Intent enableIntent = new Intent ( android . provider . Settings . ACTION_MANAGE_OVERLAY_PERMISSION ) ;
startActivity ( enableIntent ) ;
}
2022-08-21 12:26:09 +02:00
} ) . setNegativeButton ( R . string . dismiss , new DialogInterface . OnClickListener ( ) {
public void onClick ( DialogInterface dialog , int id ) { }
2022-07-19 14:04:29 +02:00
} ) ;
return builder . create ( ) ;
}
}
2022-10-09 14:53:04 +02:00
2023-06-26 13:00:05 +02:00
/// Called from onCreate - this puts up a dialog explaining we need backgound location permissions, and then requests permissions when 'ok' pressed
2022-05-20 09:39:47 +02:00
public static class LocationPermissionsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog ( Bundle savedInstanceState ) {
// Use the Builder class for convenient dialog construction
AlertDialog . Builder builder = new AlertDialog . Builder ( getActivity ( ) ) ;
Context context = getContext ( ) ;
builder . setMessage ( context . getString ( R . string . permission_location ,
getContext ( ) . getString ( R . string . app_name ) ,
getContext ( ) . getString ( R . string . ok ) ) )
. setPositiveButton ( R . string . ok , new DialogInterface . OnClickListener ( ) {
public void onClick ( DialogInterface dialog , int id ) {
2023-06-26 13:00:05 +02:00
Intent intent = new Intent ( ACTION_REQUEST_LOCATION_PERMISSIONS ) ;
2022-05-20 09:39:47 +02:00
LocalBroadcastManager . getInstance ( getContext ( ) ) . sendBroadcast ( intent ) ;
}
} ) ;
return builder . create ( ) ;
}
}
2022-10-09 14:53:04 +02:00
// Register the permissions callback, which handles the user's response to the
// system permissions dialog. Save the return value, an instance of
// ActivityResultLauncher, as an instance variable.
2023-06-26 13:00:05 +02:00
// This is required here rather than where it is used because it'll cause a
// "LifecycleOwners must call register before they are STARTED" if not called from onCreate
2022-10-09 14:53:04 +02:00
public ActivityResultLauncher < String [ ] > requestMultiplePermissionsLauncher =
registerForActivityResult ( new ActivityResultContracts . RequestMultiplePermissions ( ) , isGranted - > {
if ( isGranted . containsValue ( true ) ) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// feature requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
GB . toast ( this , getString ( R . string . permission_granting_mandatory ) , Toast . LENGTH_LONG , GB . ERROR ) ;
}
} ) ;
2023-06-26 13:00:05 +02:00
/// Called from onCreate - this puts up a dialog explaining we need permissions, and then requests permissions when 'ok' pressed
public static class PermissionsDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog ( Bundle savedInstanceState ) {
// Use the Builder class for convenient dialog construction
AlertDialog . Builder builder = new AlertDialog . Builder ( getActivity ( ) ) ;
Context context = getContext ( ) ;
builder . setMessage ( context . getString ( R . string . permission_request ,
getContext ( ) . getString ( R . string . app_name ) ,
getContext ( ) . getString ( R . string . ok ) ) )
. setPositiveButton ( R . string . ok , new DialogInterface . OnClickListener ( ) {
public void onClick ( DialogInterface dialog , int id ) {
Intent intent = new Intent ( ACTION_REQUEST_PERMISSIONS ) ;
LocalBroadcastManager . getInstance ( getContext ( ) ) . sendBroadcast ( intent ) ;
}
} ) ;
return builder . create ( ) ;
}
}
2016-10-21 13:01:30 +02:00
}