Fossil Hybrid HR: Use GB app manager (#2302)
This PR replaces (just for the Fossil Hybrid HR) the current watchface configuration screen with the native Gadgetbridge app manager. Bonus feature: when multiple watchfaces are installed on the watch, they can be switched by tapping on them. Co-authored-by: Arjan Schrijver <a_gadgetbridge@anymore.nl> Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2302 Co-authored-by: Arjan Schrijver <arjan5@noreply.codeberg.org> Co-committed-by: Arjan Schrijver <arjan5@noreply.codeberg.org>
This commit is contained in:
parent
6403e13c9b
commit
25324c61b9
|
@ -579,7 +579,7 @@
|
|||
|
||||
<activity
|
||||
android:name=".devices.qhybrid.ConfigActivity"
|
||||
android:label="@string/qhybrid_title_watchface_apps"
|
||||
android:label="@string/qhybrid_title_watchface"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
|
@ -589,7 +589,7 @@
|
|||
android:parentActivityName=".devices.qhybrid.ConfigActivity" />
|
||||
<activity
|
||||
android:name=".devices.qhybrid.HRConfigActivity"
|
||||
android:label="@string/qhybrid_title_watchface_apps"
|
||||
android:label="@string/qhybrid_title_watchface"
|
||||
android:exported="true"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity android:name=".devices.qhybrid.WidgetSettingsActivity"
|
||||
|
|
|
@ -52,10 +52,13 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
|
@ -67,6 +70,11 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
|
||||
private ItemTouchHelper appManagementTouchHelper;
|
||||
|
||||
protected final List<GBDeviceApp> appList = new ArrayList<>();
|
||||
private GBDeviceAppAdapter mGBDeviceAppAdapter;
|
||||
protected GBDevice mGBDevice = null;
|
||||
protected DeviceCoordinator mCoordinator = null;
|
||||
|
||||
protected abstract List<GBDeviceApp> getSystemAppsInCategory();
|
||||
|
||||
protected abstract String getSortFilename();
|
||||
|
@ -104,7 +112,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
appList.addAll(getCachedApps(uuids));
|
||||
}
|
||||
|
||||
private void refreshListFromPebble(Intent intent) {
|
||||
private void refreshListFromDevice(Intent intent) {
|
||||
appList.clear();
|
||||
int appCount = intent.getIntExtra("app_count", 0);
|
||||
for (int i = 0; i < appCount; i++) {
|
||||
|
@ -127,12 +135,14 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
String action = intent.getAction();
|
||||
if (action.equals(ACTION_REFRESH_APPLIST)) {
|
||||
if (intent.hasExtra("app_count")) {
|
||||
LOG.info("got app info from pebble");
|
||||
LOG.info("got app info from device");
|
||||
if (!isCacheManager()) {
|
||||
LOG.info("will refresh list based on data from pebble");
|
||||
refreshListFromPebble(intent);
|
||||
LOG.info("will refresh list based on data from device");
|
||||
refreshListFromDevice(intent);
|
||||
}
|
||||
} else if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3 || isCacheManager()) {
|
||||
} else if (mCoordinator.supportsAppListFetching()) {
|
||||
refreshList();
|
||||
} else if (isCacheManager()) {
|
||||
refreshList();
|
||||
}
|
||||
mGBDeviceAppAdapter.notifyDataSetChanged();
|
||||
|
@ -140,17 +150,13 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
}
|
||||
};
|
||||
|
||||
protected final List<GBDeviceApp> appList = new ArrayList<>();
|
||||
private GBDeviceAppAdapter mGBDeviceAppAdapter;
|
||||
protected GBDevice mGBDevice = null;
|
||||
|
||||
protected List<GBDeviceApp> getCachedApps(List<UUID> uuids) {
|
||||
List<GBDeviceApp> cachedAppList = new ArrayList<>();
|
||||
File cachePath;
|
||||
try {
|
||||
cachePath = PebbleUtils.getPbwCacheDir();
|
||||
cachePath = mCoordinator.getAppCacheDir();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while reading pbw cache.");
|
||||
LOG.warn("could not get external dir while reading app cache.");
|
||||
return cachedAppList;
|
||||
}
|
||||
|
||||
|
@ -161,13 +167,13 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
files = new File[uuids.size()];
|
||||
int index = 0;
|
||||
for (UUID uuid : uuids) {
|
||||
files[index++] = new File(uuid.toString() + ".pbw");
|
||||
files[index++] = new File(uuid.toString() + mCoordinator.getAppFileExtension());
|
||||
}
|
||||
}
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.getName().endsWith(".pbw")) {
|
||||
String baseName = file.getName().substring(0, file.getName().length() - 4);
|
||||
if (file.getName().endsWith(mCoordinator.getAppFileExtension())) {
|
||||
String baseName = file.getName().substring(0, file.getName().length() - mCoordinator.getAppFileExtension().length());
|
||||
//metadata
|
||||
File jsonFile = new File(cachePath, baseName + ".json");
|
||||
//configuration
|
||||
|
@ -178,58 +184,60 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
cachedAppList.add(new GBDeviceApp(json, configFile.exists()));
|
||||
} catch (Exception e) {
|
||||
LOG.info("could not read json file for " + baseName);
|
||||
//FIXME: this is really ugly, if we do not find system uuids in pbw cache add them manually. Also duplicated code
|
||||
switch (baseName) {
|
||||
case "8f3c8686-31a1-4f5f-91f5-01600c9bdc59":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
break;
|
||||
case "1f03293d-47af-4f28-b960-f2b02a6dd757":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "b2cae818-10f8-46df-ad2b-98ad2254a3c1":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "67a32d95-ef69-46d4-a0b9-854cc62f97f9":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "18e443ce-38fd-47c8-84d5-6d0c775fbe55":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "0863fc6a-66c5-4f62-ab8a-82ed00a98b5d":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
}
|
||||
/*
|
||||
else if (baseName.equals("4dab81a6-d2fc-458a-992c-7a1f3b96a970")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
} else if (baseName.equals("cf1e816a-9db0-4511-bbb8-f60c48ca8fac")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
*/
|
||||
if (mGBDevice != null) {
|
||||
if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
|
||||
if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
continue;
|
||||
if (mGBDevice.getType() == DeviceType.PEBBLE) {
|
||||
//FIXME: this is really ugly, if we do not find system uuids in pbw cache add them manually. Also duplicated code
|
||||
switch (baseName) {
|
||||
case "8f3c8686-31a1-4f5f-91f5-01600c9bdc59":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
break;
|
||||
case "1f03293d-47af-4f28-b960-f2b02a6dd757":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "b2cae818-10f8-46df-ad2b-98ad2254a3c1":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Notifications (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "67a32d95-ef69-46d4-a0b9-854cc62f97f9":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Alarms (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "18e443ce-38fd-47c8-84d5-6d0c775fbe55":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Watchfaces (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
case "0863fc6a-66c5-4f62-ab8a-82ed00a98b5d":
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Send Text (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
break;
|
||||
}
|
||||
/*
|
||||
else if (baseName.equals("4dab81a6-d2fc-458a-992c-7a1f3b96a970")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
} else if (baseName.equals("cf1e816a-9db0-4511-bbb8-f60c48ca8fac")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
*/
|
||||
if (mGBDevice != null) {
|
||||
if (PebbleUtils.hasHealth(mGBDevice.getModel())) {
|
||||
if (baseName.equals(PebbleProtocol.UUID_PEBBLE_HEALTH.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
|
||||
if (baseName.equals(PebbleProtocol.UUID_WORKOUT.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
|
||||
if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
}
|
||||
if (baseName.equals(PebbleProtocol.UUID_WEATHER.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (PebbleUtils.hasHRM(mGBDevice.getModel())) {
|
||||
if (baseName.equals(PebbleProtocol.UUID_WORKOUT.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WORKOUT, "Workout (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
continue;
|
||||
}
|
||||
if (uuids == null) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN));
|
||||
}
|
||||
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 4) {
|
||||
if (baseName.equals("3af858c3-16cb-4561-91e7-f1ad2df8725f")) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
}
|
||||
if (baseName.equals(PebbleProtocol.UUID_WEATHER.toString())) {
|
||||
cachedAppList.add(new GBDeviceApp(PebbleProtocol.UUID_WEATHER, "Weather (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uuids == null) {
|
||||
cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,13 +250,14 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mGBDevice = ((AppManagerActivity) getActivity()).getGBDevice();
|
||||
mCoordinator = DeviceHelper.getInstance().getCoordinator(mGBDevice);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_REFRESH_APPLIST);
|
||||
|
||||
LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter);
|
||||
|
||||
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3) {
|
||||
if (mCoordinator.supportsAppListFetching()) {
|
||||
GBApplication.deviceService().onAppInfoReq();
|
||||
if (isCacheManager()) {
|
||||
refreshList();
|
||||
|
@ -340,13 +349,17 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
switch (selectedApp.getType()) {
|
||||
case WATCHFACE:
|
||||
case APP_GENERIC:
|
||||
case APP_ACTIVITYTRACKER:
|
||||
break;
|
||||
default:
|
||||
menu.removeItem(R.id.appmanager_app_openinstore);
|
||||
if (mGBDevice.getType() == DeviceType.PEBBLE) {
|
||||
switch (selectedApp.getType()) {
|
||||
case WATCHFACE:
|
||||
case APP_GENERIC:
|
||||
case APP_ACTIVITYTRACKER:
|
||||
break;
|
||||
default:
|
||||
menu.removeItem(R.id.appmanager_app_openinstore);
|
||||
}
|
||||
} else {
|
||||
menu.removeItem(R.id.appmanager_app_openinstore);
|
||||
}
|
||||
//menu.setHeaderTitle(selectedApp.getName());
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
|
@ -361,45 +374,41 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
}
|
||||
|
||||
private boolean onContextItemSelected(MenuItem item, GBDeviceApp selectedApp) {
|
||||
File appCacheDir;
|
||||
try {
|
||||
appCacheDir = mCoordinator.getAppCacheDir();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while trying to access app cache.");
|
||||
return true;
|
||||
}
|
||||
Intent refreshIntent;
|
||||
switch (item.getItemId()) {
|
||||
case R.id.appmanager_app_delete_cache:
|
||||
File pbwCacheDir;
|
||||
try {
|
||||
pbwCacheDir = PebbleUtils.getPbwCacheDir();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while trying to access pbw cache.");
|
||||
return true;
|
||||
}
|
||||
String baseName = selectedApp.getUUID().toString();
|
||||
String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js", "_preset.json"};
|
||||
|
||||
String[] suffixToDelete = new String[]{mCoordinator.getAppFileExtension(), ".json", "_config.js", "_preset.json"};
|
||||
for (String suffix : suffixToDelete) {
|
||||
File fileToDelete = new File(pbwCacheDir,baseName + suffix);
|
||||
File fileToDelete = new File(appCacheDir,baseName + suffix);
|
||||
if (!fileToDelete.delete()) {
|
||||
LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString());
|
||||
LOG.warn("could not delete file from app cache: " + fileToDelete.toString());
|
||||
} else {
|
||||
LOG.info("deleted file: " + fileToDelete.toString());
|
||||
}
|
||||
}
|
||||
AppManagerActivity.deleteFromAppOrderFile("pbwcacheorder.txt", selectedApp.getUUID()); // FIXME: only if successful
|
||||
AppManagerActivity.deleteFromAppOrderFile(getSortFilename(), selectedApp.getUUID()); // FIXME: only if successful
|
||||
refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
|
||||
// fall through
|
||||
case R.id.appmanager_app_delete:
|
||||
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3) {
|
||||
if (mCoordinator.supportsAppReordering()) {
|
||||
AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchapps", selectedApp.getUUID()); // FIXME: only if successful
|
||||
AppManagerActivity.deleteFromAppOrderFile(mGBDevice.getAddress() + ".watchfaces", selectedApp.getUUID()); // FIXME: only if successful
|
||||
Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
|
||||
refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
|
||||
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent);
|
||||
}
|
||||
GBApplication.deviceService().onAppDelete(selectedApp.getUUID());
|
||||
return true;
|
||||
case R.id.appmanager_app_reinstall:
|
||||
File cachePath;
|
||||
try {
|
||||
cachePath = new File(PebbleUtils.getPbwCacheDir(), selectedApp.getUUID() + ".pbw");
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while trying to access pbw cache.");
|
||||
return true;
|
||||
}
|
||||
File cachePath = new File(appCacheDir, selectedApp.getUUID() + mCoordinator.getAppFileExtension());
|
||||
GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath));
|
||||
return true;
|
||||
case R.id.appmanager_health_activate:
|
||||
|
@ -455,8 +464,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
//app reordering is not possible on old firmwares
|
||||
if (PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) < 3 && !isCacheManager()) {
|
||||
if (!mCoordinator.supportsAppReordering() && !isCacheManager()) {
|
||||
return 0;
|
||||
}
|
||||
//we only support up and down movement and only for moving, not for swiping apps away
|
||||
|
|
|
@ -39,7 +39,7 @@ public class AppManagerFragmentCache extends AbstractAppManagerFragment {
|
|||
|
||||
@Override
|
||||
public String getSortFilename() {
|
||||
return "pbwcacheorder.txt";
|
||||
return mCoordinator.getAppCacheSortFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
|
@ -29,6 +30,9 @@ public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment
|
|||
@Override
|
||||
protected List<GBDeviceApp> getSystemAppsInCategory() {
|
||||
List<GBDeviceApp> systemApps = new ArrayList<>();
|
||||
if (mGBDevice.getType() != DeviceType.PEBBLE) {
|
||||
return systemApps;
|
||||
}
|
||||
//systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
//systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
systemApps.add(new GBDeviceApp(UUID.fromString("1f03293d-47af-4f28-b960-f2b02a6dd757"), "Music (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
|
||||
|
|
|
@ -21,12 +21,16 @@ import java.util.List;
|
|||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment {
|
||||
|
||||
@Override
|
||||
protected List<GBDeviceApp> getSystemAppsInCategory() {
|
||||
List<GBDeviceApp> systemWatchfaces = new ArrayList<>();
|
||||
if (mGBDevice.getType() != DeviceType.PEBBLE) {
|
||||
return systemWatchfaces;
|
||||
}
|
||||
systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
systemWatchfaces.add(new GBDeviceApp(UUID.fromString("3af858c3-16cb-4561-91e7-f1ad2df8725f"), "Kickstart (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM));
|
||||
return systemWatchfaces;
|
||||
|
|
|
@ -29,6 +29,8 @@ import androidx.annotation.Nullable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -144,6 +146,31 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAppCacheDir() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppCacheSortFilename() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppFileExtension() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppListFetching() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppReordering() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_ASK;
|
||||
|
|
|
@ -25,6 +25,8 @@ import android.content.Context;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -242,6 +244,31 @@ public interface DeviceCoordinator {
|
|||
*/
|
||||
Class<? extends Activity> getAppsManagementActivity();
|
||||
|
||||
/**
|
||||
* Returns the device app cache directory.
|
||||
*/
|
||||
File getAppCacheDir() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a String containing the device app sort order filename.
|
||||
*/
|
||||
String getAppCacheSortFilename();
|
||||
|
||||
/**
|
||||
* Returns a String containing the file extension for watch apps.
|
||||
*/
|
||||
String getAppFileExtension();
|
||||
|
||||
/**
|
||||
* Indicated whether the device supports fetching a list of its apps.
|
||||
*/
|
||||
boolean supportsAppListFetching();
|
||||
|
||||
/**
|
||||
* Indicates whether the device supports reordering of apps.
|
||||
*/
|
||||
boolean supportsAppReordering();
|
||||
|
||||
/**
|
||||
* Returns how/if the given device should be bonded before connecting to it.
|
||||
*/
|
||||
|
|
|
@ -23,6 +23,9 @@ import android.net.Uri;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
|
@ -148,6 +151,39 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
|||
return AppManagerActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAppCacheDir() throws IOException {
|
||||
return PebbleUtils.getPbwCacheDir();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppCacheSortFilename() {
|
||||
return "pbwcacheorder.txt";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAppFileExtension() {
|
||||
return ".pbw";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppListFetching() {
|
||||
GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||
if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) {
|
||||
return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppReordering() {
|
||||
GBDevice mGBDevice = GBApplication.app().getDeviceManager().getSelectedDevice();
|
||||
if (mGBDevice != null && mGBDevice.getFirmwareVersion() != null) {
|
||||
return PebbleUtils.getFwMajor(mGBDevice.getFirmwareVersion()) >= 3;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return true;
|
||||
|
|
|
@ -19,6 +19,8 @@ package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
|||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -28,8 +30,11 @@ import java.io.InputStream;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.UriHelper;
|
||||
|
||||
/**
|
||||
|
@ -45,6 +50,8 @@ public class FossilFileReader {
|
|||
private boolean isWatchface = false;
|
||||
private String foundVersion = "(Unknown version)";
|
||||
private String foundName = "(unknown)";
|
||||
private GBDeviceApp app;
|
||||
private JSONObject mAppKeys;
|
||||
|
||||
public FossilFileReader(Uri uri, Context context) throws IOException {
|
||||
uriHelper = UriHelper.get(uri, context);
|
||||
|
@ -104,7 +111,9 @@ public class FossilFileReader {
|
|||
foundName = "Fossil Hybrid HR firmware";
|
||||
}
|
||||
|
||||
private void parseApp() throws IOException {
|
||||
private void parseApp() throws IOException, JSONException {
|
||||
mAppKeys = new JSONObject();
|
||||
mAppKeys.put("creator", "(unknown)");
|
||||
InputStream in = new BufferedInputStream(uriHelper.openInputStream());
|
||||
byte[] bytes = new byte[in.available()];
|
||||
in.read(bytes);
|
||||
|
@ -114,6 +123,7 @@ public class FossilFileReader {
|
|||
buf.position(8); // skip file handle and version
|
||||
int fileSize = buf.getInt();
|
||||
foundVersion = (int)buf.get() + "." + (int)buf.get() + "." + (int)buf.get() + "." + (int)buf.get();
|
||||
mAppKeys.put("version", foundVersion);
|
||||
buf.position(buf.position() + 8); // skip null bytes
|
||||
int jerryStart = buf.getInt();
|
||||
int appIconStart = buf.getInt();
|
||||
|
@ -127,14 +137,21 @@ public class FossilFileReader {
|
|||
ArrayList<String> filenamesCode = parseAppFilenames(buf, appIconStart,false);
|
||||
if (filenamesCode.size() > 0) {
|
||||
foundName = filenamesCode.get(0);
|
||||
mAppKeys.put("name", foundName);
|
||||
mAppKeys.put("uuid", UUID.nameUUIDFromBytes(foundName.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
ArrayList<String> filenamesIcons = parseAppFilenames(buf, layout_start,false);
|
||||
ArrayList<String> filenamesLayout = parseAppFilenames(buf, display_name_start,true);
|
||||
ArrayList<String> filenamesDisplayName = parseAppFilenames(buf, config_start,true);
|
||||
|
||||
if (filenamesDisplayName.contains("theme_class")) {
|
||||
isApp = false;
|
||||
isWatchface = true;
|
||||
mAppKeys.put("type", "WATCHFACE");
|
||||
} else {
|
||||
mAppKeys.put("type", "APP_GENERIC");
|
||||
}
|
||||
app = new GBDeviceApp(mAppKeys, false);
|
||||
}
|
||||
|
||||
private ArrayList<String> parseAppFilenames(ByteBuffer buf, int untilPosition, boolean cutTrailingNull) {
|
||||
|
@ -180,4 +197,12 @@ public class FossilFileReader {
|
|||
public String getName() {
|
||||
return foundName;
|
||||
}
|
||||
|
||||
public GBDeviceApp getGBDeviceApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public JSONObject getAppKeysJSON() {
|
||||
return mAppKeys;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,18 +17,38 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class FossilHRInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FossilHRInstallHandler.class);
|
||||
|
||||
private final Uri mUri;
|
||||
private final Context mContext;
|
||||
private FossilFileReader fossilFile;
|
||||
|
@ -55,19 +75,15 @@ public class FossilHRInstallHandler implements InstallHandler {
|
|||
return;
|
||||
}
|
||||
GenericItem installItem = new GenericItem();
|
||||
installItem.setName(fossilFile.getName());
|
||||
installItem.setDetails(fossilFile.getVersion());
|
||||
if (fossilFile.isFirmware()) {
|
||||
installItem.setIcon(R.drawable.ic_firmware);
|
||||
installItem.setName(fossilFile.getName());
|
||||
installItem.setDetails(fossilFile.getVersion());
|
||||
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, "(unknown)"));
|
||||
} else if (fossilFile.isApp()) {
|
||||
installItem.setName(fossilFile.getName());
|
||||
installItem.setDetails(fossilFile.getVersion());
|
||||
installItem.setIcon(R.drawable.ic_watchapp);
|
||||
installActivity.setInfoText(mContext.getString(R.string.app_install_info, installItem.getName(), fossilFile.getVersion(), "(unknown)"));
|
||||
} else if (fossilFile.isWatchface()) {
|
||||
installItem.setName(fossilFile.getName());
|
||||
installItem.setDetails(fossilFile.getVersion());
|
||||
installItem.setIcon(R.drawable.ic_watchface);
|
||||
installActivity.setInfoText(mContext.getString(R.string.watchface_install_info, installItem.getName(), fossilFile.getVersion(), "(unknown)"));
|
||||
} else {
|
||||
|
@ -82,6 +98,50 @@ public class FossilHRInstallHandler implements InstallHandler {
|
|||
|
||||
@Override
|
||||
public void onStartInstall(GBDevice device) {
|
||||
DeviceCoordinator mCoordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(mContext);
|
||||
manager.sendBroadcast(new Intent(GB.ACTION_SET_PROGRESS_BAR).putExtra(GB.PROGRESS_BAR_INDETERMINATE, true));
|
||||
if (fossilFile.isFirmware()) {
|
||||
return;
|
||||
}
|
||||
GBDeviceApp app;
|
||||
File destDir;
|
||||
// write app file
|
||||
try {
|
||||
app = fossilFile.getGBDeviceApp();
|
||||
destDir = mCoordinator.getAppCacheDir();
|
||||
destDir.mkdirs();
|
||||
FileUtils.copyURItoFile(mContext, mUri, new File(destDir, app.getUUID().toString() + mCoordinator.getAppFileExtension()));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Saving app in cache failed: " + e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
// write app metadata
|
||||
File outputFile = new File(destDir, app.getUUID().toString() + ".json");
|
||||
Writer writer;
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(outputFile));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to open output file: " + e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
LOG.info(app.getJSON().toString());
|
||||
JSONObject appJSON = app.getJSON();
|
||||
JSONObject appKeysJSON = fossilFile.getAppKeysJSON();
|
||||
if (appKeysJSON != null) {
|
||||
appJSON.put("appKeys", appKeysJSON);
|
||||
}
|
||||
writer.write(appJSON.toString());
|
||||
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to write to output file: " + e.getMessage(), e);
|
||||
} catch (JSONException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
// refresh list
|
||||
manager.sendBroadcast(new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -57,7 +57,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
|||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_COMMAND_UPDATE_WIDGETS;
|
||||
|
||||
public class HRConfigActivity extends AbstractGBActivity implements View.OnClickListener {
|
||||
public class HRConfigActivity extends AbstractGBActivity {
|
||||
private SharedPreferences sharedPreferences;
|
||||
private WidgetListAdapter widgetListAdapter;
|
||||
private ArrayList<CustomWidget> customWidgets = new ArrayList<>();
|
||||
|
@ -73,8 +73,6 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qhybrid_hr_settings);
|
||||
|
||||
findViewById(R.id.qhybrid_apps_management_trigger).setOnClickListener(this);
|
||||
|
||||
sharedPreferences = GBApplication.getPrefs().getPreferences();
|
||||
|
||||
initMappings();
|
||||
|
@ -398,13 +396,6 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
|
|||
widgetButtonsMapping.put(R.id.qhybrid_button_widget_left, "left");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.qhybrid_apps_management_trigger) {
|
||||
startActivity(new Intent(getApplicationContext(), AppsManagementActivity.class));
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetListAdapter extends ArrayAdapter<CustomWidget> {
|
||||
public WidgetListAdapter(@NonNull List<CustomWidget> objects) {
|
||||
super(HRConfigActivity.this, 0, objects);
|
||||
|
|
|
@ -31,6 +31,8 @@ import androidx.annotation.Nullable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -39,6 +41,7 @@ import java.util.regex.Pattern;
|
|||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
|
@ -48,6 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
||||
|
@ -162,9 +166,38 @@ public class QHybridCoordinator extends AbstractDeviceCoordinator {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAppListFetching() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return isHybridHR() ? HRConfigActivity.class : ConfigActivity.class;
|
||||
return isHybridHR() ? AppManagerActivity.class : ConfigActivity.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory containing the watch app cache.
|
||||
* @throws IOException when the external files directory cannot be accessed
|
||||
*/
|
||||
public File getAppCacheDir() throws IOException {
|
||||
return new File(FileUtils.getExternalFilesDir(), "qhybrid-app-cache");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String containing the device app sort order filename.
|
||||
*/
|
||||
@Override
|
||||
public String getAppCacheSortFilename() {
|
||||
return "wappcacheorder.txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String containing the file extension for watch apps.
|
||||
*/
|
||||
@Override
|
||||
public String getAppFileExtension() {
|
||||
return ".wapp";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -774,4 +774,31 @@ public class QHybridSupport extends QHybridBaseSupport {
|
|||
((FossilHRWatchAdapter) watchAdapter).setQuickRepliesConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppInfoReq() {
|
||||
if(this.watchAdapter instanceof FossilHRWatchAdapter){
|
||||
((FossilHRWatchAdapter) watchAdapter).listApplications();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStart(UUID uuid, boolean start) {
|
||||
if(this.watchAdapter instanceof FossilHRWatchAdapter) {
|
||||
String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
|
||||
if (appName != null) {
|
||||
((FossilHRWatchAdapter) watchAdapter).activateWatchface(appName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppDelete(UUID uuid) {
|
||||
if(this.watchAdapter instanceof FossilHRWatchAdapter) {
|
||||
String appName = ((FossilHRWatchAdapter) watchAdapter).getInstalledAppNameFromUUID(uuid);
|
||||
if (appName != null) {
|
||||
watchAdapter.uninstallApp(appName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ import android.graphics.Paint;
|
|||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
@ -49,6 +47,7 @@ import java.io.InputStream;
|
|||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
|
@ -56,6 +55,7 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -64,6 +64,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
|||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
|
@ -75,6 +76,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfig
|
|||
import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
|
@ -121,6 +123,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
|
|||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply.QuickReplyConfigurationPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.quickreply.QuickReplyConfirmationPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.theme.SelectedThemePutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
|
||||
|
@ -199,7 +202,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
negotiateSymmetricKey();
|
||||
}
|
||||
|
||||
private void listApplications() {
|
||||
public void listApplications() {
|
||||
queueWrite(new ApplicationsListRequest(this));
|
||||
}
|
||||
|
||||
|
@ -246,6 +249,10 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
public void activateWatchface(String appName) {
|
||||
queueWrite(new SelectedThemePutRequest(this, appName));
|
||||
}
|
||||
|
||||
private void setVibrationStrength() {
|
||||
Prefs prefs = new Prefs(getDeviceSpecificPreferences());
|
||||
int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
|
||||
|
@ -469,6 +476,21 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
|
||||
public void setInstalledApplications(List<ApplicationInformation> installedApplications) {
|
||||
this.installedApplications = installedApplications;
|
||||
GBDeviceEventAppInfo appInfoEvent = new GBDeviceEventAppInfo();
|
||||
appInfoEvent.apps = new GBDeviceApp[installedApplications.size()];
|
||||
for (int i = 0; i < installedApplications.size(); i++) {
|
||||
String appName = installedApplications.get(i).getAppName();
|
||||
String appVersion = installedApplications.get(i).getAppVersion();
|
||||
UUID appUUID = UUID.nameUUIDFromBytes(appName.getBytes(StandardCharsets.UTF_8));
|
||||
GBDeviceApp.Type appType;
|
||||
if (installedApplications.get(i).getAppName().endsWith("App")) {
|
||||
appType = GBDeviceApp.Type.APP_GENERIC;
|
||||
} else {
|
||||
appType = GBDeviceApp.Type.WATCHFACE;
|
||||
}
|
||||
appInfoEvent.apps[i] = new GBDeviceApp(appUUID, appName, "(unknown)", appVersion, appType);
|
||||
}
|
||||
getDeviceSupport().evaluateGBDeviceEvent(appInfoEvent);
|
||||
}
|
||||
|
||||
private void uploadWidgets() {
|
||||
|
@ -786,7 +808,6 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
|
||||
@Override
|
||||
public void onInstallApp(Uri uri) {
|
||||
final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
|
||||
FossilFileReader fossilFile;
|
||||
try {
|
||||
fossilFile = new FossilFileReader(uri, getContext());
|
||||
|
@ -819,15 +840,6 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
private void toast(final String data) {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getContext(), data, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTime() {
|
||||
if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) {
|
||||
|
@ -1563,4 +1575,13 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getInstalledAppNameFromUUID(UUID uuid) {
|
||||
for (ApplicationInformation appInfo : installedApplications) {
|
||||
if (UUID.nameUUIDFromBytes(appInfo.getAppName().getBytes(StandardCharsets.UTF_8)).equals(uuid)) {
|
||||
return appInfo.getAppName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application;
|
||||
|
||||
public class ApplicationInformation {
|
||||
public class ApplicationInformation implements Comparable<ApplicationInformation> {
|
||||
String appName, version;
|
||||
int hash;
|
||||
byte fileHandle;
|
||||
|
@ -16,7 +16,16 @@ public class ApplicationInformation {
|
|||
return appName;
|
||||
}
|
||||
|
||||
public String getAppVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public byte getFileHandle() {
|
||||
return fileHandle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ApplicationInformation o) {
|
||||
return this.appName.toLowerCase().compareTo(o.getAppName().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.json.JSONObject;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
|
@ -47,6 +48,7 @@ public class ApplicationsListRequest extends FileLookupAndGetRequest{
|
|||
handle
|
||||
));
|
||||
}
|
||||
Collections.sort(applicationInfos);
|
||||
((FossilHRWatchAdapter) getAdapter()).setInstalledApplications(applicationInfos);
|
||||
GBDevice device = getAdapter().getDeviceSupport().getDevice();
|
||||
JSONArray array = new JSONArray();
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* Copyright (C) 2021 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.service.devices.qhybrid.requests.fossil_hr.theme;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.json.JsonPutRequest;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SelectedThemePutRequest extends JsonPutRequest {
|
||||
public SelectedThemePutRequest(FossilHRWatchAdapter adapter, String themeName) {
|
||||
super(createObject(themeName), adapter);
|
||||
}
|
||||
|
||||
private static JSONObject createObject(String themeName) {
|
||||
try {
|
||||
return new JSONObject()
|
||||
.put("push", new JSONObject()
|
||||
.put("set", new JSONObject()
|
||||
.put("themeApp._.config.selected_theme", themeName)
|
||||
)
|
||||
);
|
||||
} catch (JSONException e) {
|
||||
GB.toast("error creating json", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -106,10 +106,4 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/qhybrid_apps_management_trigger"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Apps management" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1163,7 +1163,7 @@
|
|||
<string name="fossil_hr_synced_activity_data">Synchronised activity data</string>
|
||||
<string name="fossil_hr_unavailable_unauthed">Not available in unauthenticated mode</string>
|
||||
<string name="fossil_hr_auth_failed">Authentication failed, limited functionality</string>
|
||||
<string name="qhybrid_title_watchface_apps">Watchface and apps</string>
|
||||
<string name="qhybrid_title_watchface">Watchface configuration</string>
|
||||
<string name="qhybrid_title_apps">Apps</string>
|
||||
<string name="qhybrid_title_background_image">Background image</string>
|
||||
<string name="qhybrid_title_file_management">File management</string>
|
||||
|
@ -1186,4 +1186,5 @@
|
|||
<string name="qhybrid_calibration_1_step">1 step</string>
|
||||
<string name="qhybrid_calibration_10_steps">10 steps</string>
|
||||
<string name="qhybrid_calibration_100_steps">100 steps</string>
|
||||
<string name="qhybrid_watchface_configuration_old_firmware">Watchface configuration screen for watches with firmware version DN1.0.2.19r and lower</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Preference
|
||||
android:title="@string/qhybrid_title_watchface"
|
||||
android:summary="@string/qhybrid_watchface_configuration_old_firmware">
|
||||
<intent
|
||||
android:targetPackage="nodomain.freeyourgadget.gadgetbridge"
|
||||
android:targetClass="nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity" />
|
||||
</Preference>
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="force_white_color_scheme"
|
||||
|
|
Loading…
Reference in New Issue