mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-26 20:06:52 +01:00
Compare commits
17 Commits
97cba3f5e6
...
d9b59df637
Author | SHA1 | Date | |
---|---|---|---|
|
d9b59df637 | ||
|
0288db15b6 | ||
|
21a3c73ca2 | ||
|
44772e3dc4 | ||
|
2892e3a08b | ||
|
9877e24182 | ||
|
01cdcc4b6f | ||
|
12cb5627f8 | ||
|
6d6c461a6a | ||
|
f017e454de | ||
|
b88464b9d0 | ||
|
06b14248d0 | ||
|
edf94625b8 | ||
|
581706b1f8 | ||
|
6f2a29f7c1 | ||
|
1145813ed2 | ||
|
5b515319bc |
@ -150,6 +150,14 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".activities.welcome.WelcomeActivity"
|
||||
android:label="@string/first_start_welcome_title"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.PermissionsActivity"
|
||||
android:label="@string/first_start_permissions_title"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
|
@ -127,7 +127,7 @@ public class GBApplication extends Application {
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||
private static final int CURRENT_PREFS_VERSION = 42;
|
||||
private static final int CURRENT_PREFS_VERSION = 43;
|
||||
|
||||
private static final LimitedQueue<Integer, String> mIDSenderLookup = new LimitedQueue<>(16);
|
||||
private static GBPrefs prefs;
|
||||
@ -1838,6 +1838,20 @@ public class GBApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion < 43) {
|
||||
// Users upgrading to this version don't need to see the welcome screen
|
||||
try (DBHandler db = acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
final List<Device> activeDevices = DBHelper.getActiveDevices(daoSession);
|
||||
|
||||
if (!activeDevices.isEmpty()) {
|
||||
editor.putBoolean("first_run", false);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
Log.e(TAG, "Failed to migrate prefs to version 42", e);
|
||||
}
|
||||
}
|
||||
|
||||
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
|
||||
editor.apply();
|
||||
}
|
||||
|
@ -21,46 +21,29 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
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.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.PhoneStateListener;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.core.view.MenuProvider;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
@ -70,26 +53,22 @@ import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
@ -98,6 +77,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
//TODO: extend AbstractGBActivity, but it requires actionbar that is not available
|
||||
@ -105,16 +85,11 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
implements NavigationView.OnNavigationItemSelectedListener, GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ControlCenterv2.class);
|
||||
public static final int MENU_REFRESH_CODE = 1;
|
||||
public static final String ACTION_REQUEST_PERMISSIONS
|
||||
= "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestpermissions";
|
||||
public static final String ACTION_REQUEST_LOCATION_PERMISSIONS
|
||||
= "nodomain.freeyourgadget.gadgetbridge.activities.controlcenter.requestlocationpermissions";
|
||||
private boolean isLanguageInvalid = false;
|
||||
private boolean isThemeInvalid = false;
|
||||
private ViewPager2 viewPager;
|
||||
private FragmentStateAdapter pagerAdapter;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private static PhoneStateListener fakeStateListener;
|
||||
private AlertDialog clDialog;
|
||||
|
||||
//needed for KK compatibility
|
||||
@ -140,12 +115,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
final GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
handleRealtimeSample(device, intent.getSerializableExtra(DeviceService.EXTRA_REALTIME_SAMPLE));
|
||||
break;
|
||||
case ACTION_REQUEST_PERMISSIONS:
|
||||
checkAndRequestPermissions();
|
||||
break;
|
||||
case ACTION_REQUEST_LOCATION_PERMISSIONS:
|
||||
checkAndRequestLocationPermissions();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (dev != null && !dev.isBusy()) {
|
||||
@ -310,79 +279,21 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
filterLocal.addAction(GBApplication.ACTION_THEME_CHANGE);
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(DeviceService.ACTION_REALTIME_SAMPLES);
|
||||
filterLocal.addAction(ACTION_REQUEST_PERMISSIONS);
|
||||
filterLocal.addAction(ACTION_REQUEST_LOCATION_PERMISSIONS);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
/*
|
||||
* Ask for permission to intercept notifications on first run.
|
||||
*/
|
||||
// Open the Welcome flow on first run, only check permissions on next runs
|
||||
boolean firstRun = prefs.getBoolean("first_run", true);
|
||||
if (firstRun) {
|
||||
launchWelcomeActivity();
|
||||
} else {
|
||||
pesterWithPermissions = prefs.getBoolean("permission_pestering", true);
|
||||
|
||||
boolean displayPermissionDialog = !prefs.getBoolean("permission_dialog_displayed", false);
|
||||
prefs.getPreferences().edit().putBoolean("permission_dialog_displayed", true).apply();
|
||||
|
||||
|
||||
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 (pesterWithPermissions && !PermissionsUtils.checkAllPermissions(this)) {
|
||||
Intent permissionsIntent = new Intent(this, PermissionsActivity.class);
|
||||
startActivity(permissionsIntent);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
|
||||
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 (!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");
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// Check all the other permissions that we need to for Android M + later
|
||||
if (getWantedPermissions().isEmpty())
|
||||
displayPermissionDialog = false;
|
||||
if (displayPermissionDialog && pesterWithPermissions) {
|
||||
DialogFragment dialog = new PermissionsDialogFragment();
|
||||
dialog.show(getSupportFragmentManager(), "PermissionsDialogFragment");
|
||||
// when 'ok' clicked, checkAndRequestPermissions() is called
|
||||
} else
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
GBChangeLog cl = GBChangeLog.createChangeLog(this);
|
||||
boolean showChangelog = prefs.getBoolean("show_changelog", true);
|
||||
if (showChangelog && cl.isFirstRun() && cl.hasChanges(cl.isFirstRunEver())) {
|
||||
@ -473,6 +384,10 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
|
||||
|
||||
private void launchWelcomeActivity() {
|
||||
startActivity(new Intent(this, WelcomeActivity.class));
|
||||
}
|
||||
|
||||
private void launchDiscoveryActivity() {
|
||||
startActivity(new Intent(this, DiscoveryActivityV2.class));
|
||||
}
|
||||
@ -489,145 +404,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAndRequestLocationPermissions() {
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
LOG.error("No permission to access background location!");
|
||||
GB.toast(getString(R.string.error_no_location_access), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private List<String> getWantedPermissions() {
|
||||
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 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.S) {
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.POST_NOTIFICATIONS);
|
||||
}
|
||||
}
|
||||
|
||||
if (BuildConfig.INTERNET_ACCESS) {
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.INTERNET) == PackageManager.PERMISSION_DENIED) {
|
||||
wantedPermissions.add(Manifest.permission.INTERNET);
|
||||
}
|
||||
}
|
||||
|
||||
return wantedPermissions;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void checkAndRequestPermissions() {
|
||||
List<String> wantedPermissions = getWantedPermissions();
|
||||
|
||||
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 {
|
||||
// Permissions have not been asked yet, but now will be
|
||||
prefs.getPreferences().edit().putBoolean("permissions_asked", true).apply();
|
||||
}
|
||||
|
||||
if (!wantedPermissions.isEmpty()) {
|
||||
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);
|
||||
} else {
|
||||
requestMultiplePermissionsLauncher.launch(wantedPermissions.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLanguage(Locale language, boolean invalidateLanguage) {
|
||||
if (invalidateLanguage) {
|
||||
isLanguageInvalid = true;
|
||||
@ -635,137 +411,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
AndroidUtils.setLanguage(this, language);
|
||||
}
|
||||
|
||||
/// 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
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
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
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
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 onCreate - this puts up a dialog explaining we need backgound location permissions, and then requests permissions when 'ok' pressed
|
||||
public static class LocationPermissionsDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
// Use the Builder class for convenient dialog construction
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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_LOCATION_PERMISSIONS);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
/// 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
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(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();
|
||||
}
|
||||
}
|
||||
|
||||
private class MainFragmentsPagerAdapter extends FragmentStateAdapter {
|
||||
public MainFragmentsPagerAdapter(FragmentActivity fa) {
|
||||
super(fa);
|
||||
|
@ -0,0 +1,38 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeFragmentPermissions;
|
||||
|
||||
public class PermissionsActivity extends AbstractGBActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_permissions);
|
||||
|
||||
WelcomeFragmentPermissions permissionsFragment = new WelcomeFragmentPermissions();
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
FragmentTransaction transaction = fragmentManager.beginTransaction();
|
||||
transaction.replace(R.id.fragment_container, permissionsFragment).commit();
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
|
||||
public class WelcomeActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeActivity.class);
|
||||
|
||||
private ViewPager2 viewPager;
|
||||
private WelcomeFragmentsPagerAdapter pagerAdapter;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
AbstractGBActivity.init(this, AbstractGBActivity.NO_ACTIONBAR);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_welcome);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
// Configure ViewPager2 with fragment adapter and default fragment
|
||||
viewPager = findViewById(R.id.welcome_viewpager);
|
||||
pagerAdapter = new WelcomeFragmentsPagerAdapter(this);
|
||||
viewPager.setAdapter(pagerAdapter);
|
||||
|
||||
// Set up welcome page indicator
|
||||
WelcomePageIndicator pageIndicator = findViewById(R.id.welcome_page_indicator);
|
||||
pageIndicator.setViewPager(viewPager);
|
||||
}
|
||||
|
||||
private class WelcomeFragmentsPagerAdapter extends FragmentStateAdapter {
|
||||
public WelcomeFragmentsPagerAdapter(FragmentActivity fa) {
|
||||
super(fa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position) {
|
||||
case 0:
|
||||
return new WelcomeFragmentIntro();
|
||||
case 1:
|
||||
return new WelcomeFragmentOverview();
|
||||
case 2:
|
||||
return new WelcomeFragmentDocsSource();
|
||||
case 3:
|
||||
return new WelcomeFragmentPermissions();
|
||||
default:
|
||||
return new WelcomeFragmentGetStarted();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomeFragmentDocsSource extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentDocsSource.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
return inflater.inflate(R.layout.fragment_welcome_docs_source, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.DataManagementActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class WelcomeFragmentGetStarted extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentGetStarted.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.fragment_welcome_get_started, container, false);
|
||||
|
||||
Button firstDevice = view.findViewById(R.id.welcome_button_add_device);
|
||||
firstDevice.setOnClickListener(firstDeviceButton -> startActivity(new Intent(requireActivity(), DiscoveryActivityV2.class)));
|
||||
Button restore = view.findViewById(R.id.welcome_button_restore);
|
||||
restore.setOnClickListener(restoreButton -> startActivity(new Intent(requireActivity(), DataManagementActivity.class)));
|
||||
Button toApp = view.findViewById(R.id.welcome_button_to_app);
|
||||
toApp.setOnClickListener(toAppButton -> {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
prefs.getPreferences().edit().putBoolean("first_run", false).apply();
|
||||
requireActivity().finish();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import com.google.android.material.textfield.MaterialAutoCompleteTextView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class WelcomeFragmentIntro extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentIntro.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
final View view = inflater.inflate(R.layout.fragment_welcome_intro, container, false);
|
||||
final String[] themes = getResources().getStringArray(R.array.pref_theme_values);
|
||||
final Prefs prefs = GBApplication.getPrefs();
|
||||
final String currentTheme = prefs.getString("pref_key_theme", getString(R.string.pref_theme_value_system));
|
||||
final int currentThemeIndex = Arrays.asList(themes).indexOf(currentTheme);
|
||||
|
||||
final MaterialAutoCompleteTextView themeMenu = view.findViewById(R.id.app_theme_dropdown_menu);
|
||||
themeMenu.setSaveEnabled(false); // https://github.com/material-components/material-components-android/issues/1464#issuecomment-1258051448
|
||||
themeMenu.setText(getResources().getStringArray(R.array.pref_theme_options)[currentThemeIndex], false);
|
||||
themeMenu.setOnItemClickListener((adapterView, view1, i, l) -> {
|
||||
final SharedPreferences.Editor editor = prefs.getPreferences().edit();
|
||||
editor.putString("pref_key_theme", themes[i]).apply();
|
||||
final Handler handler = new Handler();
|
||||
handler.postDelayed(() -> {
|
||||
// Delay recreation of the Activity to give the dropdown some time to settle.
|
||||
// If we recreate it immediately, the theme popup will reopen, which is not what the user expects.
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(GBApplication.ACTION_THEME_CHANGE);
|
||||
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent);
|
||||
}, 500);
|
||||
});
|
||||
return view;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomeFragmentOverview extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentOverview.class);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
return inflater.inflate(R.layout.fragment_welcome_overview, container, false);
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PermissionsUtils;
|
||||
|
||||
public class WelcomeFragmentPermissions extends Fragment {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WelcomeFragmentPermissions.class);
|
||||
|
||||
private RecyclerView permissionsListView;
|
||||
private PermissionAdapter permissionAdapter;
|
||||
private Button requestAllButton;
|
||||
private List<String> requestingPermissions = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.fragment_welcome_permissions, container, false);
|
||||
|
||||
requestAllButton = view.findViewById(R.id.button_request_all);
|
||||
requestAllButton.setOnClickListener(v -> {
|
||||
List<PermissionsUtils.PermissionDetails> wantedPermissions = PermissionsUtils.getRequiredPermissionsList(requireActivity());
|
||||
requestingPermissions = new ArrayList<>();
|
||||
for (PermissionsUtils.PermissionDetails wantedPermission : wantedPermissions) {
|
||||
requestingPermissions.add(wantedPermission.getPermission());
|
||||
}
|
||||
requestAllPermissions();
|
||||
});
|
||||
|
||||
if (((AppCompatActivity)getActivity()).getSupportActionBar().isShowing()) {
|
||||
// Hide title when the Action Bar is visible (i.e. when not in the first run flow)
|
||||
view.findViewById(R.id.permissions_title).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Initialize RecyclerView and data
|
||||
permissionsListView = view.findViewById(R.id.permissions_list);
|
||||
|
||||
// Set up RecyclerView
|
||||
permissionAdapter = new PermissionAdapter(PermissionsUtils.getRequiredPermissionsList(requireActivity()), requireContext());
|
||||
permissionsListView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
permissionsListView.setAdapter(permissionAdapter);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
permissionAdapter.notifyDataSetChanged();
|
||||
if (PermissionsUtils.checkAllPermissions(requireActivity())) {
|
||||
requestAllButton.setEnabled(false);
|
||||
}
|
||||
if (!requestingPermissions.isEmpty()) {
|
||||
requestAllPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
public void requestAllPermissions() {
|
||||
if (!requestingPermissions.isEmpty()) {
|
||||
Iterator<String> it = requestingPermissions.iterator();
|
||||
while (it.hasNext()) {
|
||||
String currentPermission = it.next();
|
||||
if (PermissionsUtils.specialPermissions.contains(currentPermission)) {
|
||||
it.remove();
|
||||
if (!PermissionsUtils.checkPermission(requireActivity(), currentPermission)) {
|
||||
PermissionsUtils.requestPermission(requireActivity(), currentPermission);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
String[] combinedPermissions = requestingPermissions.toArray(new String[0]);
|
||||
requestingPermissions.clear();
|
||||
ActivityCompat.requestPermissions(requireActivity(), combinedPermissions, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionHolder extends RecyclerView.ViewHolder {
|
||||
TextView titleTextView;
|
||||
TextView summaryTextView;
|
||||
ImageView checkmarkImageView;
|
||||
Button requestButton;
|
||||
|
||||
public PermissionHolder(View itemView) {
|
||||
super(itemView);
|
||||
titleTextView = itemView.findViewById(R.id.permission_title);
|
||||
summaryTextView = itemView.findViewById(R.id.permission_summary);
|
||||
checkmarkImageView = itemView.findViewById(R.id.permission_check);
|
||||
requestButton = itemView.findViewById(R.id.permission_request);
|
||||
}
|
||||
}
|
||||
|
||||
private class PermissionAdapter extends RecyclerView.Adapter<PermissionHolder> {
|
||||
private List<PermissionsUtils.PermissionDetails> permissionList;
|
||||
private Context context;
|
||||
|
||||
public PermissionAdapter(List<PermissionsUtils.PermissionDetails> permissionList, Context context) {
|
||||
this.permissionList = permissionList;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PermissionHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View itemView = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.fragment_welcome_permission_row, parent, false);
|
||||
return new PermissionHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PermissionHolder holder, int position) {
|
||||
PermissionsUtils.PermissionDetails permissionData = permissionList.get(position);
|
||||
holder.titleTextView.setText(permissionData.getTitle());
|
||||
holder.summaryTextView.setText(permissionData.getSummary());
|
||||
if (PermissionsUtils.checkPermission(requireContext(), permissionData.getPermission())) {
|
||||
holder.requestButton.setVisibility(View.INVISIBLE);
|
||||
holder.requestButton.setEnabled(false);
|
||||
holder.checkmarkImageView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.requestButton.setVisibility(View.VISIBLE);
|
||||
holder.requestButton.setEnabled(true);
|
||||
holder.checkmarkImageView.setVisibility(View.GONE);
|
||||
holder.requestButton.setOnClickListener(view -> {
|
||||
PermissionsUtils.requestPermission(requireActivity(), permissionData.getPermission());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return permissionList.size();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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 <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.welcome;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class WelcomePageIndicator extends View {
|
||||
private ViewPager2 viewPager;
|
||||
private int pageCount;
|
||||
private int dotRadius = 15;
|
||||
private int color;
|
||||
|
||||
private Paint outlinePaint;
|
||||
private Paint filledPaint;
|
||||
private float currentX = 0.0f;
|
||||
|
||||
private ValueAnimator dotAnimator;
|
||||
|
||||
public WelcomePageIndicator(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
determineColor(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public WelcomePageIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
determineColor(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void determineColor(Context context, @Nullable AttributeSet attrs) {
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WelcomePageIndicator);
|
||||
color = a.getColor(R.styleable.WelcomePageIndicator_page_indicator_color, Color.BLACK);
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
outlinePaint = new Paint();
|
||||
outlinePaint.setColor(color);
|
||||
outlinePaint.setStyle(Paint.Style.STROKE);
|
||||
outlinePaint.setStrokeWidth(4);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
filledPaint = new Paint();
|
||||
filledPaint.setColor(color);
|
||||
filledPaint.setStyle(Paint.Style.FILL);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
public void setViewPager(ViewPager2 viewPager) {
|
||||
this.viewPager = viewPager;
|
||||
this.pageCount = viewPager.getAdapter().getItemCount();
|
||||
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
animateIndicator(position);
|
||||
}
|
||||
});
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private int getHorizontalMargin() {
|
||||
int dotDiameter = dotRadius * 2;
|
||||
int dotSpaces = pageCount * 2 - 1;
|
||||
return (getWidth() - dotSpaces * dotDiameter) / 2 + dotRadius;
|
||||
}
|
||||
|
||||
private void animateIndicator(int position) {
|
||||
float horizontalMargin = getHorizontalMargin();
|
||||
if (horizontalMargin <= 0.0f) {
|
||||
// Not animating because the drawable is not ready yet
|
||||
return;
|
||||
}
|
||||
float targetX = horizontalMargin + 4 * dotRadius * position;
|
||||
if (dotAnimator != null && dotAnimator.isRunning()) {
|
||||
dotAnimator.cancel();
|
||||
}
|
||||
if (currentX == 0.0f) currentX = horizontalMargin;
|
||||
dotAnimator = ValueAnimator.ofFloat(currentX, targetX);
|
||||
dotAnimator.addUpdateListener(animation -> {
|
||||
currentX = (float) animation.getAnimatedValue();
|
||||
invalidate();
|
||||
});
|
||||
dotAnimator.setDuration(300);
|
||||
dotAnimator.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (viewPager == null || pageCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
float horizontalMargin = getHorizontalMargin();
|
||||
if (currentX == 0.0f && horizontalMargin != 0.0f) currentX = horizontalMargin;
|
||||
float circleY = getHeight() / 2f;
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
float circleX = horizontalMargin + 4 * dotRadius * i;
|
||||
canvas.drawCircle(circleX, circleY, dotRadius, outlinePaint);
|
||||
}
|
||||
canvas.drawCircle(currentX, circleY, dotRadius, filledPaint);
|
||||
}
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
/* Copyright (C) 2024 Arjan Schrijver
|
||||
|
||||
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.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
||||
|
||||
public class PermissionsUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PermissionsUtils.class);
|
||||
|
||||
public static final String CUSTOM_PERM_NOTIFICATION_LISTENER = "custom_perm_notifications_listener";
|
||||
public static final String CUSTOM_PERM_NOTIFICATION_SERVICE = "custom_perm_notifications_service";
|
||||
public static final String CUSTOM_PERM_DISPLAY_OVER = "custom_perm_display_over";
|
||||
|
||||
public static final List<String> specialPermissions = new ArrayList<String>() {{
|
||||
add(CUSTOM_PERM_NOTIFICATION_LISTENER);
|
||||
add(CUSTOM_PERM_NOTIFICATION_SERVICE);
|
||||
add(CUSTOM_PERM_DISPLAY_OVER);
|
||||
add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
add(Manifest.permission.ACCESS_BACKGROUND_LOCATION);
|
||||
}
|
||||
}};
|
||||
|
||||
public static ArrayList<PermissionDetails> getRequiredPermissionsList(Activity activity) {
|
||||
ArrayList<PermissionDetails> permissionsList = new ArrayList<>();
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_NOTIFICATION_LISTENER,
|
||||
activity.getString(R.string.menuitem_notifications),
|
||||
activity.getString(R.string.permission_notifications_summary)));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_NOTIFICATION_SERVICE,
|
||||
activity.getString(R.string.permission_manage_dnd_title),
|
||||
activity.getString(R.string.permission_manage_dnd_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
CUSTOM_PERM_DISPLAY_OVER,
|
||||
activity.getString(R.string.permission_displayover_title),
|
||||
activity.getString(R.string.permission_displayover_summary)));
|
||||
}
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
activity.getString(R.string.permission_fine_location_title),
|
||||
activity.getString(R.string.permission_fine_location_summary)));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
|
||||
activity.getString(R.string.permission_background_location_title),
|
||||
activity.getString(R.string.permission_background_location_summary)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH,
|
||||
activity.getString(R.string.permission_bluetooth_title),
|
||||
activity.getString(R.string.permission_bluetooth_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_ADMIN,
|
||||
activity.getString(R.string.permission_bluetooth_admin_title),
|
||||
activity.getString(R.string.permission_bluetooth_admin_summary)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_SCAN,
|
||||
activity.getString(R.string.permission_bluetooth_scan_title),
|
||||
activity.getString(R.string.permission_bluetooth_scan_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.BLUETOOTH_CONNECT,
|
||||
activity.getString(R.string.permission_bluetooth_connect_title),
|
||||
activity.getString(R.string.permission_bluetooth_connect_summary)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
activity.getString(R.string.permission_post_notification_title),
|
||||
activity.getString(R.string.permission_post_notification_summary)));
|
||||
}
|
||||
if (BuildConfig.INTERNET_ACCESS) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.INTERNET,
|
||||
activity.getString(R.string.permission_internet_access_title),
|
||||
activity.getString(R.string.permission_internet_access_summary)));
|
||||
}
|
||||
// permissionsList.add(new PermissionDetails( // NOTE: can't request this, it's only allowed for system apps
|
||||
// Manifest.permission.MEDIA_CONTENT_CONTROL,
|
||||
// "Media content control",
|
||||
// "Read and control media playback"));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
activity.getString(R.string.permission_contacts_title),
|
||||
activity.getString(R.string.permission_contacts_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CALENDAR,
|
||||
activity.getString(R.string.permission_calendar_title),
|
||||
activity.getString(R.string.permission_calendar_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.RECEIVE_SMS,
|
||||
activity.getString(R.string.permission_receive_sms_title),
|
||||
activity.getString(R.string.permission_receive_sms_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.SEND_SMS,
|
||||
activity.getString(R.string.permission_send_sms_title),
|
||||
activity.getString(R.string.permission_send_sms_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_CALL_LOG,
|
||||
activity.getString(R.string.permission_read_call_log_title),
|
||||
activity.getString(R.string.permission_read_call_log_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
activity.getString(R.string.permission_read_phone_state_title),
|
||||
activity.getString(R.string.permission_read_phone_state_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.CALL_PHONE,
|
||||
activity.getString(R.string.permission_call_phone_title),
|
||||
activity.getString(R.string.permission_call_phone_summary)));
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.PROCESS_OUTGOING_CALLS,
|
||||
activity.getString(R.string.permission_process_outgoing_calls_title),
|
||||
activity.getString(R.string.permission_process_outgoing_calls_summary)));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.ANSWER_PHONE_CALLS,
|
||||
activity.getString(R.string.permission_answer_phone_calls_title),
|
||||
activity.getString(R.string.permission_answer_phone_calls_summary)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
activity.getString(R.string.permission_external_storage_title),
|
||||
activity.getString(R.string.permission_external_storage_summary)));
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
permissionsList.add(new PermissionDetails(
|
||||
Manifest.permission.QUERY_ALL_PACKAGES,
|
||||
activity.getString(R.string.permission_query_all_packages_title),
|
||||
activity.getString(R.string.permission_query_all_packages_summary)));
|
||||
}
|
||||
return permissionsList;
|
||||
}
|
||||
|
||||
public static boolean checkPermission(Context context, String permission) {
|
||||
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
||||
Set<String> set = NotificationManagerCompat.getEnabledListenerPackages(context);
|
||||
return set.contains(context.getPackageName());
|
||||
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).isNotificationPolicyAccessGranted();
|
||||
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
return Settings.canDrawOverlays(context);
|
||||
} else {
|
||||
return ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkAllPermissions(Activity activity) {
|
||||
boolean result = true;
|
||||
for (PermissionDetails permission : getRequiredPermissionsList(activity)) {
|
||||
if (!checkPermission(activity, permission.getPermission())) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void requestPermission(Activity activity, String permission) {
|
||||
if (permission.equals(CUSTOM_PERM_NOTIFICATION_LISTENER)) {
|
||||
showNotifyListenerPermissionsDialog(activity);
|
||||
} else if (permission.equals(CUSTOM_PERM_NOTIFICATION_SERVICE) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
|
||||
showNotifyPolicyPermissionsDialog(activity);
|
||||
} else if (permission.equals(CUSTOM_PERM_DISPLAY_OVER)) {
|
||||
showDisplayOverOthersPermissionsDialog(activity);
|
||||
} else if (permission.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION) && (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)) {
|
||||
showBackgroundLocationPermissionsDialog(activity);
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{permission}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PermissionDetails {
|
||||
private String permission;
|
||||
private String title;
|
||||
private String summary;
|
||||
|
||||
public PermissionDetails(String permission, String title, String summary) {
|
||||
this.permission = permission;
|
||||
this.title = title;
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
|
||||
private static void showNotifyListenerPermissionsDialog(Activity activity) {
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(activity.getString(R.string.permission_notification_listener,
|
||||
activity.getString(R.string.app_name),
|
||||
activity.getString(R.string.ok)))
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
try {
|
||||
Intent intent;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS);
|
||||
intent.putExtra(Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, new ComponentName(BuildConfig.APPLICATION_ID, NotificationListener.class.getName()).flattenToString());
|
||||
} else {
|
||||
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
|
||||
}
|
||||
String showArgs = BuildConfig.APPLICATION_ID + "/" + NotificationListener.class.getName();
|
||||
intent.putExtra(":settings:fragment_args_key", showArgs);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(":settings:fragment_args_key", showArgs);
|
||||
intent.putExtra(":settings:show_fragment_args", bundle);
|
||||
activity.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
GB.toast(activity, "'Notification Listener Settings' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("'Notification Listener Settings' activity not found");
|
||||
}
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void showNotifyPolicyPermissionsDialog(Activity activity) {
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(activity.getString(R.string.permission_notification_policy_access,
|
||||
activity.getString(R.string.app_name),
|
||||
activity.getString(R.string.ok)))
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
try {
|
||||
activity.startActivity(new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
GB.toast(activity, "'Notification Policy' activity not found", Toast.LENGTH_LONG, GB.ERROR);
|
||||
LOG.error("'Notification Policy' activity not found");
|
||||
}
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private static void showDisplayOverOthersPermissionsDialog(Activity activity) {
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(activity.getString(R.string.permission_display_over_other_apps,
|
||||
activity.getString(R.string.app_name),
|
||||
activity.getString(R.string.ok)))
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Intent enableIntent = new Intent(
|
||||
android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
||||
Uri.parse("package:" + BuildConfig.APPLICATION_ID)
|
||||
);
|
||||
activity.startActivity(enableIntent);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.dismiss, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.R)
|
||||
private static void showBackgroundLocationPermissionsDialog(Activity activity) {
|
||||
new MaterialAlertDialogBuilder(activity)
|
||||
.setMessage(activity.getString(R.string.permission_location,
|
||||
activity.getString(R.string.app_name),
|
||||
activity.getPackageManager().getBackgroundPermissionOptionLabel()))
|
||||
.setPositiveButton(R.string.ok, (dialog, id) -> {
|
||||
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 0);
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
10
app/src/main/res/layout/activity_permissions.xml
Normal file
10
app/src/main/res/layout/activity_permissions.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</RelativeLayout>
|
25
app/src/main/res/layout/activity_welcome.xml
Normal file
25
app/src/main/res/layout/activity_welcome.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical"
|
||||
tools:context=".activities.welcome.WelcomeActivity">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/welcome_viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/welcome_page_indicator" />
|
||||
|
||||
<nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomePageIndicator
|
||||
android:id="@+id/welcome_page_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:page_indicator_color="?attr/colorPrimary" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
43
app/src/main/res/layout/fragment_welcome_docs_source.xml
Normal file
43
app/src/main/res/layout/fragment_welcome_docs_source.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_open_source_title"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_open_source_text" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:src="@drawable/ic_engineering"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
52
app/src/main/res/layout/fragment_welcome_get_started.xml
Normal file
52
app/src/main/res/layout/fragment_welcome_get_started.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/intro_gadgetbridge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_get_started_title"
|
||||
android:textSize="25sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_get_started_desc" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_add_device"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="@string/first_start_get_started_add_first_device_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_restore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="@string/first_start_get_started_restore_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/welcome_button_to_app"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:text="@string/first_start_get_started_go_to_app_button" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
61
app/src/main/res/layout/fragment_welcome_intro.xml
Normal file
61
app/src/main/res/layout/fragment_welcome_intro.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:background="@color/accent"
|
||||
android:src="@drawable/ic_launcher_foreground" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_intro_welcome_to"
|
||||
android:textSize="30sp" />
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="30sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_intro_tag_line" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:id="@+id/app_theme_dropdown_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:hint="@string/pref_title_theme">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/app_theme_dropdown_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
app:simpleItems="@array/pref_theme_options" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
64
app/src/main/res/layout/fragment_welcome_overview.xml
Normal file
64
app/src/main/res/layout/fragment_welcome_overview.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="50dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_overview_title"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_overview_desc" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:src="@drawable/ic_dashboard"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_overview_dashboard" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_devices_other"
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_overview_devices" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
48
app/src/main/res/layout/fragment_welcome_permission_row.xml
Normal file
48
app/src/main/res/layout/fragment_welcome_permission_row.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="50dp">
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/permission_request"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:id="@+id/permission_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:text="Placeholder: Permission name" />
|
||||
<TextView
|
||||
android:id="@+id/permission_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Placeholder: Permission summary" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/permission_request"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:text="@string/first_start_permissions_request_button" />
|
||||
<ImageView
|
||||
android:id="@+id/permission_check"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/cpv_preset_checked"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/holo_green_dark"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
38
app/src/main/res/layout/fragment_welcome_permissions.xml
Normal file
38
app/src/main/res/layout/fragment_welcome_permissions.xml
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permissions_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_permissions_title"
|
||||
android:textSize="30sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textSize="20sp"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/first_start_permissions_desc" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_request_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/first_start_permissions_request_all_button"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/permissions_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp" />
|
||||
</LinearLayout>
|
@ -2124,7 +2124,7 @@
|
||||
<string name="permission_granting_mandatory">All these permissions are required and instability might occur if not granted</string>
|
||||
<string name="permission_notification_listener">%1$s needs access to Notifications in order to display them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Notification Access\', then tap \'Back\' to return to %1$s.</string>
|
||||
<string name="permission_notification_policy_access">%1$s needs access to Do Not Disturb settings in order to honour them on your watch when your phone\'s screen is off.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow Do Not Disturb\', then tap \'Back\' to return to %1$s.</string>
|
||||
<string name="permission_location">%1$s needs access to your location in the background to allow it to stay connected to your watch even when your screen is off.\n\nPlease tap \'%2$s\' to agree.</string>
|
||||
<string name="permission_location">%1$s needs access to your location in the background to allow it to stay connected to your watch even when your screen is off.\n\nPlease choose \'%2$s\' in the following screen, then tap \'Back\' to return to %1$s.</string>
|
||||
<string name="permission_display_over_other_apps">%1$s needs permission to display over other apps in order to let Bangle.js watches start activities via intents when %1$s is in the background.\n\nThis can be used to start a music app and play a song, and many other things.\n\nPlease tap \'%2$s\' then \'%1$s\' and enable \'Allow display over other apps\', then tap \'Back\' to return to %1$s.\n\nTo stop %1$s asking for permissions go to \'Settings\' and uncheck \'Check permission status\'.\n\nMake sure to grant %1$s the permissions needed to function as you expect.</string>
|
||||
<string name="error_version_check_extreme_caution">CAUTION: Error when checking version information! You should not continue! Saw version name \"%s\"</string>
|
||||
<string name="require_location_provider">Location must be enabled</string>
|
||||
@ -3386,4 +3386,67 @@
|
||||
<string name="inactivity_warnings_minimum_steps_summary">Minimum amount of steps that need to be taken during the threshold minutes</string>
|
||||
<string name="prefs_hrv_monitoring_title">HRV monitoring</string>
|
||||
<string name="prefs_hrv_monitoring_description">Automatically monitor heart rate variability throughout the day</string>
|
||||
|
||||
<!-- Welcome screens strings -->
|
||||
<string name="first_start_welcome_title">Welcome</string>
|
||||
<string name="first_start_intro_welcome_to">Welcome to</string>
|
||||
<string name="first_start_intro_tag_line">Break free from the proprietary apps and cloud services of gadget vendors.</string>
|
||||
<string name="first_start_overview_title">Overview</string>
|
||||
<string name="first_start_overview_desc">Gadgetbridge has two main views, each with their own purpose.</string>
|
||||
<string name="first_start_overview_dashboard">The dashboard allows you to get a quick idea of how you\'re doing today. The calendar view shows the status of your goals over a whole month.</string>
|
||||
<string name="first_start_overview_devices">The devices view shows all devices you have configured and their status, and gives access to device specific functions such as detailed charts, settings, apps and alarms.</string>
|
||||
<string name="first_start_open_source_title">Open Source</string>
|
||||
<string name="first_start_open_source_text">Gadgetbridge is an open source app. It is developed by the community, for the community.\n\nAnyone is welcome to contribute via code, documentation, testing and donations.\n\nGadgetbridge contains no ads and no tracking. It keeps your data locally on your Android device, so it is 100% privacy friendly.\n\nVisit our website for more information, documentation and links to our communication channels.</string>
|
||||
<string name="first_start_permissions_title">Permissions</string>
|
||||
<string name="first_start_permissions_desc">Gadgetbridge needs a lot of permissions to perform all its functions. Review the permissions and their purposes below.</string>
|
||||
<string name="first_start_permissions_request_all_button">Request all permissions</string>
|
||||
<string name="first_start_permissions_request_button">Request</string>
|
||||
<string name="first_start_get_started_title">Get started</string>
|
||||
<string name="first_start_get_started_desc">To get started, add your first device directly from this screen, restore a backup or start with a clean database.</string>
|
||||
<string name="first_start_get_started_add_first_device_button">Add first device</string>
|
||||
<string name="first_start_get_started_restore_button">Restore backup</string>
|
||||
<string name="first_start_get_started_go_to_app_button">Go to the app</string>
|
||||
<string name="permission_notifications_summary">Forwarding notifications to connected gadgets</string>
|
||||
<string name="permission_manage_dnd_title">Manage Do Not Disturb</string>
|
||||
<string name="permission_manage_dnd_summary">Changing DND notification policy</string>
|
||||
<string name="permission_displayover_title">Display over other apps</string>
|
||||
<string name="permission_displayover_summary">Used by Bangle.js for starting apps and other functionality on your phone</string>
|
||||
<string name="permission_fine_location_title">Fine location</string>
|
||||
<string name="permission_fine_location_summary">Scanning for Bluetooth devices</string>
|
||||
<string name="permission_background_location_title">Background location</string>
|
||||
<string name="permission_background_location_summary">Scanning for Bluetooth devices in the background and sending the location to certain gadgets</string>
|
||||
<string name="permission_bluetooth_title">Bluetooth</string>
|
||||
<string name="permission_bluetooth_summary">Connecting to Bluetooth devices</string>
|
||||
<string name="permission_bluetooth_admin_title">Bluetooth admin</string>
|
||||
<string name="permission_bluetooth_admin_summary">Discovering and pairing Bluetooth devices</string>
|
||||
<string name="permission_bluetooth_scan_title">Bluetooth scan</string>
|
||||
<string name="permission_bluetooth_scan_summary">Scanning for new Bluetooth devices</string>
|
||||
<string name="permission_bluetooth_connect_title">Bluetooth connect</string>
|
||||
<string name="permission_bluetooth_connect_summary">Connecting to already-paired Bluetooth devices</string>
|
||||
<string name="permission_post_notification_title">Post notifications</string>
|
||||
<string name="permission_post_notification_summary">Posting ongoing notification which keeps the service running</string>
|
||||
<string name="permission_internet_access_title">Internet access</string>
|
||||
<string name="permission_internet_access_summary">Synchronization with online resources</string>
|
||||
<string name="permission_contacts_title">Contacts</string>
|
||||
<string name="permission_contacts_summary">Sending contacts to gadgets</string>
|
||||
<string name="permission_calendar_title">Calendar</string>
|
||||
<string name="permission_calendar_summary">Sending calendar to gadgets</string>
|
||||
<string name="permission_receive_sms_title">Receive SMS</string>
|
||||
<string name="permission_receive_sms_summary">Forwarding SMS messages to gadgets</string>
|
||||
<string name="permission_send_sms_title">Send SMS</string>
|
||||
<string name="permission_send_sms_summary">Sending SMS (canned response) from gadgets</string>
|
||||
<string name="permission_read_call_log_title">Read call log</string>
|
||||
<string name="permission_read_call_log_summary">Forwarding call log to gadgets</string>
|
||||
<string name="permission_read_phone_state_title">Read phone state</string>
|
||||
<string name="permission_read_phone_state_summary">Reading status of ongoing calls</string>
|
||||
<string name="permission_call_phone_title">Call phone</string>
|
||||
<string name="permission_call_phone_summary">Initiating phone calls from gadgets</string>
|
||||
<string name="permission_process_outgoing_calls_title">Process outgoing calls</string>
|
||||
<string name="permission_process_outgoing_calls_summary">Reading the number of an outgoing call to display it on a gadget</string>
|
||||
<string name="permission_answer_phone_calls_title">Answer phone calls</string>
|
||||
<string name="permission_answer_phone_calls_summary">Answering phone calls from gadgets</string>
|
||||
<string name="permission_external_storage_title">External storage</string>
|
||||
<string name="permission_external_storage_summary">Using images, ringtones, app files and more</string>
|
||||
<string name="permission_query_all_packages_title">Query all packages</string>
|
||||
<string name="permission_query_all_packages_summary">Reading names and icons of all installed apps</string>
|
||||
</resources>
|
||||
|
6
app/src/main/res/values/wpi_attrs.xml
Normal file
6
app/src/main/res/values/wpi_attrs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<resources>
|
||||
<declare-styleable name="WelcomePageIndicator">
|
||||
<attr name="page_indicator_color" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
Loading…
Reference in New Issue
Block a user