Improvements to the install activity #30

- made it independent of Mi fw and Pebble fw + app classes
- automatically connect to the last used device
- some other small fixes/improvements
master
cpfeiffer 2015-08-06 02:17:38 +02:00
parent 8dee55198e
commit 2a2eae068a
14 changed files with 274 additions and 67 deletions

View File

@ -18,28 +18,25 @@ import android.widget.Toast;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FwAppInstallerActivity extends Activity { public class FwAppInstallerActivity extends Activity implements InstallActivity {
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class); private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
TextView fwAppInstallTextView; private TextView fwAppInstallTextView;
Button installButton; private Button installButton;
private Uri uri;
// FIXME: abstraction for these would make code cleaner in this class private GBDevice device;
private PBWReader mPBWReader = null; private InstallHandler installHandler;
private MiBandFWHelper mFwReader = null; private boolean mayConnect;
private BroadcastReceiver mReceiver = new BroadcastReceiver() { private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override @Override
@ -48,31 +45,43 @@ public class FwAppInstallerActivity extends Activity {
if (action.equals(ControlCenter.ACTION_QUIT)) { if (action.equals(ControlCenter.ACTION_QUIT)) {
finish(); finish();
} else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) { } else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
GBDevice dev = intent.getParcelableExtra("device"); device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (dev.getType() == DeviceType.PEBBLE && mPBWReader != null) { if (device != null) {
if (mPBWReader.isFirmware()) { if (!device.isConnected()) {
String hwRevision = mPBWReader.getHWRevision(); if (mayConnect) {
if (hwRevision != null && hwRevision.equals(dev.getHardwareVersion()) && dev.isConnected()) { GB.toast(FwAppInstallerActivity.this, getString(R.string.connecting), Toast.LENGTH_SHORT, GB.INFO);
installButton.setEnabled(true); connect();
} else { } else {
installButton.setEnabled(false); setInfoText(getString(R.string.not_connected));
} }
} else { } else {
installButton.setEnabled(dev.isConnected()); validateInstallation();
} }
} else if (dev.getType() == DeviceType.MIBAND && mFwReader != null) {
installButton.setEnabled(dev.isInitialized());
} }
} }
} }
}; };
private void connect() {
Intent startIntent = new Intent(FwAppInstallerActivity.this, DeviceCommunicationService.class);
startIntent.setAction(DeviceCommunicationService.ACTION_CONNECT);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
startService(startIntent);
}
private void validateInstallation() {
if (installHandler != null) {
installHandler.validateInstallation(this, device);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appinstaller); setContentView(R.layout.activity_appinstaller);
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
mayConnect = true;
fwAppInstallTextView = (TextView) findViewById(R.id.debugTextView); fwAppInstallTextView = (TextView) findViewById(R.id.debugTextView);
installButton = (Button) findViewById(R.id.installButton); installButton = (Button) findViewById(R.id.installButton);
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
@ -80,49 +89,45 @@ public class FwAppInstallerActivity extends Activity {
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED); filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter); LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
final Uri uri = getIntent().getData();
mPBWReader = new PBWReader(uri, getApplicationContext());
if (mPBWReader.isValid()) {
GBDeviceApp app = mPBWReader.getGBDeviceApp();
if (mPBWReader.isFirmware()) {
fwAppInstallTextView.setText(getString(R.string.firmware_install_warning, mPBWReader.getHWRevision()));
} else if (app != null) {
fwAppInstallTextView.setText(getString(R.string.app_install_info, app.getName(), app.getVersion(), app.getCreator()));
}
} else {
mPBWReader = null;
try {
mFwReader = new MiBandFWHelper(uri, getApplicationContext());
fwAppInstallTextView.setText(getString(R.string.fw_upgrade_notice, mFwReader.getHumanFirmwareVersion()));
if (mFwReader.isFirmwareWhitelisted()) {
fwAppInstallTextView.append(" " + getString(R.string.miband_firmware_known));
} else {
fwAppInstallTextView.append(" " + getString(R.string.miband_firmware_unknown_warning) + " " +
getString(R.string.miband_firmware_suggest_whitelist, mFwReader.getFirmwareVersion()));
}
} catch (IOException ex) {
GB.toast(getApplicationContext(), "Firmware cannot be installed: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
}
}
installButton.setOnClickListener(new View.OnClickListener() { installButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent startIntent = new Intent(FwAppInstallerActivity.this, DeviceCommunicationService.class); Intent startIntent = new Intent(FwAppInstallerActivity.this, DeviceCommunicationService.class);
startIntent.setAction(DeviceCommunicationService.ACTION_INSTALL); startIntent.setAction(DeviceCommunicationService.ACTION_INSTALL);
startIntent.putExtra("uri", uri.toString()); startIntent.putExtra("uri", uri);
startService(startIntent); startService(startIntent);
} }
}); });
uri = getIntent().getData();
installHandler = findInstallHandlerFor(uri);
if (installHandler == null) {
setInfoText(getString(R.string.installer_activity_unable_to_find_handler));
setInstallEnabled(false);
} else {
setInfoText(getString(R.string.installer_activity_wait_while_determining_status));
setInstallEnabled(false);
// needed to get the device
Intent connectIntent = new Intent(this, DeviceCommunicationService.class);
connectIntent.setAction(DeviceCommunicationService.ACTION_CONNECT);
startService(connectIntent);
Intent versionInfoIntent = new Intent(this, DeviceCommunicationService.class); Intent versionInfoIntent = new Intent(this, DeviceCommunicationService.class);
versionInfoIntent.setAction(DeviceCommunicationService.ACTION_REQUEST_VERSIONINFO); versionInfoIntent.setAction(DeviceCommunicationService.ACTION_REQUEST_VERSIONINFO);
startService(versionInfoIntent); startService(versionInfoIntent);
} }
}
private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this);
if (handler != null) {
return handler;
}
}
return null;
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
@ -139,4 +144,14 @@ public class FwAppInstallerActivity extends Activity {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
super.onDestroy(); super.onDestroy();
} }
@Override
public void setInfoText(String text) {
fwAppInstallTextView.setText(text);
}
@Override
public void setInstallEnabled(boolean enable) {
installButton.setEnabled(device != null && device.isConnected() && enable);
}
} }

View File

@ -0,0 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
public interface InstallActivity {
public void setInfoText(String text);
public void setInstallEnabled(boolean enable);
}

View File

@ -1,10 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.devices; package nodomain.freeyourgadget.gadgetbridge.devices;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public interface DeviceCoordinator { public interface DeviceCoordinator {
String EXTRA_DEVICE_MAC_ADDRESS = "nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate.EXTRA_MAC_ADDRESS"; String EXTRA_DEVICE_MAC_ADDRESS = "nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate.EXTRA_MAC_ADDRESS";
@ -20,4 +22,6 @@ public interface DeviceCoordinator {
Class<? extends Activity> getPrimaryActivity(); Class<? extends Activity> getPrimaryActivity();
SampleProvider getSampleProvider(); SampleProvider getSampleProvider();
InstallHandler findInstallHandler(Uri uri, Context context);
} }

View File

@ -0,0 +1,29 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
import android.net.Uri;
import android.support.annotation.Nullable;
import nodomain.freeyourgadget.gadgetbridge.activities.FwAppInstallerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
/**
* Interface for the UI side of certain kinds of installation of things on the
* gadget device. The actual element to install will be passed in the constructor.
*/
public interface InstallHandler {
/**
* Returns true if this handler is able to install the element.
*/
public boolean isValid();
/**
* Checks whether the installation of the 'element' on the device is possible
* and configures the InstallActivity accordingly (sets helpful texts,
* enables/disables the "Install" button, etc.
* @param installActivity the activity to interact with
* @param device the device to which the element shall be installed
*/
void validateInstallation(InstallActivity installActivity, GBDevice device);
}

View File

@ -1,6 +1,8 @@
package nodomain.freeyourgadget.gadgetbridge.devices; package nodomain.freeyourgadget.gadgetbridge.devices;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
@ -66,4 +68,9 @@ public class UnknownDeviceCoordinator implements DeviceCoordinator {
public SampleProvider getSampleProvider() { public SampleProvider getSampleProvider() {
return sampleProvider; return sampleProvider;
} }
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
} }

View File

@ -1,15 +1,19 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband; package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Calendar; import java.util.Calendar;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -54,6 +58,12 @@ public class MiBandCoordinator implements DeviceCoordinator {
return sampleProvider; return sampleProvider;
} }
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
MiBandFWInstallHandler handler = new MiBandFWInstallHandler(uri, context);
return handler.isValid() ? handler : null;
}
public static boolean hasValidUserInfo() { public static boolean hasValidUserInfo() {
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER + ":00:00:00"; String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER + ":00:00:00";
try { try {

View File

@ -12,6 +12,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Locale; import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class MiBandFWHelper { public class MiBandFWHelper {

View File

@ -0,0 +1,58 @@
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
import android.content.Context;
import android.net.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class MiBandFWInstallHandler implements InstallHandler {
private static final Logger LOG = LoggerFactory.getLogger(MiBandFWInstallHandler.class);
private final Context mContext;
private MiBandFWHelper helper;
private String errorMessage;
public MiBandFWInstallHandler(Uri uri, Context context) {
mContext = context;
try {
helper = new MiBandFWHelper(uri, mContext);
} catch (IOException e) {
errorMessage = e.getMessage();
LOG.warn(errorMessage, e);
}
}
@Override
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
if (device.isBusy() || device.getType() != DeviceType.MIBAND || !device.isInitialized()) {
installActivity.setInfoText("Element cannot be installed");
installActivity.setInstallEnabled(false);
return;
}
StringBuilder builder = new StringBuilder(mContext.getString(R.string.fw_upgrade_notice, helper.getHumanFirmwareVersion()));
if (helper.isFirmwareWhitelisted()) {
builder.append(" ").append(mContext.getString(R.string.miband_firmware_known));
} else {
builder.append(" ").append(mContext.getString(R.string.miband_firmware_unknown_warning)).append(" ")
.append(mContext.getString(R.string.miband_firmware_suggest_whitelist, helper.getFirmwareVersion()));
}
installActivity.setInfoText(builder.toString());
installActivity.setInstallEnabled(true);
}
public boolean isValid() {
return helper != null;
}
}

View File

@ -0,0 +1,57 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.content.Context;
import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.InstallActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
public class PBWInstallHandler implements InstallHandler {
private final Context mContext;
private final PBWReader mPBWReader;
private final Uri mUri;
public PBWInstallHandler(Uri uri, Context context) {
mContext = context;
mPBWReader = new PBWReader(uri, context);
mUri = uri;
}
@Override
public void validateInstallation(InstallActivity installActivity, GBDevice device) {
if (device.isBusy() || device.getType() != DeviceType.PEBBLE || !device.isConnected()) {
installActivity.setInfoText("Element cannot be installed");
installActivity.setInstallEnabled(false);
return;
}
if (mPBWReader.isFirmware()) {
String hwRevision = mPBWReader.getHWRevision();
if (hwRevision != null && hwRevision.equals(device.getHardwareVersion())) {
installActivity.setInfoText(mContext.getString(R.string.firmware_install_warning, hwRevision));
installActivity.setInstallEnabled(true);
} else {
installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_hw_revision_mismatch));
installActivity.setInstallEnabled(false);
}
} else {
GBDeviceApp app = mPBWReader.getGBDeviceApp();
if (app != null) {
installActivity.setInfoText(mContext.getString(R.string.app_install_info, app.getName(), app.getVersion(), app.getCreator()));
installActivity.setInstallEnabled(true);
} else {
installActivity.setInfoText(mContext.getString(R.string.pbw_install_handler_unable_to_install, mUri.getPath()));
installActivity.setInstallEnabled(false);
}
}
}
public boolean isValid() {
return mPBWReader.isValid();
}
}

View File

@ -173,7 +173,6 @@ public class PBWReader {
e.printStackTrace(); e.printStackTrace();
return null; return null;
} }
ZipInputStream zis = new ZipInputStream(fin); ZipInputStream zis = new ZipInputStream(fin);
ZipEntry ze; ZipEntry ze;
try { try {
@ -183,7 +182,12 @@ public class PBWReader {
} }
} }
zis.close(); zis.close();
} catch (IOException e) { } catch (Throwable e) {
try {
zis.close();
} catch (IOException e1) {
// ignore
}
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;

View File

@ -1,9 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.devices.pebble; package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AppManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -45,4 +48,10 @@ public class PebbleCoordinator implements DeviceCoordinator {
public SampleProvider getSampleProvider() { public SampleProvider getSampleProvider() {
return sampleProvider; return sampleProvider;
} }
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
PBWInstallHandler installHandler = new PBWInstallHandler(uri, context);
return installHandler.isValid() ? installHandler : null;
}
} }

View File

@ -143,7 +143,11 @@ public class DeviceCommunicationService extends Service {
case ACTION_CONNECT: case ACTION_CONNECT:
String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS); String btDeviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
if (btDeviceAddress == null) {
btDeviceAddress = sharedPrefs.getString("last_device_address", null);
} else {
sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply(); sharedPrefs.edit().putString("last_device_address", btDeviceAddress).apply();
}
if (btDeviceAddress != null && !isConnected() && !isConnecting()) { if (btDeviceAddress != null && !isConnected() && !isConnecting()) {
if (mDeviceSupport != null) { if (mDeviceSupport != null) {
@ -247,10 +251,10 @@ public class DeviceCommunicationService extends Service {
mDeviceSupport.onAppDelete(uuid); mDeviceSupport.onAppDelete(uuid);
break; break;
case ACTION_INSTALL: case ACTION_INSTALL:
String uriString = intent.getStringExtra("uri"); Uri uri = intent.getParcelableExtra("uri");
if (uriString != null) { if (uri != null) {
LOG.info("will try to install app/fw"); LOG.info("will try to install app/fw");
mDeviceSupport.onInstallApp(Uri.parse(uriString)); mDeviceSupport.onInstallApp(uri);
} }
break; break;
case ACTION_START: case ACTION_START:

View File

@ -77,5 +77,4 @@ public class DeviceHelper {
result.add(new PebbleCoordinator()); result.add(new PebbleCoordinator());
return result; return result;
} }
} }

View File

@ -80,7 +80,7 @@
<string name="installing_binary_d_d">installing binary %1$d/%2$d</string> <string name="installing_binary_d_d">installing binary %1$d/%2$d</string>
<string name="installation_failed_">installation failed!</string> <string name="installation_failed_">installation failed!</string>
<string name="installation_successful">installation successful</string> <string name="installation_successful">installation successful</string>
<string name="firmware_install_warning">YOUR ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK.\n\n\n This firmware is for HW Revision: %s</string> <string name="firmware_install_warning">YOU ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK.\n\n\n This firmware is for HW Revision: %s</string>
<string name="app_install_info">You are about to install the following app:\n\n\n%1$s Version %2$s by %3$s\n</string> <string name="app_install_info">You are about to install the following app:\n\n\n%1$s Version %2$s by %3$s\n</string>
<string name="n_a">N/A</string> <string name="n_a">N/A</string>
<string name="initialized">initialized</string> <string name="initialized">initialized</string>
@ -161,4 +161,8 @@
<string name="dbaccess_error_executing">Error executing \'%1$s\'</string> <string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
<string name="controlcenter_start_activitymonitor">Your Activity (ALPHA)</string> <string name="controlcenter_start_activitymonitor">Your Activity (ALPHA)</string>
<string name="cannot_connect">Cannot connect: %1$s</string> <string name="cannot_connect">Cannot connect: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string>
<string name="pbw_install_handler_unable_to_install">Unable to install the given file: $1%s</string>
<string name="pbw_install_handler_hw_revision_mismatch">Unable to install the given firmware: it doesn\'t match your Pebble\'s hardware revision.</string>
<string name="installer_activity_wait_while_determining_status">Please wait while determining the installation status...</string>
</resources> </resources>