Implement App Sorting

- grab icon to move apps
- cache can be sorted but nothing will be send to watch
- if sorting apps or watchfaces, order will be sent to watch
- we try to keep track of what is installed and what not

Firmware 2.x is currently not working properly
This commit is contained in:
Andreas Shimokawa 2016-06-17 22:43:06 +02:00
parent 65ac4b364f
commit 79b439da28
8 changed files with 233 additions and 43 deletions

View File

@ -48,13 +48,21 @@ public abstract class AbstractAppManagerFragment extends Fragment {
private static final Logger LOG = LoggerFactory.getLogger(AbstractAppManagerFragment.class);
public void refreshList() {
protected void refreshList() {
}
public String getSortFilename() {
protected String getSortFilename() {
return null;
}
protected void onChangedAppOrder() {
List<UUID> 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) {
@ -62,30 +70,33 @@ public abstract class AbstractAppManagerFragment extends Fragment {
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)];
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<GBDeviceApp> iter = appList.listIterator(); iter.hasNext(); ) {
final GBDeviceApp app = iter.next();
if (app.getUUID().equals(uuid)) {
boolean found = false;
for (final ListIterator<GBDeviceApp> 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);
iter.set(app);
found = true;
break;
appList.add(app);
}
}
if (!found) {
GBDeviceApp app = new GBDeviceApp(uuid, appName, appCreator, "", appType);
app.setOnDevice(true);
appList.add(app);
}
} else {
refreshList();
}
mGBDeviceAppAdapter.notifyDataSetChanged();
}
}
@ -116,7 +127,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
return systemWatchfaces;
}
protected List<GBDeviceApp> getCachedApps() {
protected List<GBDeviceApp> getCachedApps(List<UUID> uuids) {
List<GBDeviceApp> cachedAppList = new ArrayList<>();
File cachePath;
try {
@ -126,7 +137,16 @@ public abstract class AbstractAppManagerFragment extends Fragment {
return cachedAppList;
}
File files[] = cachePath.listFiles();
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")) {
@ -140,8 +160,27 @@ public abstract class AbstractAppManagerFragment extends Fragment {
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));
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));
}
}
}
}
@ -177,9 +216,35 @@ public abstract class AbstractAppManagerFragment extends Fragment {
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<UUID> uuids = new ArrayList<UUID>();
for (GBDeviceApp gbDeviceApp : mGBDeviceAppAdapter.getItemList()) {
uuids.add(gbDeviceApp.getUUID());
}
if (concatFilename != null) {
ArrayList<UUID> concatUuids = AppManagerActivity.getUuidsFromFile(concatFilename);
uuids.addAll(concatUuids);
}
GBApplication.deviceService().onAppReorder(uuids.toArray(new UUID[uuids.size()]));
}
private void removeAppFromList(UUID uuid) {
for (final ListIterator<GBDeviceApp> iter = appList.listIterator(); iter.hasNext(); ) {
final GBDeviceApp app = iter.next();
@ -247,9 +312,13 @@ public abstract class AbstractAppManagerFragment extends Fragment {
LOG.info("deleted file: " + fileToDelete.toString());
}
}
removeAppFromList(selectedApp.getUUID());
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:
@ -274,7 +343,6 @@ public abstract class AbstractAppManagerFragment extends Fragment {
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);

View File

@ -7,13 +7,29 @@ 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() {
@ -45,6 +61,12 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
return new SectionsPagerAdapter(fragmentManager);
}
public static synchronized void deleteFromAppOrderFile(String filename, UUID uuid) {
ArrayList<UUID> uuids = getUuidsFromFile(filename);
uuids.remove(uuid);
rewriteAppOrderFile(filename, uuids);
}
public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
@ -95,4 +117,40 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
return super.onOptionsItemSelected(item);
}
static synchronized void rewriteAppOrderFile(String filename, List<UUID> 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<UUID> uuids = getUuidsFromFile(filename);
uuids.remove(uuid); // if alread there
uuids.add(uuid);
rewriteAppOrderFile(filename, uuids);
}
static synchronized ArrayList<UUID> getUuidsFromFile(String filename) {
ArrayList<UUID> 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;
}
}

View File

@ -3,6 +3,12 @@ package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
public class AppManagerFragmentCache extends AbstractAppManagerFragment {
@Override
public void refreshList() {
appList.addAll(getCachedApps());
appList.clear();
appList.addAll(getCachedApps(null));
}
@Override
public String getSortFilename() {
return "pbwcacheorder.txt";
}
}

View File

@ -1,13 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentInstalledApps extends AbstractAppManagerFragment {
@Override
public void refreshList() {
appList.addAll(getSystemApps());
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
public String getSortFilename() {
@Override
protected String getSortFilename() {
return mGBDevice.getAddress() + ".watchapps";
}
@Override
protected void onChangedAppOrder() {
super.onChangedAppOrder();
sendOrderToDevice(mGBDevice.getAddress() + ".watchfaces");
}
}

View File

@ -1,12 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.activities.appmanager;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class AppManagerFragmentInstalledWatchfaces extends AbstractAppManagerFragment {
@Override
public void refreshList() {
appList.addAll(getSystemWatchfaces());
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));
}
}
public String getSortFilename() {
@Override
protected String getSortFilename() {
return mGBDevice.getAddress() + ".watchfaces";
}
@Override
protected void onChangedAppOrder() {
super.onChangedAppOrder();
sendOrderToDevice(mGBDevice.getAddress() + ".watchapps");
}
}

View File

@ -59,10 +59,6 @@ public class GBDeviceAppAdapter extends DragItemAdapter<GBDeviceApp, GBDeviceApp
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" : "") + ")";
}
holder.mDeviceAppNameLabel.setText(appNameLabelText);
switch (deviceApp.getType()) {

View File

@ -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() {

View File

@ -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();