diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd0ff297..a73f51392 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ####Version 0.10.2 * Pebble: allow to manually paste configuration data for legacy configuration pages * Pebble: various improvements to the configuration page +* Pebble: Suppport FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge) +* Pebble: Fix a problem with key events when using the Pebble music player ####Version 0.10.1 * Pebble: set extended music info by dissecting notifications on Android 5.0+ diff --git a/app/build.gradle b/app/build.gradle index 607ee74b9..3f6389ce6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.10.1" - versionCode 54 + versionName "0.10.2" + versionCode 55 } buildTypes { release { @@ -60,6 +60,7 @@ dependencies { compile 'de.cketti.library.changelog:ckchangelog:1.2.2' compile 'net.e175.klaus:solarpositioning:0.0.9' compile 'com.github.freeyourgadget:greendao:c3830951e5dd3d1e63d7bac600d5f773b81df363' + compile 'com.github.woxthebox:draglistview:1.2.6' } preBuild.dependsOn(":GBDaoGenerator:genSources") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4535b4b30..97599340a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,7 +55,7 @@ android:parentActivityName=".activities.SettingsActivity" /> + android:parentActivityName=".activities.appmanager.AppManagerActivity"> diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java deleted file mode 100644 index a6dae1b2d..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java +++ /dev/null @@ -1,286 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.activities; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.support.v4.content.LocalBroadcastManager; -import android.view.ContextMenu; -import android.view.MenuItem; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; -import java.util.UUID; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; -import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol; -import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; -import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; - - -public class AppManagerActivity extends GBActivity { - public static final String ACTION_REFRESH_APPLIST - = "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist"; - private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class); - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (action.equals(GBApplication.ACTION_QUIT)) { - finish(); - } else if (action.equals(ACTION_REFRESH_APPLIST)) { - int appCount = intent.getIntExtra("app_count", 0); - for (Integer i = 0; i < appCount; i++) { - String appName = intent.getStringExtra("app_name" + i.toString()); - String appCreator = intent.getStringExtra("app_creator" + i.toString()); - UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString())); - GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)]; - - boolean found = false; - for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { - final GBDeviceApp app = iter.next(); - if (app.getUUID().equals(uuid)) { - app.setOnDevice(true); - iter.set(app); - found = true; - break; - } - } - if (!found) { - GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType); - app.setOnDevice(true); - appList.add(app); - } - } - - mGBDeviceAppAdapter.notifyDataSetChanged(); - } - } - }; - - private Prefs prefs; - - private final List appList = new ArrayList<>(); - private GBDeviceAppAdapter mGBDeviceAppAdapter; - private GBDeviceApp selectedApp = null; - private GBDevice mGBDevice = null; - - private List getSystemApps() { - List systemApps = new ArrayList<>(); - if (prefs.getBoolean("pebble_force_untested", false)) { - 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)); - } - if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { - systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); - } - - return systemApps; - } - - private List getCachedApps() { - List cachedAppList = new ArrayList<>(); - File cachePath; - try { - cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache"); - } catch (IOException e) { - LOG.warn("could not get external dir while reading pbw cache."); - return cachedAppList; - } - - File files[] = cachePath.listFiles(); - if (files != null) { - for (File file : files) { - if (file.getName().endsWith(".pbw")) { - String baseName = file.getName().substring(0, file.getName().length() - 4); - //metadata - File jsonFile = new File(cachePath, baseName + ".json"); - //configuration - File configFile = new File(cachePath, baseName + "_config.js"); - try { - String jsonstring = FileUtils.getStringFromFile(jsonFile); - JSONObject json = new JSONObject(jsonstring); - cachedAppList.add(new GBDeviceApp(json, configFile.exists())); - } catch (Exception e) { - LOG.warn("could not read json file for " + baseName, e.getMessage(), e); - cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN)); - } - } - } - } - return cachedAppList; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - Bundle extras = getIntent().getExtras(); - if (extras != null) { - mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); - } else { - throw new IllegalArgumentException("Must provide a device when invoking this activity"); - } - - prefs = GBApplication.getPrefs(); - - setContentView(R.layout.activity_appmanager); - - ListView appListView = (ListView) findViewById(R.id.appListView); - mGBDeviceAppAdapter = new GBDeviceAppAdapter(this, appList); - appListView.setAdapter(this.mGBDeviceAppAdapter); - - appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View v, int position, long id) { - UUID uuid = appList.get(position).getUUID(); - GBApplication.deviceService().onAppStart(uuid, true); - } - }); - - registerForContextMenu(appListView); - - appList.addAll(getCachedApps()); - - appList.addAll(getSystemApps()); - - IntentFilter filter = new IntentFilter(); - filter.addAction(GBApplication.ACTION_QUIT); - filter.addAction(ACTION_REFRESH_APPLIST); - - LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter); - - GBApplication.deviceService().onAppInfoReq(); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - getMenuInflater().inflate(R.menu.appmanager_context, menu); - AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo; - selectedApp = appList.get(acmi.position); - - if (!selectedApp.isInCache()) { - menu.removeItem(R.id.appmanager_app_reinstall); - menu.removeItem(R.id.appmanager_app_delete_cache); - } - if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { - menu.removeItem(R.id.appmanager_health_activate); - menu.removeItem(R.id.appmanager_health_deactivate); - } - if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) { - menu.removeItem(R.id.appmanager_app_delete); - } - if (!selectedApp.isConfigurable()) { - menu.removeItem(R.id.appmanager_app_configure); - } - if (mGBDevice != null && !mGBDevice.getFirmwareVersion().startsWith("v3")) { - menu.removeItem(R.id.appmanager_app_move_to_top); - } - menu.setHeaderTitle(selectedApp.getName()); - } - - private void removeAppFromList(UUID uuid) { - for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { - final GBDeviceApp app = iter.next(); - if (app.getUUID().equals(uuid)) { - iter.remove(); - mGBDeviceAppAdapter.notifyDataSetChanged(); - break; - } - } - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.appmanager_health_deactivate: - case R.id.appmanager_app_delete_cache: - String baseName; - try { - baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); - } catch (IOException e) { - LOG.warn("could not get external dir while trying to access pbw cache."); - return true; - } - - String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"}; - - for (String suffix : suffixToDelete) { - File fileToDelete = new File(baseName + suffix); - if (!fileToDelete.delete()) { - LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString()); - } else { - LOG.info("deleted file: " + fileToDelete.toString()); - } - } - removeAppFromList(selectedApp.getUUID()); - // fall through - case R.id.appmanager_app_delete: - GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); - return true; - case R.id.appmanager_app_reinstall: - File cachePath; - try { - cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); - } catch (IOException e) { - LOG.warn("could not get external dir while trying to access pbw cache."); - return true; - } - GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath)); - return true; - case R.id.appmanager_health_activate: - GBApplication.deviceService().onInstallApp(Uri.parse("fake://health")); - return true; - case R.id.appmanager_app_configure: - GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true); - - Intent startIntent = new Intent(getApplicationContext(), ExternalPebbleJSActivity.class); - startIntent.putExtra(DeviceService.EXTRA_APP_UUID, selectedApp.getUUID()); - startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice); - startActivity(startIntent); - return true; - case R.id.appmanager_app_move_to_top: - GBApplication.deviceService().onAppReorder(new UUID[]{selectedApp.getUUID()}); - return true; - default: - return super.onContextItemSelected(item); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onDestroy() { - LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); - super.onDestroy(); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java new file mode 100644 index 000000000..f96615db1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java @@ -0,0 +1,354 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.appmanager; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.widget.LinearLayoutManager; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.PopupMenu; + +import com.woxthebox.draglistview.DragListView; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.UUID; + +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.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + + +public abstract class AbstractAppManagerFragment extends Fragment { + public static final String ACTION_REFRESH_APPLIST + = "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist"; + private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class); + + + protected void refreshList() { + } + + protected String getSortFilename() { + return null; + } + + protected void onChangedAppOrder() { + List uuidList = new ArrayList<>(); + for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getItemList()) { + uuidList.add(gbDeviceApp.getUUID()); + } + AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuidList); + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(GBApplication.ACTION_QUIT)) { + // finish(); + } else if (action.equals(ACTION_REFRESH_APPLIST)) { + if (intent.hasExtra("app_count")) { + int appCount = intent.getIntExtra("app_count", 0); + for (Integer i = 0; i < appCount; i++) { + String appName = intent.getStringExtra("app_name" + i.toString()); + String appCreator = intent.getStringExtra("app_creator" + i.toString()); + UUID uuid = UUID.fromString(intent.getStringExtra("app_uuid" + i.toString())); + GBDeviceApp.Type appType = GBDeviceApp.Type.values()[intent.getIntExtra("app_type" + i.toString(), 0)]; + + boolean found = false; + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + app.setOnDevice(true); + iter.set(app); + found = true; + break; + } + } + if (!found) { + GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType); + app.setOnDevice(true); + appList.add(app); + } + } + } else { + refreshList(); + } + mGBDeviceAppAdapter.notifyDataSetChanged(); + } + } + }; + + private Prefs prefs; + + protected final List appList = new ArrayList<>(); + private GBDeviceAppAdapter mGBDeviceAppAdapter; + protected GBDevice mGBDevice = null; + + protected List getSystemApps() { + List systemApps = new ArrayList<>(); + if (prefs.getBoolean("pebble_force_untested", false)) { + 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)); + } + if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { + systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM)); + } + + return systemApps; + } + + protected List getSystemWatchfaces() { + List systemWatchfaces = new ArrayList<>(); + systemWatchfaces.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM)); + return systemWatchfaces; + } + + protected List getCachedApps(List uuids) { + List cachedAppList = new ArrayList<>(); + File cachePath; + try { + cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache"); + } catch (IOException e) { + LOG.warn("could not get external dir while reading pbw cache."); + return cachedAppList; + } + + File[] files; + if (uuids == null) { + files = cachePath.listFiles(); + } else { + files = new File[uuids.size()]; + int index = 0; + for (UUID uuid : uuids) { + files[index++] = new File(uuid.toString() + ".pbw"); + } + } + if (files != null) { + for (File file : files) { + if (file.getName().endsWith(".pbw")) { + String baseName = file.getName().substring(0, file.getName().length() - 4); + //metadata + File jsonFile = new File(cachePath, baseName + ".json"); + //configuration + File configFile = new File(cachePath, baseName + "_config.js"); + try { + String jsonstring = FileUtils.getStringFromFile(jsonFile); + JSONObject json = new JSONObject(jsonstring); + 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 + if (prefs.getBoolean("pebble_force_untested", false)) { + 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 (baseName.equals("8f3c8686-31a1-4f5f-91f5-01600c9bdc59")) { + cachedAppList.add(new GBDeviceApp(UUID.fromString("8f3c8686-31a1-4f5f-91f5-01600c9bdc59"), "Tic Toc (System)", "Pebble Inc.", "", GBDeviceApp.Type.WATCHFACE_SYSTEM)); + } + if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) { + 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 (uuids == null) { + cachedAppList.add(new GBDeviceApp(UUID.fromString(baseName), baseName, "N/A", "", GBDeviceApp.Type.UNKNOWN)); + } + } + } + } + } + return cachedAppList; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + mGBDevice = ((AppManagerActivity) getActivity()).getGBDevice(); + + prefs = GBApplication.getPrefs(); + + refreshList(); + IntentFilter filter = new IntentFilter(); + filter.addAction(GBApplication.ACTION_QUIT); + filter.addAction(ACTION_REFRESH_APPLIST); + + LocalBroadcastManager.getInstance(getContext()).registerReceiver(mReceiver, filter); + + GBApplication.deviceService().onAppInfoReq(); + + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View rootView = inflater.inflate(R.layout.activity_appmanager, container, false); + + DragListView appListView = (DragListView) (rootView.findViewById(R.id.appListView)); + appListView.setLayoutManager(new LinearLayoutManager(getActivity())); + mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_with_details, R.id.item_image, this.getContext(), this); + appListView.setAdapter(mGBDeviceAppAdapter, false); + appListView.setCanDragHorizontally(false); + appListView.setDragListListener(new DragListView.DragListListener() { + @Override + public void onItemDragStarted(int position) { + } + + @Override + public void onItemDragging(int itemPosition, float x, float y) { + } + + @Override + public void onItemDragEnded(int fromPosition, int toPosition) { + onChangedAppOrder(); + } + }); + return rootView; + } + + protected void sendOrderToDevice(String concatFilename) { + ArrayList uuids = new ArrayList(); + for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getItemList()) { + uuids.add(gbDeviceApp.getUUID()); + } + if (concatFilename != null) { + ArrayList concatUuids = AppManagerActivity.getUuidsFromFile(concatFilename); + uuids.addAll(concatUuids); + } + GBApplication.deviceService().onAppReorder(uuids.toArray(new UUID[uuids.size()])); + } + + private void removeAppFromList(UUID uuid) { + for (final ListIterator iter = appList.listIterator(); iter.hasNext(); ) { + final GBDeviceApp app = iter.next(); + if (app.getUUID().equals(uuid)) { + iter.remove(); + mGBDeviceAppAdapter.notifyDataSetChanged(); + break; + } + } + } + + public boolean openPopupMenu(View view, int position) { + PopupMenu popupMenu = new PopupMenu(getContext(), view); + popupMenu.getMenuInflater().inflate(R.menu.appmanager_context, popupMenu.getMenu()); + Menu menu = popupMenu.getMenu(); + final GBDeviceApp selectedApp = appList.get(position); + + if (!selectedApp.isInCache()) { + menu.removeItem(R.id.appmanager_app_reinstall); + menu.removeItem(R.id.appmanager_app_delete_cache); + } + if (!PebbleProtocol.UUID_PEBBLE_HEALTH.equals(selectedApp.getUUID())) { + menu.removeItem(R.id.appmanager_health_activate); + menu.removeItem(R.id.appmanager_health_deactivate); + } + if (selectedApp.getType() == GBDeviceApp.Type.APP_SYSTEM) { + menu.removeItem(R.id.appmanager_app_delete); + } + if (!selectedApp.isConfigurable()) { + menu.removeItem(R.id.appmanager_app_configure); + } + //menu.setHeaderTitle(selectedApp.getName()); + popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + return onContextItemSelected(item, selectedApp); + } + } + ); + popupMenu.show(); + return false; // FIXME: whats that for? + } + + public boolean onContextItemSelected(MenuItem item, GBDeviceApp selectedApp) { + switch (item.getItemId()) { + case R.id.appmanager_health_deactivate: + case R.id.appmanager_app_delete_cache: + String baseName; + try { + baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID(); + } catch (IOException e) { + LOG.warn("could not get external dir while trying to access pbw cache."); + return true; + } + + String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js"}; + + for (String suffix : suffixToDelete) { + File fileToDelete = new File(baseName + suffix); + if (!fileToDelete.delete()) { + LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString()); + } else { + LOG.info("deleted file: " + fileToDelete.toString()); + } + } + AppManagerActivity.deleteFromAppOrderFile("pbwcacheorder.txt", selectedApp.getUUID()); // FIXME: only if successful + // fall through + case R.id.appmanager_app_delete: + 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); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent); + GBApplication.deviceService().onAppDelete(selectedApp.getUUID()); + return true; + case R.id.appmanager_app_reinstall: + File cachePath; + try { + cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw"); + } catch (IOException e) { + LOG.warn("could not get external dir while trying to access pbw cache."); + return true; + } + GBApplication.deviceService().onInstallApp(Uri.fromFile(cachePath)); + return true; + case R.id.appmanager_health_activate: + GBApplication.deviceService().onInstallApp(Uri.parse("fake://health")); + return true; + case R.id.appmanager_app_configure: + GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true); + + Intent startIntent = new Intent(getContext().getApplicationContext(), ExternalPebbleJSActivity.class); + startIntent.putExtra(DeviceService.EXTRA_APP_UUID, selectedApp.getUUID()); + startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice); + startActivity(startIntent); + return true; + default: + return super.onContextItemSelected(item); + } + } + + + @Override + public void onDestroy() { + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(mReceiver); + super.onDestroy(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java new file mode 100644 index 000000000..e1a9d44bf --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerActivity.java @@ -0,0 +1,156 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.appmanager; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.NavUtils; +import android.support.v4.view.ViewPager; +import android.view.MenuItem; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractFragmentPagerAdapter; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragmentActivity; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; + + +public class AppManagerActivity extends AbstractGBFragmentActivity { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class); + + private GBDevice mGBDevice = null; + + public GBDevice getGBDevice() { + return mGBDevice; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_fragmentappmanager); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); + } else { + throw new IllegalArgumentException("Must provide a device when invoking this activity"); + } + + + // Set up the ViewPager with the sections adapter. + ViewPager viewPager = (ViewPager) findViewById(R.id.appmanager_pager); + viewPager.setAdapter(getPagerAdapter()); + } + + @Override + protected AbstractFragmentPagerAdapter createFragmentPagerAdapter(FragmentManager fragmentManager) { + return new SectionsPagerAdapter(fragmentManager); + } + + public static synchronized void deleteFromAppOrderFile(String filename, UUID uuid) { + ArrayList uuids = getUuidsFromFile(filename); + uuids.remove(uuid); + rewriteAppOrderFile(filename, uuids); + } + + public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter { + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + // getItem is called to instantiate the fragment for the given page. + switch (position) { + case 0: + return new AppManagerFragmentCache(); + case 1: + return new AppManagerFragmentInstalledApps(); + case 2: + return new AppManagerFragmentInstalledWatchfaces(); + } + return null; + } + + @Override + public int getCount() { + return 3; + } + + @Override + public CharSequence getPageTitle(int position) { + switch (position) { + case 0: + return "Apps in cache"; + case 1: + return "Installed apps"; + case 2: + return "Installed watchfaces"; + case 3: + } + return super.getPageTitle(position); + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + NavUtils.navigateUpFromSameTask(this); + return true; + } + return super.onOptionsItemSelected(item); + } + + + static synchronized void rewriteAppOrderFile(String filename, List uuids) { + try { + FileWriter fileWriter = new FileWriter(FileUtils.getExternalFilesDir() + "/" + filename); + BufferedWriter out = new BufferedWriter(fileWriter); + for (UUID uuid : uuids) { + out.write(uuid.toString()); + out.newLine(); + } + out.close(); + } catch (IOException e) { + LOG.warn("can't write app order to file!"); + } + } + + synchronized public static void addToAppOrderFile(String filename, UUID uuid) { + ArrayList uuids = getUuidsFromFile(filename); + uuids.remove(uuid); // if alread there + uuids.add(uuid); + rewriteAppOrderFile(filename, uuids); + } + + static synchronized ArrayList getUuidsFromFile(String filename) { + ArrayList uuids = new ArrayList<>(); + try { + FileReader fileReader = new FileReader(FileUtils.getExternalFilesDir() + "/" + filename); + BufferedReader in = new BufferedReader(fileReader); + String line; + while ((line = in.readLine()) != null) { + uuids.add(UUID.fromString(line)); + } + } catch (IOException e) { + LOG.warn("could not read sort file"); + } + return uuids; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java new file mode 100644 index 000000000..6435bc125 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentCache.java @@ -0,0 +1,14 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.appmanager; + +public class AppManagerFragmentCache extends AbstractAppManagerFragment { + @Override + public void refreshList() { + appList.clear(); + appList.addAll(getCachedApps(null)); + } + + @Override + public String getSortFilename() { + return "pbwcacheorder.txt"; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java new file mode 100644 index 000000000..474e4c24d --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledApps.java @@ -0,0 +1,33 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.appmanager; + +import java.util.ArrayList; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; + +public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment { + @Override + protected void refreshList() { + appList.clear(); + ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename()); + if (uuids.isEmpty()) { + appList.addAll(getSystemApps()); + for (GBDeviceApp gbDeviceApp : appList) { + uuids.add(gbDeviceApp.getUUID()); + } + AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids); + } else { + appList.addAll(getCachedApps(uuids)); + } + } + + @Override + protected String getSortFilename() { + return mGBDevice.getAddress() + ".watchapps"; + } + + @Override + protected void onChangedAppOrder() { + super.onChangedAppOrder(); + sendOrderToDevice(mGBDevice.getAddress() + ".watchfaces"); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java new file mode 100644 index 000000000..799584591 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AppManagerFragmentInstalledWatchfaces.java @@ -0,0 +1,33 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.appmanager; + +import java.util.ArrayList; + +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; + +public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment { + @Override + protected void refreshList() { + appList.clear(); + ArrayList uuids = AppManagerActivity.getUuidsFromFile(getSortFilename()); + if (uuids.isEmpty()) { + appList.addAll(getSystemWatchfaces()); + for (GBDeviceApp gbDeviceApp : appList) { + uuids.add(gbDeviceApp.getUUID()); + } + AppManagerActivity.rewriteAppOrderFile(getSortFilename(), uuids); + } else { + appList.addAll(getCachedApps(uuids)); + } + } + + @Override + protected String getSortFilename() { + return mGBDevice.getAddress() + ".watchfaces"; + } + + @Override + protected void onChangedAppOrder() { + super.onChangedAppOrder(); + sendOrderToDevice(mGBDevice.getAddress() + ".watchapps"); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java index d4ae263f3..35549ae80 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAppAdapter.java @@ -4,69 +4,102 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; -import java.util.List; +import com.woxthebox.draglistview.DragItemAdapter; +import java.util.List; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; /** * Adapter for displaying GBDeviceApp instances. */ -public class GBDeviceAppAdapter extends ArrayAdapter { - private final Context context; +public class GBDeviceAppAdapter extends DragItemAdapter { - public GBDeviceAppAdapter(Context context, List appList) { - super(context, 0, appList); + private final int mLayoutId; + private final int mGrabHandleId; + private final Context mContext; + private final AbstractAppManagerFragment mParentFragment; - this.context = context; + public GBDeviceAppAdapter(List list, int layoutId, int grabHandleId, Context context, AbstractAppManagerFragment parentFragment) { + super(true); // longpress + mLayoutId = layoutId; + mGrabHandleId = grabHandleId; + mContext = context; + mParentFragment = parentFragment; + setHasStableIds(true); + setItemList(list); } @Override - public View getView(int position, View view, ViewGroup parent) { - GBDeviceApp deviceApp = getItem(position); + public long getItemId(int position) { + return mItemList.get(position).getUUID().getLeastSignificantBits(); + } - if (view == null) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - view = inflater.inflate(R.layout.item_with_details, parent, false); - } - TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details); - TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name); - ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image); + View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false); + return new ViewHolder(view); + } - deviceAppVersionAuthorLabel.setText(getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator())); + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + GBDeviceApp deviceApp = mItemList.get(position); + + holder.mDeviceAppVersionAuthorLabel.setText(GBApplication.getContext().getString(R.string.appversion_by_creator, deviceApp.getVersion(), deviceApp.getCreator())); // FIXME: replace with small icons String appNameLabelText = deviceApp.getName(); - if (deviceApp.isInCache() || deviceApp.isOnDevice()) { - appNameLabelText += " (" + (deviceApp.isInCache() ? "C" : "") - + (deviceApp.isOnDevice() ? "D" : "") + ")"; - } - deviceAppNameLabel.setText(appNameLabelText); + holder.mDeviceAppNameLabel.setText(appNameLabelText); switch (deviceApp.getType()) { case APP_GENERIC: - deviceImageView.setImageResource(R.drawable.ic_watchapp); + holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp); break; case APP_ACTIVITYTRACKER: - deviceImageView.setImageResource(R.drawable.ic_activitytracker); + holder.mDeviceImageView.setImageResource(R.drawable.ic_activitytracker); break; case APP_SYSTEM: - deviceImageView.setImageResource(R.drawable.ic_systemapp); + holder.mDeviceImageView.setImageResource(R.drawable.ic_systemapp); break; case WATCHFACE: - deviceImageView.setImageResource(R.drawable.ic_watchface); + holder.mDeviceImageView.setImageResource(R.drawable.ic_watchface); break; default: - deviceImageView.setImageResource(R.drawable.ic_watchapp); + holder.mDeviceImageView.setImageResource(R.drawable.ic_watchapp); + } + } + + public class ViewHolder extends DragItemAdapter.ViewHolder { + TextView mDeviceAppVersionAuthorLabel; + TextView mDeviceAppNameLabel; + ImageView mDeviceImageView; + + public ViewHolder(final View itemView) { + super(itemView, mGrabHandleId); + mDeviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details); + mDeviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name); + mDeviceImageView = (ImageView) itemView.findViewById(R.id.item_image); } - return view; + @Override + public void onItemClicked(View view) { + UUID uuid = mItemList.get(getAdapterPosition()).getUUID(); + GBApplication.deviceService().onAppStart(uuid, true); + } + + @Override + public boolean onItemLongClicked(View view) { + return mParentFragment.openPopupMenu(view, getAdapterPosition()); + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java index 9d66c0dbc..a00f49b2d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWInstallHandler.java @@ -18,6 +18,7 @@ import java.io.Writer; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; @@ -135,6 +136,8 @@ public class PBWInstallHandler implements InstallHandler { destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); destDir.mkdirs(); FileUtils.copyURItoFile(mContext, mUri, new File(destDir, app.getUUID().toString() + ".pbw")); + + AppManagerActivity.addToAppOrderFile("pbwcacheorder.txt", app.getUUID()); } catch (IOException e) { LOG.error("Installation failed: " + e.getMessage(), e); return; @@ -174,6 +177,7 @@ public class PBWInstallHandler implements InstallHandler { LOG.error("Failed to open output file: " + e.getMessage(), e); } } + } public boolean isValid() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 6ada2cd7d..99b5e1557 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -6,17 +6,14 @@ import android.net.Uri; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity; -import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +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; import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; -import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample; 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.Prefs; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java index f55876453..5cbd0fab0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceApp.java @@ -89,6 +89,7 @@ public class GBDeviceApp { public enum Type { UNKNOWN, WATCHFACE, + WATCHFACE_SYSTEM, APP_GENERIC, APP_ACTIVITYTRACKER, APP_SYSTEM, diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java index 21d7965b9..627a1cf51 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java @@ -24,7 +24,7 @@ import java.util.Objects; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsHost; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; @@ -160,7 +160,7 @@ public abstract class AbstractDeviceSupport implements DeviceSupport { Context context = getContext(); LOG.info("Got event for APP_INFO"); - Intent appInfoIntent = new Intent(AppManagerActivity.ACTION_REFRESH_APPLIST); + Intent appInfoIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST); int appCount = appInfoEvent.apps.length; appInfoIntent.putExtra("app_count", appCount); for (Integer i = 0; i < appCount; i++) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index e7fd0d7e7..cb0764711 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.ParcelUuid; +import android.support.v4.content.LocalBroadcastManager; import org.json.JSONArray; import org.json.JSONException; @@ -28,6 +29,8 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment; +import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; @@ -75,6 +78,7 @@ public class PebbleIoThread extends GBDeviceIoThread { private boolean mIsInstalling = false; private PBWReader mPBWReader = null; + private GBDeviceApp mCurrentlyInstallingApp = null; private int mAppInstallToken = -1; private InputStream mFis = null; private PebbleAppInstallState mInstallState = PebbleAppInstallState.UNKNOWN; @@ -613,12 +617,12 @@ public class PebbleIoThread extends GBDeviceIoThread { */ writeInstallApp(mPebbleProtocol.encodeGetTime()); } else { - GBDeviceApp app = mPBWReader.getGBDeviceApp(); + mCurrentlyInstallingApp = mPBWReader.getGBDeviceApp(); if (mPebbleProtocol.mFwMajor >= 3 && !mPBWReader.isLanguage()) { if (appId == 0) { // only install metadata - not the binaries - write(mPebbleProtocol.encodeInstallMetadata(app.getUUID(), app.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion(), mPBWReader.getFlags(), mPBWReader.getIconId())); - write(mPebbleProtocol.encodeAppStart(app.getUUID(), true)); + write(mPebbleProtocol.encodeInstallMetadata(mCurrentlyInstallingApp.getUUID(), mCurrentlyInstallingApp.getName(), mPBWReader.getAppVersion(), mPBWReader.getSdkVersion(), mPBWReader.getFlags(), mPBWReader.getIconId())); + write(mPebbleProtocol.encodeAppStart(mCurrentlyInstallingApp.getUUID(), true)); } else { // this came from an app fetch request, so do the real stuff mIsInstalling = true; @@ -637,7 +641,7 @@ public class PebbleIoThread extends GBDeviceIoThread { writeInstallApp(mPebbleProtocol.encodeGetTime()); } else { mInstallState = PebbleAppInstallState.WAIT_SLOT; - writeInstallApp(mPebbleProtocol.encodeAppDelete(app.getUUID())); + writeInstallApp(mPebbleProtocol.encodeAppDelete(mCurrentlyInstallingApp.getUUID())); } } } @@ -651,6 +655,17 @@ public class PebbleIoThread extends GBDeviceIoThread { GB.updateInstallNotification(getContext().getString(R.string.installation_failed_), false, 0, getContext()); } else { GB.updateInstallNotification(getContext().getString(R.string.installation_successful), false, 0, getContext()); + String filenameSuffix; + if (mCurrentlyInstallingApp != null) { + if (mCurrentlyInstallingApp.getType() == GBDeviceApp.Type.WATCHFACE) { + filenameSuffix = ".watchfaces"; + } else { + filenameSuffix = ".watchapps"; + } + AppManagerActivity.addToAppOrderFile(gbDevice.getAddress() + filenameSuffix, mCurrentlyInstallingApp.getUUID()); + Intent refreshIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(refreshIntent); + } } mInstallState = PebbleAppInstallState.UNKNOWN; @@ -660,6 +675,8 @@ public class PebbleIoThread extends GBDeviceIoThread { mPBWReader = null; mIsInstalling = false; + mCurrentlyInstallingApp = null; + if (mFis != null) { try { mFis.close(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 262d1a4a9..b209d24d1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -1109,6 +1109,10 @@ public class PebbleProtocol extends GBDeviceProtocol { } public byte[] encodeSetMusicState(byte state, int position, int playRate, byte shuffle, byte repeat) { + if (mFwMajor < 3) { + return null; + } + byte playState; switch (state) { @@ -1144,7 +1148,7 @@ public class PebbleProtocol extends GBDeviceProtocol { @Override public byte[] encodeSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) { String[] parts = {artist, album, track}; - if (duration == 0) { + if (duration == 0 || mFwMajor < 3) { return encodeMessage(ENDPOINT_MUSICCONTROL, MUSICCONTROL_SETMUSICINFO, 0, parts); } else { // Calculate length first diff --git a/app/src/main/res/layout/activity_appmanager.xml b/app/src/main/res/layout/activity_appmanager.xml index 868f628fa..bdc6317c3 100644 --- a/app/src/main/res/layout/activity_appmanager.xml +++ b/app/src/main/res/layout/activity_appmanager.xml @@ -1,16 +1,17 @@ - + - + - + + + + diff --git a/app/src/main/res/layout/activity_fragmentappmanager.xml b/app/src/main/res/layout/activity_fragmentappmanager.xml new file mode 100644 index 000000000..639a58f72 --- /dev/null +++ b/app/src/main/res/layout/activity_fragmentappmanager.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/menu/appmanager_context.xml b/app/src/main/res/menu/appmanager_context.xml index 72cb29300..873e0c246 100644 --- a/app/src/main/res/menu/appmanager_context.xml +++ b/app/src/main/res/menu/appmanager_context.xml @@ -18,8 +18,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 089322bed..31efd8ece 100644 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -1,11 +1,10 @@ - - Pebble: allow to manually paste configuration data for legacy configuration pages - + + Pebble: allow to manually paste configuration data for legacy configuration pages Pebble: various improvements to the configuration page + Pebble: Suppport FW 4.0-dp1 and Pebble2 emulator (needs recompilation of Gadgetbridge) + Pebble: Fix a problem with key events when using the Pebble music player Pebble: set extended music info by dissecting notifications on Android 5.0+