mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-29 08:20:16 +02:00
Add support for (explicit) service intents. Add support for setting flags for intents. Add support for setting multiple categories for intents. Add ability for Gadgetbridge to wake the Android device and leave the lock screen to start activities when it is sleeping. A new activity 'WakeActivity' is used for this. (Must use 'trusted device' in Android) Add dismiss-button to 'display over other apps' permission pop up. Bangle.js can send "gadgetbridge" as package info to accomodate the different GB build variants/flavours. Use only getContext() and not getApplicationContext() when executing the intents.
665 lines
31 KiB
Java
665 lines
31 KiB
Java
/* Copyright (C) 2016-2020 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
|
Gobbetti, Johannes Tysiak, Taavi Eomäe, vanous
|
|
|
|
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/>. */
|
|
package nodomain.freeyourgadget.gadgetbridge.activities;
|
|
|
|
import android.Manifest;
|
|
import android.annotation.TargetApi;
|
|
import android.app.AlertDialog;
|
|
import android.app.Dialog;
|
|
import android.app.NotificationManager;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.PackageManager;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.telephony.PhoneStateListener;
|
|
import android.telephony.TelephonyManager;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.appcompat.app.AppCompatDelegate;
|
|
import androidx.appcompat.view.menu.MenuItemImpl;
|
|
import androidx.appcompat.widget.Toolbar;
|
|
import androidx.core.app.ActivityCompat;
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.core.view.GravityCompat;
|
|
import androidx.drawerlayout.widget.DrawerLayout;
|
|
import androidx.fragment.app.DialogFragment;
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
import com.google.android.material.navigation.NavigationView;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.GregorianCalendar;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
import de.cketti.library.changelog.ChangeLog;
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
|
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
|
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapterv2;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
|
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
|
|
|
//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
|
|
public class ControlCenterv2 extends AppCompatActivity
|
|
implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
|
|
|
|
public static final int MENU_REFRESH_CODE = 1;
|
|
public static final String ACTION_REQUEST_PERMISSIONS
|
|
= "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions";
|
|
private static PhoneStateListener fakeStateListener;
|
|
|
|
//needed for KK compatibility
|
|
static {
|
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
|
}
|
|
|
|
private DeviceManager deviceManager;
|
|
private GBDeviceAdapterv2 mGBDeviceAdapter;
|
|
private RecyclerView deviceListView;
|
|
private FloatingActionButton fab;
|
|
private boolean isLanguageInvalid = false;
|
|
List<GBDevice> deviceList;
|
|
private HashMap<String,long[]> deviceActivityHashMap = new HashMap();
|
|
|
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
switch (Objects.requireNonNull(action)) {
|
|
case GBApplication.ACTION_LANGUAGE_CHANGE:
|
|
setLanguage(GBApplication.getLanguage(), true);
|
|
break;
|
|
case GBApplication.ACTION_QUIT:
|
|
finish();
|
|
break;
|
|
case DeviceManager.ACTION_DEVICES_CHANGED:
|
|
case GBApplication.ACTION_NEW_DATA:
|
|
createRefreshTask("get activity data", getApplication()).execute();
|
|
mGBDeviceAdapter.rebuildFolders();
|
|
refreshPairedDevices();
|
|
break;
|
|
case DeviceService.ACTION_REALTIME_SAMPLES:
|
|
handleRealtimeSample(intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
|
|
break;
|
|
case ACTION_REQUEST_PERMISSIONS:
|
|
checkAndRequestPermissions(false);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
private boolean pesterWithPermissions = true;
|
|
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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
|
|
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_controlcenterv2);
|
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
setSupportActionBar(toolbar);
|
|
|
|
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
|
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
|
|
this, drawer, toolbar, R.string.controlcenter_navigation_drawer_open, R.string.controlcenter_navigation_drawer_close);
|
|
drawer.setDrawerListener(toggle);
|
|
toggle.syncState();
|
|
|
|
/* 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);
|
|
}
|
|
|
|
NavigationView navigationView = findViewById(R.id.nav_view);
|
|
navigationView.setNavigationItemSelectedListener(this);
|
|
|
|
//end of material design boilerplate
|
|
deviceManager = ((GBApplication) getApplication()).getDeviceManager();
|
|
|
|
deviceListView = findViewById(R.id.deviceListView);
|
|
deviceListView.setHasFixedSize(true);
|
|
deviceListView.setLayoutManager(new LinearLayoutManager(this));
|
|
|
|
deviceList = deviceManager.getDevices();
|
|
mGBDeviceAdapter = new GBDeviceAdapterv2(this, deviceList, deviceActivityHashMap);
|
|
mGBDeviceAdapter.setHasStableIds(true);
|
|
|
|
// get activity data asynchronously, this fills the deviceActivityHashMap
|
|
// and calls refreshPairedDevices() → notifyDataSetChanged
|
|
createRefreshTask("get activity data", getApplication()).execute();
|
|
|
|
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
|
|
|
fab = findViewById(R.id.fab);
|
|
fab.setOnClickListener(new View.OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
launchDiscoveryActivity();
|
|
}
|
|
});
|
|
|
|
showFabIfNeccessary();
|
|
|
|
/* uncomment to enable fixed-swipe to reveal more actions
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
|
|
RecyclerView.ViewHolder viewHolder, float dX, float dY,
|
|
int actionState, boolean isCurrentlyActive) {
|
|
}
|
|
});
|
|
|
|
swipeToDismissTouchHelper.attachToRecyclerView(deviceListView);
|
|
*/
|
|
|
|
registerForContextMenu(deviceListView);
|
|
|
|
IntentFilter filterLocal = new IntentFilter();
|
|
filterLocal.addAction(GBApplication.ACTION_LANGUAGE_CHANGE);
|
|
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
|
filterLocal.addAction(GBApplication.ACTION_NEW_DATA);
|
|
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
|
|
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
|
filterLocal.addAction(ACTION_REQUEST_PERMISSIONS);
|
|
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
|
|
|
refreshPairedDevices();
|
|
|
|
/*
|
|
* Ask for permission to intercept notifications on first run.
|
|
*/
|
|
Prefs prefs = GBApplication.getPrefs();
|
|
pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
|
|
|
|
Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(this);
|
|
if (pesterWithPermissions) {
|
|
if (!set.contains(this.getPackageName())) { // If notification listener 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 Notification access
|
|
DialogFragment dialog = new NotifyListenerPermissionsDialogFragment();
|
|
dialog.show(getSupportFragmentManager(), "NotifyListenerPermissionsDialogFragment");
|
|
}
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
/* 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();
|
|
dialog.show(getSupportFragmentManager(), "NotifyPolicyPermissionsDialogFragment");
|
|
}
|
|
}
|
|
|
|
if (!android.provider.Settings.canDrawOverlays(getApplicationContext())) {
|
|
// 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");
|
|
}
|
|
}
|
|
|
|
// Check all the other permissions that we need to for Android M + later
|
|
checkAndRequestPermissions(true);
|
|
}
|
|
|
|
ChangeLog cl = createChangeLog();
|
|
if (cl.isFirstRun()) {
|
|
try {
|
|
cl.getLogDialog().show();
|
|
} catch (Exception ignored) {
|
|
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
if (isLanguageInvalid) {
|
|
isLanguageInvalid = false;
|
|
recreate();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
unregisterForContextMenu(deviceListView);
|
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
|
if (drawer.isDrawerOpen(GravityCompat.START)) {
|
|
drawer.closeDrawer(GravityCompat.START);
|
|
} else {
|
|
super.onBackPressed();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
if (requestCode == MENU_REFRESH_CODE) {
|
|
showFabIfNeccessary();
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
|
|
|
DrawerLayout drawer = findViewById(R.id.drawer_layout);
|
|
drawer.closeDrawer(GravityCompat.START);
|
|
|
|
switch (item.getItemId()) {
|
|
case R.id.action_settings:
|
|
Intent settingsIntent = new Intent(this, SettingsActivity.class);
|
|
startActivityForResult(settingsIntent, MENU_REFRESH_CODE);
|
|
return false; //we do not want the drawer menu item to get selected
|
|
case R.id.action_debug:
|
|
Intent debugIntent = new Intent(this, DebugActivity.class);
|
|
startActivity(debugIntent);
|
|
return false;
|
|
case R.id.action_data_management:
|
|
Intent dbIntent = new Intent(this, DataManagementActivity.class);
|
|
startActivity(dbIntent);
|
|
return false;
|
|
case R.id.action_notification_management:
|
|
Intent blIntent = new Intent(this, NotificationManagementActivity.class);
|
|
startActivity(blIntent);
|
|
return false;
|
|
case R.id.device_action_discover:
|
|
launchDiscoveryActivity();
|
|
return false;
|
|
case R.id.action_quit:
|
|
GBApplication.quit();
|
|
return false;
|
|
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);
|
|
return false;
|
|
case R.id.external_changelog:
|
|
ChangeLog cl = createChangeLog();
|
|
try {
|
|
cl.getLogDialog().show();
|
|
} catch (Exception ignored) {
|
|
GB.toast(getBaseContext(), "Error showing Changelog", Toast.LENGTH_LONG, GB.ERROR);
|
|
}
|
|
return false;
|
|
case R.id.about:
|
|
Intent aboutIntent = new Intent(this, AboutActivity.class);
|
|
startActivity(aboutIntent);
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private ChangeLog createChangeLog() {
|
|
String css = ChangeLog.DEFAULT_CSS;
|
|
css += "body { "
|
|
+ "color: " + AndroidUtils.getTextColorHex(getBaseContext()) + "; "
|
|
+ "background-color: " + AndroidUtils.getBackgroundColorHex(getBaseContext()) + ";" +
|
|
"}";
|
|
return new ChangeLog(this, css);
|
|
}
|
|
|
|
private void launchDiscoveryActivity() {
|
|
startActivity(new Intent(this, DiscoveryActivity.class));
|
|
}
|
|
|
|
private void refreshPairedDevices() {
|
|
mGBDeviceAdapter.notifyDataSetChanged();
|
|
}
|
|
|
|
private void showFabIfNeccessary() {
|
|
if (GBApplication.getPrefs().getBoolean("display_add_device_fab", true)) {
|
|
fab.show();
|
|
} else {
|
|
if (deviceManager.getDevices().size() < 1) {
|
|
fab.show();
|
|
} else {
|
|
fab.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
@TargetApi(Build.VERSION_CODES.M)
|
|
private void checkAndRequestPermissions(boolean showDialogFirst) {
|
|
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);
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) == PackageManager.PERMISSION_DENIED)
|
|
wantedPermissions.add(Manifest.permission.READ_CALL_LOG);
|
|
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);
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_DENIED)
|
|
wantedPermissions.add(Manifest.permission.RECEIVE_SMS);
|
|
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);
|
|
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);
|
|
|
|
try {
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.MEDIA_CONTENT_CONTROL) == PackageManager.PERMISSION_DENIED)
|
|
wantedPermissions.add(Manifest.permission.MEDIA_CONTENT_CONTROL);
|
|
} catch (Exception ignored) {
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
if (pesterWithPermissions) {
|
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ANSWER_PHONE_CALLS) == PackageManager.PERMISSION_DENIED) {
|
|
wantedPermissions.add(Manifest.permission.ANSWER_PHONE_CALLS);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
|
wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
|
}
|
|
}
|
|
|
|
if (BuildConfig.INTERNET_ACCESS) {
|
|
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) {
|
|
wantedPermissions.add(Manifest.permission.INTERNET);
|
|
}
|
|
}
|
|
|
|
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);
|
|
} else if (!showDialogFirst) {
|
|
// Permissions have not been asked yet, but now will be
|
|
prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply();
|
|
}
|
|
|
|
if (!wantedPermissions.isEmpty()) {
|
|
if (showDialogFirst) {
|
|
// Show a dialog - thus will then call checkAndRequestPermissions(false)
|
|
DialogFragment dialog = new LocationPermissionsDialogFragment();
|
|
dialog.show(getSupportFragmentManager(), "LocationPermissionsDialogFragment");
|
|
} else {
|
|
GB.toast(this, getString(R.string.permission_granting_mandatory), Toast.LENGTH_LONG, GB.ERROR);
|
|
ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[0]), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
public void setLanguage(Locale language, boolean invalidateLanguage) {
|
|
if (invalidateLanguage) {
|
|
isLanguageInvalid = true;
|
|
}
|
|
AndroidUtils.setLanguage(this, language);
|
|
}
|
|
|
|
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);
|
|
if (coordinator.supportsActivityTracking()) {
|
|
long[] stepsAndSleepData = getSteps(gbDevice, db);
|
|
deviceActivityHashMap.put(gbDevice.getAddress(), stepsAndSleepData);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPostExecute(Object o) {
|
|
refreshPairedDevices();
|
|
}
|
|
|
|
}
|
|
|
|
/// 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());
|
|
final Context context = getContext();
|
|
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() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
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());
|
|
final Context context = getContext();
|
|
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) {
|
|
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);
|
|
}
|
|
}
|
|
});
|
|
return builder.create();
|
|
}
|
|
}
|
|
|
|
/// 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() {
|
|
public void onClick(DialogInterface dialog, int id) {
|
|
Intent enableIntent = new Intent(android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
|
|
startActivity(enableIntent);
|
|
}
|
|
}).setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() {
|
|
public void onClick(DialogInterface dialog, int id) {}
|
|
});
|
|
return builder.create();
|
|
}
|
|
}
|
|
|
|
/// Called from checkAndRequestPermissions - this puts up a dialog explaining we need permissions, and then calls checkAndRequestPermissions (via an intent) when 'ok' pressed
|
|
public static class LocationPermissionsDialogFragment extends DialogFragment {
|
|
ControlCenterv2 controlCenter;
|
|
|
|
@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) {
|
|
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
|
|
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
|
}
|
|
});
|
|
return builder.create();
|
|
}
|
|
}
|
|
}
|