diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index 9b19e7e96d..1afb4246d9 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -15,6 +15,8 @@
*/
package nodomain.freeyourgadget.gadgetbridge.daogen;
+import java.util.Date;
+
import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Index;
@@ -32,6 +34,7 @@ public class GBDaoGenerator {
private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge";
private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model";
private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
+ private static final String ACTIVITY_SUMMARY = MODEL_PACKAGE + ".ActivitySummary";
private static final String OVERRIDE = "@Override";
private static final String SAMPLE_RAW_INTENSITY = "rawIntensity";
private static final String SAMPLE_STEPS = "steps";
@@ -42,7 +45,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
- Schema schema = new Schema(17, MAIN_PACKAGE + ".entities");
+ Schema schema = new Schema(18, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -69,6 +72,8 @@ public static void main(String[] args) throws Exception {
addCalendarSyncState(schema, device);
+ addBipActivitySummary(schema, user, device);
+
new DaoGenerator().generateAll(schema, "app/src/main/java");
}
@@ -309,6 +314,31 @@ private static void addCalendarSyncState(Schema schema, Entity device) {
calendarSyncState.addIntProperty("hash").notNull();
}
+ private static void addBipActivitySummary(Schema schema, Entity user, Entity device) {
+ Entity summary = addEntity(schema, "BaseActivitySummary");
+ summary.implementsInterface(ACTIVITY_SUMMARY);
+ summary.addIdProperty();
+
+ summary.setJavaDoc(
+ "This class represents the summary of a user's activity event. I.e. a walk, hike, a bicycle tour, etc.");
+
+ summary.addStringProperty("name").codeBeforeGetter(OVERRIDE);
+ summary.addDateProperty("startTime").notNull().codeBeforeGetter(OVERRIDE);
+ summary.addDateProperty("endTime").notNull().codeBeforeGetter(OVERRIDE);
+ summary.addIntProperty("activityKind").notNull().codeBeforeGetter(OVERRIDE);
+
+ summary.addIntProperty("baseLongitude").javaDocGetterAndSetter("Temporary, bip-specific");
+ summary.addIntProperty("baseLatitude").javaDocGetterAndSetter("Temporary, bip-specific");
+ summary.addIntProperty("baseAltitude").javaDocGetterAndSetter("Temporary, bip-specific");
+
+ summary.addStringProperty("gpxTrack").codeBeforeGetter(OVERRIDE);
+
+ Property deviceId = summary.addLongProperty("deviceId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
+ summary.addToOne(device, deviceId);
+ Property userId = summary.addLongProperty("userId").notNull().codeBeforeGetter(OVERRIDE).getProperty();
+ summary.addToOne(user, userId);
+ }
+
private static Property findProperty(Entity entity, String propertyName) {
for (Property prop : entity.getProperties()) {
if (propertyName.equals(prop.getPropertyName())) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ac716aaf84..c1bad1947a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -59,6 +59,10 @@
android:name=".devices.miband.MiBandPreferencesActivity"
android:label="@string/preferences_miband_settings"
android:parentActivityName=".activities.SettingsActivity" />
+
+ android:resource="@xml/shared_paths" />
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
index 0a9742ecf4..7370690c12 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java
@@ -25,6 +25,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
@@ -607,4 +610,24 @@ public static GBApplication app() {
public static Locale getLanguage() {
return language;
}
+
+ public String getVersion() {
+ try {
+ return getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ GB.log("Unable to determine Gadgetbridge's version", GB.WARN, e);
+ return "0.0.0";
+ }
+ }
+
+ public String getNameAndVersion() {
+ try {
+ ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA);
+ return String.format("%s %s", appInfo.name, packageInfo.versionName);
+ } catch (PackageManager.NameNotFoundException e) {
+ GB.log("Unable to determine Gadgetbridge's name/version", GB.WARN, e);
+ return "Gadgetbridge";
+ }
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
index 302d7babe6..725533574e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/Logging.java
@@ -148,7 +148,7 @@ public static String formatBytes(byte[] bytes) {
}
StringBuilder builder = new StringBuilder(bytes.length * 5);
for (byte b : bytes) {
- builder.append(String.format("0x%2x", b));
+ builder.append(String.format("0x%02x", b));
builder.append(" ");
}
return builder.toString().trim();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java
new file mode 100644
index 0000000000..bbdd11e2a2
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractListActivity.java
@@ -0,0 +1,51 @@
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.os.Bundle;
+import android.support.v7.widget.Toolbar;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.adapter.AbstractItemAdapter;
+import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public abstract class AbstractListActivity extends AbstractGBActivity {
+ private AbstractItemAdapter itemAdapter;
+ private ListView itemListView;
+
+ public void setItemAdapter(AbstractItemAdapter itemAdapter) {
+ this.itemAdapter = itemAdapter;
+ itemListView.setAdapter(itemAdapter);
+ }
+
+ protected void refresh() {
+ this.itemAdapter.loadItems();
+ }
+
+ public AbstractItemAdapter getItemAdapter() {
+ return itemAdapter;
+ }
+
+ public ListView getItemListView() {
+ return itemListView;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_list);
+ itemListView = (ListView) findViewById(R.id.itemListView);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java
new file mode 100644
index 0000000000..209544e835
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ActivitySummariesActivity.java
@@ -0,0 +1,82 @@
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Toast;
+
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.adapter.ActivitySummariesAdapter;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ActivitySummariesActivity extends AbstractListActivity {
+
+ private int selectedIndex;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setItemAdapter(new ActivitySummariesAdapter(this));
+
+ getItemListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ Object item = parent.getItemAtPosition(position);
+ if (item != null) {
+ ActivitySummary summary = (ActivitySummary) item;
+ String gpxTrack = summary.getGpxTrack();
+ if (gpxTrack != null) {
+ showTrack(gpxTrack);
+ }
+ }
+ }
+ });
+
+
+ getItemListView().setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, final ContextMenu.ContextMenuInfo menuInfo) {
+ MenuItem delete = menu.add("Delete");
+ delete.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ deleteItemAt(selectedIndex);
+ return true;
+ }
+ });
+ }
+ });
+
+ getItemListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView> parent, View view, int position, long id) {
+ selectedIndex = position;
+ return getItemListView().showContextMenu();
+ }
+ });
+ }
+
+ private void deleteItemAt(int position) {
+ BaseActivitySummary item = getItemAdapter().getItem(position);
+ if (item != null) {
+ item.delete();
+ getItemAdapter().remove(item);
+ refresh();
+ }
+ }
+
+ private void showTrack(String gpxTrack) {
+ try {
+ AndroidUtils.viewFile(gpxTrack, Intent.ACTION_VIEW, this);
+ } catch (IOException e) {
+ GB.toast(this, "Unable to display GPX track: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR, e);
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java
index 1b2730f60c..a2f3b0734c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/HeartRateUtils.java
@@ -27,4 +27,8 @@ public class HeartRateUtils {
* Value is in minutes
*/
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
+
+ public static boolean isValidHeartRateValue(int value) {
+ return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
index b61513847b..e9fa8f0207 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
@@ -70,6 +70,8 @@
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import static nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils.isValidHeartRateValue;
+
/**
* A base class fragment to be used with ChartsActivity. The fragment can supply
* a title to be displayed in the activity by returning non-null in #getTitle()
@@ -439,6 +441,7 @@ protected DefaultChartsData refresh(GBDevice gbDevice, List extends
List notWornEntries = new ArrayList<>(numEntries);
boolean hr = supportsHeartrate(gbDevice);
List heartrateEntries = hr ? new ArrayList(numEntries) : null;
+ List colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1;
for (int i = 0; i < numEntries; i++) {
@@ -509,7 +512,7 @@ protected DefaultChartsData refresh(GBDevice gbDevice, List extends
}
activityEntries.add(createLineEntry(value, ts));
}
- if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && isValidHeartRateValue(sample.getHeartRate())) {
+ if (hr && sample.getKind() != ActivityKind.TYPE_NOT_WORN && HeartRateUtils.isValidHeartRateValue(sample.getHeartRate())) {
if (lastHrSampleIndex > -1 && ts - lastHrSampleIndex > 1800*HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
heartrateEntries.add(createLineEntry(0, ts - 1));
@@ -574,10 +577,6 @@ protected DefaultChartsData refresh(GBDevice gbDevice, List extends
return new DefaultChartsData(lineData, xValueFormatter);
}
- protected boolean isValidHeartRateValue(int value) {
- return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
- }
-
/**
* Implement this to supply the samples to be displayed.
*
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java
index 67b86c5fb3..53c7563fd1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/LiveActivityFragment.java
@@ -168,7 +168,7 @@ public void onReceive(Context context, Intent intent) {
private void addSample(ActivitySample sample) {
int heartRate = sample.getHeartRate();
int timestamp = tsTranslation.shorten(sample.getTimestamp());
- if (isValidHeartRateValue(heartRate)) {
+ if (HeartRateUtils.isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
int steps = sample.getSteps();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java
new file mode 100644
index 0000000000..9ad6e57a18
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/AbstractItemAdapter.java
@@ -0,0 +1,122 @@
+/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+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.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+/**
+ * Adapter for displaying generic ItemWithDetails instances.
+ */
+public abstract class AbstractItemAdapter extends ArrayAdapter {
+
+ public static final int SIZE_SMALL = 1;
+ public static final int SIZE_MEDIUM = 2;
+ public static final int SIZE_LARGE = 3;
+ private final Context context;
+ private final List items;
+ private boolean horizontalAlignment;
+ private int size = SIZE_MEDIUM;
+
+ public AbstractItemAdapter(Context context) {
+ this (context, new ArrayList());
+ }
+
+ public AbstractItemAdapter(Context context, List items) {
+ super(context, 0, items);
+
+ this.context = context;
+ this.items = items;
+ }
+
+ public void setHorizontalAlignment(boolean horizontalAlignment) {
+ this.horizontalAlignment = horizontalAlignment;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ T item = getItem(position);
+
+ if (view == null) {
+ LayoutInflater inflater = (LayoutInflater) context
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ if (horizontalAlignment) {
+ view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
+ } else {
+ switch (size) {
+ case SIZE_SMALL:
+ view = inflater.inflate(R.layout.item_with_details_small, parent, false);
+ break;
+ default:
+ view = inflater.inflate(R.layout.item_with_details, parent, false);
+ break;
+ }
+ }
+ }
+ ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
+ TextView nameView = (TextView) view.findViewById(R.id.item_name);
+ TextView detailsView = (TextView) view.findViewById(R.id.item_details);
+
+ nameView.setText(getName(item));
+ detailsView.setText(getDetails(item));
+ iconView.setImageResource(getIcon(item));
+
+ return view;
+ }
+
+ protected abstract String getName(T item);
+
+ protected abstract String getDetails(T item);
+
+ @DrawableRes
+ protected abstract int getIcon(T item);
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void loadItems() {
+ }
+
+ public void setItems(List items, boolean notify) {
+ this.items.clear();
+ this.items.addAll(items);
+ if (notify) {
+ notifyDataSetChanged();
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java
new file mode 100644
index 0000000000..c90ff1831a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ActivitySummariesAdapter.java
@@ -0,0 +1,56 @@
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummaryDao;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ActivitySummariesAdapter extends AbstractItemAdapter {
+ public ActivitySummariesAdapter(Context context) {
+ super(context);
+ loadItems();
+ }
+
+ public void loadItems() {
+ try (DBHandler handler = GBApplication.acquireDB()) {
+ BaseActivitySummaryDao summaryDao = handler.getDaoSession().getBaseActivitySummaryDao();
+ List allSummaries = summaryDao.loadAll();
+ setItems(allSummaries, true);
+ } catch (Exception e) {
+ GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
+ }
+ }
+
+ @Override
+ protected String getName(BaseActivitySummary item) {
+ String name = item.getName();
+ if (name != null && name.length() > 0) {
+ return name;
+ }
+
+ Date startTime = item.getStartTime();
+ if (startTime != null) {
+ return DateTimeUtils.formatDateTime(startTime);
+ }
+ return "Unknown activity";
+ }
+
+ @Override
+ protected String getDetails(BaseActivitySummary item) {
+ return ActivityKind.asString(item.getActivityKind(), getContext());
+ }
+
+ @Override
+ protected int getIcon(BaseActivitySummary item) {
+ return ActivityKind.getIconId(item.getActivityKind());
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
index 105a1316bd..84d7ea6f70 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
@@ -43,6 +43,7 @@
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
@@ -210,6 +211,20 @@ public void onClick(View v) {
}
);
+ //show activity tracks
+ holder.showActivityTracks.setVisibility(coordinator.supportsActivityTracks() ? View.VISIBLE : View.GONE);
+ holder.showActivityTracks.setOnClickListener(new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View v) {
+ Intent startIntent;
+ startIntent = new Intent(context, ActivitySummariesActivity.class);
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+ context.startActivity(startIntent);
+ }
+ }
+ );
+
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true);
holder.deviceInfoList.setAdapter(infoAdapter);
@@ -338,6 +353,7 @@ static class ViewHolder extends RecyclerView.ViewHolder {
ImageView manageAppsView;
ImageView setAlarmsView;
ImageView showActivityGraphs;
+ ImageView showActivityTracks;
ImageView deviceInfoView;
//overflow
@@ -365,6 +381,7 @@ static class ViewHolder extends RecyclerView.ViewHolder {
manageAppsView = (ImageView) view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = (ImageView) view.findViewById(R.id.device_action_set_alarms);
showActivityGraphs = (ImageView) view.findViewById(R.id.device_action_show_activity_graphs);
+ showActivityTracks = (ImageView) view.findViewById(R.id.device_action_show_activity_tracks);
deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image);
deviceInfoBox = (RelativeLayout) view.findViewById(R.id.device_item_infos_box);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
index 962a894c58..f038655d20 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
@@ -17,77 +17,33 @@
package nodomain.freeyourgadget.gadgetbridge.adapter;
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 nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
/**
* Adapter for displaying generic ItemWithDetails instances.
*/
-public class ItemWithDetailsAdapter extends ArrayAdapter {
-
- public static final int SIZE_SMALL = 1;
- public static final int SIZE_MEDIUM = 2;
- public static final int SIZE_LARGE = 3;
- private final Context context;
- private boolean horizontalAlignment;
- private int size = SIZE_MEDIUM;
+public class ItemWithDetailsAdapter extends AbstractItemAdapter {
public ItemWithDetailsAdapter(Context context, List items) {
- super(context, 0, items);
-
- this.context = context;
+ super(context, items);
}
- public void setHorizontalAlignment(boolean horizontalAlignment) {
- this.horizontalAlignment = horizontalAlignment;
+ @Override
+ protected String getName(ItemWithDetails item) {
+ return item.getName();
}
@Override
- public View getView(int position, View view, ViewGroup parent) {
- ItemWithDetails item = getItem(position);
-
- if (view == null) {
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- if (horizontalAlignment) {
- view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
- } else {
- switch (size) {
- case SIZE_SMALL:
- view = inflater.inflate(R.layout.item_with_details_small, parent, false);
- break;
- default:
- view = inflater.inflate(R.layout.item_with_details, parent, false);
- break;
- }
- }
- }
- ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
- TextView nameView = (TextView) view.findViewById(R.id.item_name);
- TextView detailsView = (TextView) view.findViewById(R.id.item_details);
-
- nameView.setText(item.getName());
- detailsView.setText(item.getDetails());
- iconView.setImageResource(item.getIcon());
-
- return view;
+ protected String getDetails(ItemWithDetails item) {
+ return item.getDetails();
}
- public void setSize(int size) {
- this.size = size;
+ @Override
+ protected int getIcon(ItemWithDetails item) {
+ return item.getIcon();
}
- public int getSize() {
- return size;
- }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/UsedConfiguration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/UsedConfiguration.java
deleted file mode 100644
index 1f9c13d999..0000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/UsedConfiguration.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/* Copyright (C) 2015-2018 Carsten Pfeiffer
-
- This file is part of Gadgetbridge.
-
- Gadgetbridge is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- Gadgetbridge is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see . */
-package nodomain.freeyourgadget.gadgetbridge.database;
-
-/**
- * Contains the configuration used for particular activity samples.
- */
-public class UsedConfiguration {
- String fwVersion;
- String userName;
- short userWeight;
- short userSize;
- // ...
- int usedFrom; // timestamp
- int usedUntil; // timestamp
- short sleepGoal; // minutes
- short stepsGoal;
-}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index 194d642e45..6acbe6e4cf 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -124,4 +124,9 @@ public boolean isHealthWearable(BluetoothDevice device) {
public int getBondingStyle(GBDevice device) {
return BONDING_STYLE_ASK;
}
+
+ @Override
+ public boolean supportsActivityTracks() {
+ return false;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index 54d99a334f..45d4150ebd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -139,6 +139,14 @@ public interface DeviceCoordinator {
*/
boolean supportsActivityTracking();
+ /**
+ * Indicates whether the device supports recording dedicated activity tracks, like
+ * walking, hiking, running, swimming, etc. and retrieving the recorded
+ * data. This is different from the constant activity tracking since the tracks are
+ * usually recorded with additional features, like e.g. GPS.
+ */
+ boolean supportsActivityTracks();
+
/**
* Returns true if activity data fetching is supported AND possible at this
* very moment. This will consider the device state (being connected/disconnected/busy...)
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java
new file mode 100644
index 0000000000..baf58485a1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/amazfitbip/BipActivitySummary.java
@@ -0,0 +1,206 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip;
+
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class BipActivitySummary extends BaseActivitySummary {
+ private int version;
+ private float distanceMeters;
+ private float ascentMeters;
+ private float descentMeters;
+ private float minAltitude;
+ private float maxAltitude;
+ private int minLatitude;
+ private int maxLatitude;
+ private int minLongitude;
+ private int maxLongitude;
+ private long steps;
+ private long activeTimeSeconds;
+ private float caloriesBurnt;
+ private float maxSpeed;
+ private float minPace;
+ private float maxPace;
+ private float totalStride;
+ private long timeAscent;
+ private long timeDescent;
+ private long timeFlat;
+ private int averageHR;
+ private int averagePace;
+ private int averageStride;
+
+// @Override
+// public long getSteps() {
+// return steps;
+// }
+//
+// @Override
+// public float getDistanceMeters() {
+// return distanceMeters;
+// }
+//
+// @Override
+// public float getAscentMeters() {
+// return ascentMeters;
+// }
+//
+// @Override
+// public float getDescentMeters() {
+// return descentMeters;
+// }
+//
+// @Override
+// public float getMinAltitude() {
+// return minAltitude;
+// }
+//
+// @Override
+// public float getMaxAltitude() {
+// return maxAltitude;
+// }
+//
+// @Override
+// public float getCalories() {
+// return caloriesBurnt;
+// }
+//
+// @Override
+// public float getMaxSpeed() {
+// return maxSpeed;
+// }
+//
+// @Override
+// public float getMinSpeed() {
+// return minPace;
+// }
+//
+// @Override
+// public float getAverageSpeed() {
+// return averagePace;
+// }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public void setDistanceMeters(float distanceMeters) {
+ this.distanceMeters = distanceMeters;
+ }
+
+ public void setAscentMeters(float ascentMeters) {
+ this.ascentMeters = ascentMeters;
+ }
+
+ public void setDescentMeters(float descentMeters) {
+ this.descentMeters = descentMeters;
+ }
+
+ public void setMinAltitude(float minAltitude) {
+ this.minAltitude = minAltitude;
+ }
+
+ public void setMaxAltitude(float maxAltitude) {
+ this.maxAltitude = maxAltitude;
+ }
+
+ public void setMinLatitude(int minLatitude) {
+ this.minLatitude = minLatitude;
+ }
+
+ public void setMaxLatitude(int maxLatitude) {
+ this.maxLatitude = maxLatitude;
+ }
+
+ public void setMinLongitude(int minLongitude) {
+ this.minLongitude = minLongitude;
+ }
+
+ public void setMaxLongitude(int maxLongitude) {
+ this.maxLongitude = maxLongitude;
+ }
+
+ public void setSteps(long steps) {
+ this.steps = steps;
+ }
+
+ public void setActiveTimeSeconds(long activeTimeSeconds) {
+ this.activeTimeSeconds = activeTimeSeconds;
+ }
+
+ public void setCaloriesBurnt(float caloriesBurnt) {
+ this.caloriesBurnt = caloriesBurnt;
+ }
+
+ public void setMaxSpeed(float maxSpeed) {
+ this.maxSpeed = maxSpeed;
+ }
+
+ public void setMinPace(float minPace) {
+ this.minPace = minPace;
+ }
+
+ public void setMaxPace(float maxPace) {
+ this.maxPace = maxPace;
+ }
+
+ public void setTotalStride(float totalStride) {
+ this.totalStride = totalStride;
+ }
+
+ public float getTotalStride() {
+ return totalStride;
+ }
+
+ public void setTimeAscent(long timeAscent) {
+ this.timeAscent = timeAscent;
+ }
+
+ public long getTimeAscent() {
+ return timeAscent;
+ }
+
+ public void setTimeDescent(long timeDescent) {
+ this.timeDescent = timeDescent;
+ }
+
+ public long getTimeDescent() {
+ return timeDescent;
+ }
+
+ public void setTimeFlat(long timeFlat) {
+ this.timeFlat = timeFlat;
+ }
+
+ public long getTimeFlat() {
+ return timeFlat;
+ }
+
+ public void setAverageHR(int averageHR) {
+ this.averageHR = averageHR;
+ }
+
+ public int getAverageHR() {
+ return averageHR;
+ }
+
+ public void setAveragePace(int averagePace) {
+ this.averagePace = averagePace;
+ }
+
+ public int getAveragePace() {
+ return averagePace;
+ }
+
+ public void setAverageStride(int averageStride) {
+ this.averageStride = averageStride;
+ }
+
+ public int getAverageStride() {
+ return averageStride;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java
index 0ea400c075..25a1f1db03 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipCoordinator.java
@@ -64,6 +64,11 @@ public boolean supportsHeartRateMeasurement(GBDevice device) {
return true;
}
+ @Override
+ public boolean supportsActivityTracks() {
+ return true;
+ }
+
@Override
public boolean supportsWeather() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java
index 0c439bf04d..38b81f3f84 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/amazfitbip/AmazfitBipService.java
@@ -36,5 +36,8 @@ public class AmazfitBipService {
public static final byte[] COMMAND_SET_LANGUAGE_NEW_TEMPLATE = new byte[]{ENDPOINT_DISPLAY, 0x17, 0x00, 0, 0, 0, 0, 0};
+ public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES = 0x05;
+ public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS = 0x06;
+
public static final byte[] COMMAND_ACK_FIND_PHONE_IN_PROGRESS = new byte[]{ENDPOINT_DISPLAY, 0x14, 0x00, 0x00};
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
index 1b12910fc1..0dac10166b 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2SampleProvider.java
@@ -20,38 +20,15 @@
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2Const;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2Const.*;
public class MiBand2SampleProvider extends AbstractMiBandSampleProvider {
-
- // observed the following values so far:
- // 00 01 02 09 0a 0b 0c 10 11
-
- // 0 = same activity kind as before
- // 1 = light activity walking?
- // 3 = definitely non-wear
- // 9 = probably light sleep, definitely some kind of sleep
- // 10 = ignore, except for hr (if valid)
- // 11 = probably deep sleep
- // 12 = definitely wake up
- // 17 = definitely not sleep related
-
- public static final int TYPE_UNSET = -1;
- public static final int TYPE_NO_CHANGE = 0;
- public static final int TYPE_ACTIVITY = 1;
- public static final int TYPE_RUNNING = 2;
- public static final int TYPE_NONWEAR = 3;
- public static final int TYPE_CHARGING = 6;
- public static final int TYPE_LIGHT_SLEEP = 9;
- public static final int TYPE_IGNORE = 10;
- public static final int TYPE_DEEP_SLEEP = 11;
- public static final int TYPE_WAKE_UP = 12;
-
public MiBand2SampleProvider(GBDevice device, DaoSession session) {
super(device, session);
}
@@ -111,39 +88,11 @@ private int determinePreviousValidActivityType(MiBandActivitySample sample) {
@Override
public int normalizeType(int rawType) {
- switch (rawType) {
- case TYPE_DEEP_SLEEP:
- return ActivityKind.TYPE_DEEP_SLEEP;
- case TYPE_LIGHT_SLEEP:
- return ActivityKind.TYPE_LIGHT_SLEEP;
- case TYPE_ACTIVITY:
- case TYPE_RUNNING:
- case TYPE_WAKE_UP:
- return ActivityKind.TYPE_ACTIVITY;
- case TYPE_NONWEAR:
- return ActivityKind.TYPE_NOT_WORN;
- case TYPE_CHARGING:
- return ActivityKind.TYPE_NOT_WORN; //I believe it's a safe assumption
- default:
- case TYPE_UNSET: // fall through
- return ActivityKind.TYPE_UNKNOWN;
- }
+ return MiBand2Const.toActivityKind(rawType);
}
@Override
public int toRawActivityKind(int activityKind) {
- switch (activityKind) {
- case ActivityKind.TYPE_ACTIVITY:
- return TYPE_ACTIVITY;
- case ActivityKind.TYPE_DEEP_SLEEP:
- return TYPE_DEEP_SLEEP;
- case ActivityKind.TYPE_LIGHT_SLEEP:
- return TYPE_LIGHT_SLEEP;
- case ActivityKind.TYPE_NOT_WORN:
- return TYPE_NONWEAR;
- case ActivityKind.TYPE_UNKNOWN: // fall through
- default:
- return TYPE_UNSET;
- }
+ return MiBand2Const.toRawActivityType(activityKind);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Const.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Const.java
new file mode 100644
index 0000000000..a153dda97e
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband2/MiBand2Const.java
@@ -0,0 +1,68 @@
+package nodomain.freeyourgadget.gadgetbridge.devices.miband2;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public class MiBand2Const {
+ // observed the following values so far:
+ // 00 01 02 09 0a 0b 0c 10 11
+
+ // 0 = same activity kind as before
+ // 1 = light activity walking?
+ // 3 = definitely non-wear
+ // 9 = probably light sleep, definitely some kind of sleep
+ // 10 = ignore, except for hr (if valid)
+ // 11 = probably deep sleep
+ // 12 = definitely wake up
+ // 17 = definitely not sleep related
+
+ public static final int TYPE_UNSET = -1;
+ public static final int TYPE_NO_CHANGE = 0;
+ public static final int TYPE_ACTIVITY = 1;
+ public static final int TYPE_RUNNING = 2;
+ public static final int TYPE_NONWEAR = 3;
+ public static final int TYPE_RIDE_BIKE = 4;
+ public static final int TYPE_CHARGING = 6;
+ public static final int TYPE_LIGHT_SLEEP = 9;
+ public static final int TYPE_IGNORE = 10;
+ public static final int TYPE_DEEP_SLEEP = 11;
+ public static final int TYPE_WAKE_UP = 12;
+
+ public static int toActivityKind(int rawType) {
+ switch (rawType) {
+ case TYPE_DEEP_SLEEP:
+ return ActivityKind.TYPE_DEEP_SLEEP;
+ case TYPE_LIGHT_SLEEP:
+ return ActivityKind.TYPE_LIGHT_SLEEP;
+ case TYPE_ACTIVITY:
+ case TYPE_RUNNING:
+ case TYPE_WAKE_UP:
+ return ActivityKind.TYPE_ACTIVITY;
+ case TYPE_NONWEAR:
+ return ActivityKind.TYPE_NOT_WORN;
+ case TYPE_CHARGING:
+ return ActivityKind.TYPE_NOT_WORN; //I believe it's a safe assumption
+ case TYPE_RIDE_BIKE:
+ return ActivityKind.TYPE_CYCLING;
+ default:
+ case TYPE_UNSET: // fall through
+ return ActivityKind.TYPE_UNKNOWN;
+ }
+ }
+
+ public static int toRawActivityType(int activityKind) {
+ switch (activityKind) {
+ case ActivityKind.TYPE_ACTIVITY:
+ return TYPE_ACTIVITY;
+ case ActivityKind.TYPE_DEEP_SLEEP:
+ return TYPE_DEEP_SLEEP;
+ case ActivityKind.TYPE_LIGHT_SLEEP:
+ return TYPE_LIGHT_SLEEP;
+ case ActivityKind.TYPE_NOT_WORN:
+ return TYPE_NONWEAR;
+ case ActivityKind.TYPE_UNKNOWN: // fall through
+ default:
+ return TYPE_UNSET;
+ }
+ }
+
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java
new file mode 100644
index 0000000000..914fcc666a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/ActivityTrackExporter.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.export;
+
+import android.support.annotation.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+
+public interface ActivityTrackExporter {
+ @NonNull
+ String getDefaultFileName(@NonNull ActivityTrack track);
+
+ void performExport(ActivityTrack track, File targetFile) throws IOException;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java
new file mode 100644
index 0000000000..5ae0ba7468
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/export/GPXExporter.java
@@ -0,0 +1,158 @@
+package nodomain.freeyourgadget.gadgetbridge.export;
+
+import android.support.annotation.NonNull;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+public class GPXExporter implements ActivityTrackExporter {
+ private static final String NS_DEFAULT = "";
+ private static final String NS_DEFAULT_URI = "http://www.topografix.com/GPX/1/1";
+ private static final String NS_DEFAULT_PREFIX = "";
+ private static final String NS_TRACKPOINT_EXTENSION = "gpxtpx";
+ private static final String NS_TRACKPOINT_EXTENSION_URI = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1";
+ private static final String NS_XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
+
+ private String creator;
+ private boolean includeHeartRate = true;
+
+ @NonNull
+ @Override
+ public String getDefaultFileName(@NonNull ActivityTrack track) {
+ return FileUtils.makeValidFileName(track.getName());
+ }
+
+ @Override
+ public void performExport(ActivityTrack track, File targetFile) throws IOException {
+ String encoding = StandardCharsets.UTF_8.name();
+ XmlSerializer ser = Xml.newSerializer();
+ try {
+ ser.setOutput(new FileOutputStream(targetFile), encoding);
+ ser.startDocument(encoding, Boolean.TRUE);
+ ser.setPrefix("xsi", NS_XSI_URI);
+ ser.setPrefix(NS_DEFAULT_PREFIX, NS_DEFAULT);
+
+ ser.startTag(NS_DEFAULT, "gpx");
+ ser.attribute(NS_DEFAULT, "version", "1.1");
+ ser.attribute(NS_DEFAULT, "creator", getCreator());
+ ser.attribute(NS_XSI_URI, "schemaLocation", NS_DEFAULT_URI + " " + "http://www.topografix.com/GPX/1/1/gpx.xsd");
+
+ exportMetadata(ser, track);
+ exportTrack(ser, track);
+
+ ser.endTag(NS_DEFAULT, "gpx");
+ ser.endDocument();
+ } finally {
+ ser.flush();
+ }
+ }
+
+ private void exportMetadata(XmlSerializer ser, ActivityTrack track) throws IOException {
+ ser.startTag(NS_DEFAULT, "metadata");
+ ser.startTag(NS_DEFAULT, "name").text(track.getName()).endTag(NS_DEFAULT, "name");
+
+ ser.startTag(NS_DEFAULT, "author");
+ ser.startTag(NS_DEFAULT, "name").text(track.getUser().getName()).endTag(NS_DEFAULT, "name");
+ ser.endTag(NS_DEFAULT, "author");
+
+ ser.startTag(NS_DEFAULT, "time").text(formatTime(new Date())).endTag(NS_DEFAULT, "time");
+
+ ser.endTag(NS_DEFAULT, "metadata");
+ }
+
+ private String formatTime(Date date) {
+ return DateTimeUtils.formatIso8601(date);
+ }
+
+ private void exportTrack(XmlSerializer ser, ActivityTrack track) throws IOException {
+ ser.startTag(NS_DEFAULT, "trk");
+ ser.startTag(NS_DEFAULT, "trkseg");
+
+ List trackPoints = track.getTrackPoints();
+ String source = getSource(track);
+ for (ActivityPoint point : trackPoints) {
+ exportTrackPoint(ser, point, source);
+ }
+
+ ser.endTag(NS_DEFAULT, "trkseg");
+ ser.endTag(NS_DEFAULT, "trk");
+ }
+
+ private String getSource(ActivityTrack track) {
+ return track.getDevice().getName();
+ }
+
+ private void exportTrackPoint(XmlSerializer ser, ActivityPoint point, String source) throws IOException {
+ GPSCoordinate location = point.getLocation();
+ if (location == null) {
+ return; // skip invalid points, that just contain hr data, for example
+ }
+ ser.startTag(NS_DEFAULT, "trkpt");
+ ser.attribute(NS_DEFAULT, "lon", formatLocation(location.getLongitude()));
+ ser.attribute(NS_DEFAULT, "lat", formatLocation(location.getLatitude()));
+ ser.startTag(NS_DEFAULT, "ele").text(formatLocation(location.getAltitude())).endTag(NS_DEFAULT, "ele");
+ ser.startTag(NS_DEFAULT, "time").text(formatTime(point.getTime())).endTag(NS_DEFAULT, "time");
+ String description = point.getDescription();
+ if (description != null) {
+ ser.startTag(NS_DEFAULT, "desc").text(description).endTag(NS_DEFAULT, "desc");
+ }
+ ser.startTag(NS_DEFAULT, "src").text(source).endTag(NS_DEFAULT, "src");
+
+ exportTrackpointExtensions(ser, point);
+
+ ser.endTag(NS_DEFAULT, "trkpt");
+ }
+
+ private void exportTrackpointExtensions(XmlSerializer ser, ActivityPoint point) throws IOException {
+ if (!includeHeartRate) {
+ return;
+ }
+
+ int hr = point.getHeartRate();
+ if (!HeartRateUtils.isValidHeartRateValue(hr)) {
+ return;
+ }
+ ser.startTag(NS_DEFAULT, "extensions");
+
+ ser.setPrefix(NS_TRACKPOINT_EXTENSION, NS_TRACKPOINT_EXTENSION_URI);
+ ser.startTag(NS_TRACKPOINT_EXTENSION_URI, "hr").text(String.valueOf(hr)).endTag(NS_TRACKPOINT_EXTENSION_URI, "hr");
+
+ ser.endTag(NS_DEFAULT, "extensions");
+ }
+
+ private String formatLocation(double value) {
+ return new BigDecimal(value).setScale(GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE, RoundingMode.HALF_UP).toPlainString();
+ }
+
+ public String getCreator() {
+ return creator; // TODO: move to some kind of BrandingInfo class
+ }
+
+ public void setCreator(String creator) {
+ this.creator = creator;
+ }
+
+ public void setIncludeHeartRate(boolean includeHeartRate) {
+ this.includeHeartRate = includeHeartRate;
+ }
+
+ public boolean isIncludeHeartRate() {
+ return includeHeartRate;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
index 6dc1eb3d77..774bde62d4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityKind.java
@@ -17,8 +17,12 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.model;
+import android.content.Context;
+import android.support.annotation.DrawableRes;
+
import java.util.Arrays;
+import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
public class ActivityKind {
@@ -28,6 +32,11 @@ public class ActivityKind {
public static final int TYPE_LIGHT_SLEEP = 2;
public static final int TYPE_DEEP_SLEEP = 4;
public static final int TYPE_NOT_WORN = 8;
+ public static final int TYPE_RUNNING = 16;
+ public static final int TYPE_WALKING = 32;
+ public static final int TYPE_SWIMMING = 64;
+ public static final int TYPE_CYCLING = 128;
+ public static final int TYPE_TREADMILL = 256;
public static final int TYPE_SLEEP = TYPE_LIGHT_SLEEP | TYPE_DEEP_SLEEP;
public static final int TYPE_ALL = TYPE_ACTIVITY | TYPE_SLEEP | TYPE_NOT_WORN;
@@ -47,7 +56,75 @@ public static int[] mapToDBActivityTypes(int types, SampleProvider provider) {
if ((types & ActivityKind.TYPE_NOT_WORN) != 0) {
result[i++] = provider.toRawActivityKind(TYPE_NOT_WORN);
}
+ if ((types & ActivityKind.TYPE_RUNNING) != 0) {
+ result[i++] = provider.toRawActivityKind(TYPE_RUNNING);
+ }
+ if ((types & ActivityKind.TYPE_WALKING) != 0) {
+ result[i++] = provider.toRawActivityKind(TYPE_WALKING);
+ }
+ if ((types & ActivityKind.TYPE_SWIMMING) != 0) {
+ result[i++] = provider.toRawActivityKind(TYPE_SWIMMING);
+ }
+ if ((types & ActivityKind.TYPE_CYCLING) != 0) {
+ result[i++] = provider.toRawActivityKind(TYPE_CYCLING);
+ }
+ if ((types & ActivityKind.TYPE_TREADMILL) != 0) {
+ result[i++] = provider.toRawActivityKind(TYPE_TREADMILL);
+ }
return Arrays.copyOf(result, i);
}
+ public static String asString(int kind, Context context) {
+ switch (kind) {
+ case TYPE_NOT_MEASURED:
+ return context.getString(R.string.activity_type_not_measured);
+ case TYPE_ACTIVITY:
+ return context.getString(R.string.activity_type_activity);
+ case TYPE_LIGHT_SLEEP:
+ return context.getString(R.string.activity_type_light_sleep);
+ case TYPE_DEEP_SLEEP:
+ return context.getString(R.string.activity_type_deep_sleep);
+ case TYPE_NOT_WORN:
+ return context.getString(R.string.activity_type_not_worn);
+ case TYPE_RUNNING:
+ return context.getString(R.string.activity_type_running);
+ case TYPE_WALKING:
+ return context.getString(R.string.activity_type_walking);
+ case TYPE_SWIMMING:
+ return context.getString(R.string.activity_type_swimming);
+ case TYPE_CYCLING:
+ return context.getString(R.string.activity_type_biking);
+ case TYPE_TREADMILL:
+ return context.getString(R.string.activity_type_treadmill);
+ case TYPE_UNKNOWN:
+ default:
+ return context.getString(R.string.activity_type_unknown);
+ }
+ }
+
+ @DrawableRes
+ public static int getIconId(int kind) {
+ switch (kind) {
+ case TYPE_NOT_MEASURED:
+ return R.drawable.ic_activity_not_measured;
+ case TYPE_LIGHT_SLEEP:
+ return R.drawable.ic_activity_light_sleep;
+ case TYPE_DEEP_SLEEP:
+ return R.drawable.ic_activity_deep_sleep;
+ case TYPE_RUNNING:
+ return R.drawable.ic_activity_running;
+ case TYPE_WALKING:
+ return R.drawable.ic_activity_walking;
+ case TYPE_CYCLING:
+ return R.drawable.ic_activity_biking;
+ case TYPE_TREADMILL:
+ return R.drawable.ic_activity_walking;
+ case TYPE_SWIMMING: // fall through
+ case TYPE_NOT_WORN: // fall through
+ case TYPE_ACTIVITY: // fall through
+ case TYPE_UNKNOWN: // fall through
+ default:
+ return R.drawable.ic_activity_unknown;
+ }
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java
new file mode 100644
index 0000000000..da0c6d2c42
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityPoint.java
@@ -0,0 +1,95 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import android.support.annotation.Nullable;
+
+import java.util.Date;
+
+// https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
+/*
+
+ 29.2
+
+
+
+ 11
+ 92
+ 0
+
+
+
+*/
+public class ActivityPoint {
+ private Date time;
+ private GPSCoordinate location;
+ private int heartRate;
+ private long speed4;
+ private long speed5;
+ private long speed6;
+
+ // e.g. to describe a pause during the activity
+ private @Nullable String description;
+
+ public ActivityPoint() {
+ }
+
+ public ActivityPoint(Date time) {
+ this.time = time;
+ }
+
+ public Date getTime() {
+ return time;
+ }
+
+ public void setTime(Date time) {
+ this.time = time;
+ }
+
+ @Nullable
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(@Nullable String description) {
+ this.description = description;
+ }
+
+ public GPSCoordinate getLocation() {
+ return location;
+ }
+
+ public void setLocation(GPSCoordinate location) {
+ this.location = location;
+ }
+
+ public int getHeartRate() {
+ return heartRate;
+ }
+
+ public void setHeartRate(int heartRate) {
+ this.heartRate = heartRate;
+ }
+
+ public long getSpeed4() {
+ return speed4;
+ }
+
+ public void setSpeed4(long speed4) {
+ this.speed4 = speed4;
+ }
+
+ public long getSpeed5() {
+ return speed5;
+ }
+
+ public void setSpeed5(long speed5) {
+ this.speed5 = speed5;
+ }
+
+ public long getSpeed6() {
+ return speed6;
+ }
+
+ public void setSpeed6(long speed6) {
+ this.speed6 = speed6;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java
new file mode 100644
index 0000000000..9bb670a1f6
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummary.java
@@ -0,0 +1,33 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * Summarized information about a temporal activity.
+ *
+ * // TODO: split into separate entities?
+ */
+public interface ActivitySummary extends Serializable {
+ String getName();
+ Date getStartTime();
+ Date getEndTime();
+
+ int getActivityKind();
+ String getGpxTrack();
+
+ long getDeviceId();
+
+ long getUserId();
+ // long getSteps();
+// float getDistanceMeters();
+// float getAscentMeters();
+// float getDescentMeters();
+// float getMinAltitude();
+// float getMaxAltitude();
+// float getCalories();
+//
+// float getMaxSpeed();
+// float getMinSpeed();
+// float getAverageSpeed();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java
new file mode 100644
index 0000000000..22507c312f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivityTrack.java
@@ -0,0 +1,62 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+
+public class ActivityTrack {
+ private Date baseTime;
+ private Device device;
+ private User user;
+ private String name;
+
+
+ public void setBaseTime(Date baseTime) {
+ this.baseTime = baseTime;
+ }
+
+ public Device getDevice() {
+ return device;
+ }
+
+ public void setDevice(Device device) {
+ this.device = device;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public void setTrackPoints(List trackPoints) {
+ this.trackPoints = trackPoints;
+ }
+
+ private List trackPoints = new ArrayList<>();
+
+ public void addTrackPoint(ActivityPoint point) {
+ trackPoints.add(point);
+ }
+
+ public List getTrackPoints() {
+ return trackPoints;
+ }
+
+ public Date getBaseTime() {
+ return baseTime;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java
new file mode 100644
index 0000000000..e8b51d76ca
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/GPSCoordinate.java
@@ -0,0 +1,65 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public final class GPSCoordinate {
+ private final double latitude;
+ private final double longitude;
+ private final double altitude;
+
+ public static final int GPS_DECIMAL_DEGREES_SCALE = 6; // precise to 111.132mm at equator: https://en.wikipedia.org/wiki/Decimal_degrees
+
+ public GPSCoordinate(double longitude, double latitude, double altitude) {
+ this.longitude = longitude;
+ this.latitude = latitude;
+ this.altitude = altitude;
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ public double getAltitude() {
+ return altitude;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ GPSCoordinate that = (GPSCoordinate) o;
+
+ if (Double.compare(that.getLatitude(), getLatitude()) != 0) return false;
+ if (Double.compare(that.getLongitude(), getLongitude()) != 0) return false;
+ return Double.compare(that.getAltitude(), getAltitude()) == 0;
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ temp = Double.doubleToLongBits(getLatitude());
+ result = (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(getLongitude());
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(getAltitude());
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ private String formatLocation(double value) {
+ return new BigDecimal(value).setScale(8, RoundingMode.HALF_UP).toPlainString();
+ }
+
+ @Override
+ public String toString() {
+ return "lon: " + formatLocation(longitude) + ", lat: " + formatLocation(latitude) + ", alt: " + formatLocation(altitude) + "m";
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
index e640952412..5dc29645ff 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
@@ -43,6 +43,7 @@
public abstract class AbstractBTLEOperation implements GattCallback, BTLEOperation {
private final T mSupport;
protected OperationStatus operationStatus = OperationStatus.INITIAL;
+ private String name;
protected AbstractBTLEOperation(T support) {
mSupport = support;
@@ -115,6 +116,22 @@ protected GBDevice getDevice() {
return mSupport.getDevice();
}
+ protected void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getName() {
+ if (name != null) {
+ return name;
+ }
+ String busyTask = getDevice().getBusyTask();
+ if (busyTask != null) {
+ return busyTask;
+ }
+ return getClass().getSimpleName();
+ }
+
protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
return mSupport.getCharacteristic(uuid);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
index 49e05c31ae..55ecb9dcc4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BLETypeConversions.java
@@ -30,6 +30,9 @@
* Provides methods to convert standard BLE units to byte sequences and vice versa.
*/
public class BLETypeConversions {
+ public static final int TZ_FLAG_NONE = 0;
+ public static final int TZ_FLAG_INCLUDE_DST_IN_TZ = 1;
+
/**
* Converts a timestamp to the byte sequence to be sent to the current time characteristic
*
@@ -164,10 +167,33 @@ public static GregorianCalendar rawBytesToCalendar(byte[] value, boolean honorDe
return createCalendar();
}
+ public static long toUnsigned(int unsignedInt) {
+ return ((long) unsignedInt) & 0xffffffffL;
+ }
+ public static int toUnsigned(short value) {
+ return value & 0xffff;
+ }
+
+ public static int toUnsigned(byte value) {
+ return value & 0xff;
+ }
+
+ public static int toUint16(byte value) {
+ return toUnsigned(value);
+ }
+
public static int toUint16(byte... bytes) {
return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8);
}
+ public static int toInt16(byte... bytes) {
+ return (short) (bytes[0] & 0xff | ((bytes[1] & 0xff) << 8));
+ }
+
+ public static int toUint32(byte... bytes) {
+ return (bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8) | ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24);
+ }
+
public static byte[] fromUint16(int value) {
return new byte[] {
(byte) (value & 0xff),
@@ -230,7 +256,20 @@ public static byte[] calendarToLocalTimeBytes(GregorianCalendar now) {
* @return sint8 value from -48..+56
*/
public static byte mapTimeZone(TimeZone timeZone) {
- int utcOffsetInHours = (timeZone.getRawOffset() / (1000 * 60 * 60));
+ return mapTimeZone(timeZone, TZ_FLAG_NONE);
+ }
+
+ /**
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.time_zone.xml
+ * @param timeZone
+ * @return sint8 value from -48..+56
+ */
+ public static byte mapTimeZone(TimeZone timeZone, int timezoneFlags) {
+ int offsetMillis = timeZone.getRawOffset();
+ if (false && timezoneFlags == TZ_FLAG_INCLUDE_DST_IN_TZ) {
+ offsetMillis += timeZone.getDSTSavings();
+ }
+ int utcOffsetInHours = (offsetMillis / (1000 * 60 * 60));
return (byte) (utcOffsetInHours * 4);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java
index 973a795fcd..779e9f5a48 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BTLEOperation.java
@@ -20,4 +20,9 @@
public interface BTLEOperation {
void perform() throws IOException;
+
+ /**
+ * Returns a human readable name of this operation, to be used e.g. in log output.
+ */
+ String getName();
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
index 80509afcc3..1f2cb4d348 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
@@ -38,6 +38,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
@@ -467,10 +468,7 @@ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descri
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
if (LOG.isDebugEnabled()) {
- String content = "";
- for (byte b : characteristic.getValue()) {
- content += String.format(" 0x%1x", b);
- }
+ String content = Logging.formatBytes(characteristic.getValue());
LOG.debug("characteristic changed: " + characteristic.getUuid() + " value: " + content);
}
if (!checkCorrectGattInstance(gatt, "characteristic changed")) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/ActivityDetailsParser.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/ActivityDetailsParser.java
new file mode 100644
index 0000000000..ae1ef5d137
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/ActivityDetailsParser.java
@@ -0,0 +1,216 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+public class ActivityDetailsParser {
+ private static final Logger LOG = LoggerFactory.getLogger(ActivityDetailsParser.class);
+
+ private static final byte TYPE_GPS = 0;
+ private static final byte TYPE_HR = 1;
+ private static final byte TYPE_UNKNOWN2 = 2;
+ private static final byte TYPE_PAUSE = 3;
+ private static final byte TYPE_SPEED4 = 4;
+ private static final byte TYPE_SPEED5 = 5;
+ private static final byte TYPE_GPS_SPEED6 = 6;
+
+ public static final BigDecimal HUAMI_TO_DECIMAL_DEGREES_DIVISOR = new BigDecimal(3000000.0);
+ private final BaseActivitySummary summary;
+ private final ActivityTrack activityTrack;
+// private final int version;
+ private final Date baseDate;
+ private long baseLongitude;
+ private long baseLatitude;
+ private int baseAltitude;
+ private ActivityPoint lastActivityPoint;
+
+ public boolean getSkipCounterByte() {
+ return skipCounterByte;
+ }
+
+ public void setSkipCounterByte(boolean skipCounterByte) {
+ this.skipCounterByte = skipCounterByte;
+ }
+
+ private boolean skipCounterByte;
+
+ public ActivityDetailsParser(BaseActivitySummary summary) {
+ this.summary = summary;
+// this.version = version;
+// this.baseDate = baseDate;
+//
+ this.baseLongitude = summary.getBaseLongitude();
+ this.baseLatitude = summary.getBaseLatitude();
+ this.baseAltitude = summary.getBaseAltitude();
+ this.baseDate = summary.getStartTime();
+
+ this.activityTrack = new ActivityTrack();
+ activityTrack.setUser(summary.getUser());
+ activityTrack.setDevice(summary.getDevice());
+ activityTrack.setName(summary.getName() + "-" + summary.getId());
+ }
+
+ public ActivityTrack parse(byte[] bytes) throws GBException {
+ int i = 0;
+ try {
+ long totalTimeOffset = 0;
+ int lastTimeOffset = 0;
+ while (i < bytes.length) {
+ if (skipCounterByte && (i % 17) == 0) {
+ i++;
+ }
+
+ byte type = bytes[i++];
+ int timeOffset = BLETypeConversions.toUnsigned(bytes[i++]);
+ // handle timeOffset overflows (1 byte, always increasing, relative to base)
+ if (lastTimeOffset <= timeOffset) {
+ timeOffset = timeOffset - lastTimeOffset;
+ lastTimeOffset += timeOffset;
+ } else {
+ lastTimeOffset = timeOffset;
+ }
+ totalTimeOffset += timeOffset;
+
+ switch (type) {
+ case TYPE_GPS:
+ i += consumeGPSAndUpdateBaseLocation(bytes, i, totalTimeOffset);
+ break;
+ case TYPE_HR:
+ i += consumeHeartRate(bytes, i, totalTimeOffset);
+ break;
+ case TYPE_UNKNOWN2:
+ i += consumeUnknown2(bytes, i);
+ break;
+ case TYPE_PAUSE:
+ i += consumePause(bytes, i);
+ break;
+ case TYPE_SPEED4:
+ i += consumeSpeed4(bytes, i);
+ break;
+ case TYPE_SPEED5:
+ i += consumeSpeed5(bytes, i);
+ break;
+ case TYPE_GPS_SPEED6:
+ i += consumeSpeed6(bytes, i);
+ break;
+ }
+ }
+ } catch (IndexOutOfBoundsException ex) {
+ throw new GBException("Error parsing activity details: " + ex.getMessage(), ex);
+ }
+
+ return activityTrack;
+ }
+
+ private int consumeGPSAndUpdateBaseLocation(byte[] bytes, int offset, long timeOffset) {
+ int i = 0;
+ int longitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+ int latitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+ int altitudeDelta = BLETypeConversions.toInt16(bytes[offset + i++], bytes[offset + i++]);
+
+ baseLongitude += longitudeDelta;
+ baseLatitude += latitudeDelta;
+ baseAltitude += altitudeDelta;
+
+ GPSCoordinate coordinate = new GPSCoordinate(
+ convertHuamiValueToDecimalDegrees(baseLongitude),
+ convertHuamiValueToDecimalDegrees(baseLatitude),
+ baseAltitude);
+
+ ActivityPoint ap = getActivityPointFor(timeOffset);
+ ap.setLocation(coordinate);
+ add(ap);
+
+ return i;
+ }
+
+ private double convertHuamiValueToDecimalDegrees(long huamiValue) {
+ BigDecimal result = new BigDecimal(huamiValue).divide(HUAMI_TO_DECIMAL_DEGREES_DIVISOR, GPSCoordinate.GPS_DECIMAL_DEGREES_SCALE, RoundingMode.HALF_UP);
+ return result.doubleValue();
+ }
+
+ private int consumeHeartRate(byte[] bytes, int offset, long timeOffsetSeconds) {
+ int v1 = BLETypeConversions.toUint16(bytes[offset]);
+ int v2 = BLETypeConversions.toUint16(bytes[offset + 1]);
+ int v3 = BLETypeConversions.toUint16(bytes[offset + 2]);
+ int v4 = BLETypeConversions.toUint16(bytes[offset + 3]);
+ int v5 = BLETypeConversions.toUint16(bytes[offset + 4]);
+ int v6 = BLETypeConversions.toUint16(bytes[offset + 5]);
+
+ if (v2 == 0 && v3 == 0 && v4 == 0 && v5 == 0 && v6 == 0) {
+ // new version
+// LOG.info("detected heart rate in 'new' version, where version is: " + summary.getVersion());
+ LOG.info("detected heart rate in 'new' version format");
+ ActivityPoint ap = getActivityPointFor(timeOffsetSeconds);
+ ap.setHeartRate(v1);
+ add(ap);
+ } else {
+ ActivityPoint ap = getActivityPointFor(v1);
+ ap.setHeartRate(v2);
+ add(ap);
+
+ ap = getActivityPointFor(v3);
+ ap.setHeartRate(v4);
+ add(ap);
+
+ ap = getActivityPointFor(v5);
+ ap.setHeartRate(v6);
+ add(ap);
+ }
+ return 6;
+ }
+
+ private ActivityPoint getActivityPointFor(long timeOffsetSeconds) {
+ Date time = makeAbsolute(timeOffsetSeconds);
+// if (lastActivityPoint != null) {
+// if (lastActivityPoint.getTime().equals(time)) {
+// return lastActivityPoint;
+// }
+// }
+ return new ActivityPoint(time);
+ }
+
+ private Date makeAbsolute(long timeOffsetSeconds) {
+ return new Date(baseDate.getTime() + timeOffsetSeconds * 1000);
+ }
+
+ private void add(ActivityPoint ap) {
+ if (ap != lastActivityPoint) {
+ lastActivityPoint = ap;
+ activityTrack.addTrackPoint(ap);
+ } else {
+ LOG.info("skipping point!");
+ }
+ }
+
+ private int consumeUnknown2(byte[] bytes, int offset) {
+ return 6; // just guessing...
+ }
+
+ private int consumePause(byte[] bytes, int i) {
+ return 6; // just guessing...
+ }
+
+ private int consumeSpeed4(byte[] bytes, int offset) {
+ return 6;
+ }
+
+ private int consumeSpeed5(byte[] bytes, int offset) {
+ return 6;
+ }
+
+ private int consumeSpeed6(byte[] bytes, int offset) {
+ return 6;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java
index 189ffdd4de..29c3eb31a1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/AmazfitBipSupport.java
@@ -49,10 +49,10 @@
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertCategory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertNotificationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.NewAlert;
-import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.operations.AmazfitBipFetchLogsOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.HuamiIcon;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
import nodomain.freeyourgadget.gadgetbridge.util.Version;
@@ -285,7 +285,8 @@ public void onSendWeather(WeatherSpec weatherSpec) {
@Override
public void onTestNewFunction() {
try {
- new AmazfitBipFetchLogsOperation(this).perform();
+// new AmazfitBipFetchLogsOperation(this).perform();
+ new FetchSportsSummaryOperation(this).perform();
} catch (IOException ex) {
LOG.error("Unable to fetch logs", ex);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/BipActivityType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/BipActivityType.java
new file mode 100644
index 0000000000..bc842f3574
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/BipActivityType.java
@@ -0,0 +1,53 @@
+package nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip;
+
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
+
+public enum BipActivityType {
+ Outdoor(1),
+ Treadmill(2),
+ Walking(3),
+ Cycling(4);
+
+ private final int code;
+
+ BipActivityType(final int code) {
+ this.code = code;
+ }
+
+ public int toActivityKind() {
+ switch (this) {
+ case Outdoor:
+ return ActivityKind.TYPE_RUNNING;
+ case Treadmill:
+ return ActivityKind.TYPE_TREADMILL;
+ case Cycling:
+ return ActivityKind.TYPE_CYCLING;
+ case Walking:
+ return ActivityKind.TYPE_WALKING;
+ }
+ throw new RuntimeException("Not mapped activity kind for: " + this);
+ }
+
+ public static BipActivityType fromCode(int bipCode) {
+ for (BipActivityType type : values()) {
+ if (type.code == bipCode) {
+ return type;
+ }
+ }
+ throw new RuntimeException("No matching BipActivityType for code: " + bipCode);
+ }
+
+ public static BipActivityType fromActivityKind(int activityKind) {
+ switch (activityKind) {
+ case ActivityKind.TYPE_RUNNING:
+ return Outdoor;
+ case ActivityKind.TYPE_TREADMILL:
+ return Treadmill;
+ case ActivityKind.TYPE_CYCLING:
+ return Cycling;
+ case ActivityKind.TYPE_WALKING:
+ return Walking;
+ }
+ throw new RuntimeException("No matching activity activityKind: " + activityKind);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java
index ce9b98f9e1..61fcd0ecae 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/amazfitbip/operations/AmazfitBipFetchLogsOperation.java
@@ -26,7 +26,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
-import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -50,6 +49,7 @@ public class AmazfitBipFetchLogsOperation extends AbstractFetchOperation {
public AmazfitBipFetchLogsOperation(AmazfitBipSupport support) {
super(support);
+ setName("fetch logs");
}
@Override
@@ -89,8 +89,8 @@ protected String getLastSyncTimeKey() {
}
@Override
- protected void handleActivityFetchFinish() {
- LOG.info("Fetching log data has finished");
+ protected void handleActivityFetchFinish(boolean success) {
+ LOG.info(getName() +" data has finished");
try {
logOutputStream.close();
logOutputStream = null;
@@ -98,7 +98,7 @@ protected void handleActivityFetchFinish() {
LOG.warn("could not close output stream", e);
return;
}
- super.handleActivityFetchFinish();
+ super.handleActivityFetchFinish(success);
}
@Override
@@ -113,18 +113,18 @@ protected void handleActivityNotif(byte[] value) {
lastPacketCounter++;
bufferActivityData(value);
} else {
- GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
- handleActivityFetchFinish();
+ GB.toast("Error " + getName() + " invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish(false);
}
}
@Override
protected void bufferActivityData(@NonNull byte[] value) {
try {
- logOutputStream.write(Arrays.copyOfRange(value, 1, value.length));
+ logOutputStream.write(value, 1, value.length);
} catch (IOException e) {
LOG.warn("could not write to output stream", e);
- handleActivityFetchFinish();
+ handleActivityFetchFinish(false);
}
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
index fefeba9e49..735924f2f6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java
@@ -40,6 +40,7 @@
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
+import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
@@ -69,6 +70,7 @@
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband2.MiBand2Const;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
@@ -105,6 +107,7 @@
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.actions.StopNotificationAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchActivityOperation;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchSportsSummaryOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.InitOperation;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -214,6 +217,14 @@ protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
return builder;
}
+ /**
+ * Returns the given date/time (calendar) as a byte sequence, suitable for sending to the
+ * Mi Band 2 (or derivative). The band appears to not handle DST offsets, so we simply add this
+ * to the timezone.
+ * @param calendar
+ * @param precision
+ * @return
+ */
public byte[] getTimeBytes(Calendar calendar, TimeUnit precision) {
byte[] bytes;
if (precision == TimeUnit.MINUTES) {
@@ -223,7 +234,8 @@ public byte[] getTimeBytes(Calendar calendar, TimeUnit precision) {
} else {
throw new IllegalArgumentException("Unsupported precision, only MINUTES and SECONDS are supported till now");
}
- byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar.getTimeZone()) }; // 0 = adjust reason bitflags? or DST offset?? , timezone
+ byte[] tail = new byte[] { 0, BLETypeConversions.mapTimeZone(calendar.getTimeZone(), BLETypeConversions.TZ_FLAG_INCLUDE_DST_IN_TZ) };
+ // 0 = adjust reason bitflags? or DST offset?? , timezone
// byte[] tail = new byte[] { 0x2 }; // reason
byte[] all = BLETypeConversions.join(bytes, tail);
return all;
@@ -1189,7 +1201,7 @@ public void doCurrentSample() {
sample.setHeartRate(getHeartrateBpm());
sample.setSteps(getSteps());
sample.setRawIntensity(ActivitySample.NOT_MEASURED);
- sample.setRawKind(MiBand2SampleProvider.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
+ sample.setRawKind(MiBand2Const.TYPE_ACTIVITY); // to make it visible in the charts TODO: add a MANUAL kind for that?
provider.addGBActivitySample(sample);
@@ -1372,10 +1384,9 @@ public void onSendConfiguration(String config) {
@Override
public void onTestNewFunction() {
try {
- TransactionBuilder builder = performInitialized("test realtime steps");
- builder.read(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_7_REALTIME_STEPS));
- builder.queue(getQueue());
- } catch (IOException e) {
+ new FetchSportsSummaryOperation(this).perform();
+ } catch (IOException ex) {
+ LOG.error("Unable to fetch MI activity data", ex);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java
index a63c17b350..dcad7583ee 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/AbstractFetchOperation.java
@@ -57,6 +57,7 @@ public abstract class AbstractFetchOperation extends AbstractMiBand2Operation {
protected BluetoothGattCharacteristic characteristicActivityData;
protected BluetoothGattCharacteristic characteristicFetch;
protected Calendar startTimestamp;
+ protected int expectedDataLength;
public AbstractFetchOperation(MiBand2Support support) {
super(support);
@@ -66,7 +67,8 @@ public AbstractFetchOperation(MiBand2Support support) {
protected void enableNeededNotifications(TransactionBuilder builder, boolean enable) {
if (!enable) {
// dynamically enabled, but always disabled on finish
- builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_5_ACTIVITY_DATA), enable);
+ builder.notify(characteristicFetch, enable);
+ builder.notify(characteristicActivityData, enable);
}
}
@@ -78,7 +80,7 @@ protected void doPerform() throws IOException {
protected void startFetching() throws IOException {
lastPacketCounter = -1;
- TransactionBuilder builder = performInitialized("fetching activity data");
+ TransactionBuilder builder = performInitialized(getName());
getSupport().setLowLatency(builder);
if (fetchCount == 0) {
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.busy_task_fetch_activity_data), getContext()));
@@ -115,7 +117,7 @@ public boolean onCharacteristicChanged(BluetoothGatt gatt,
}
@CallSuper
- protected void handleActivityFetchFinish() {
+ protected void handleActivityFetchFinish(boolean success) {
operationFinished();
unsetBusy();
}
@@ -139,7 +141,8 @@ protected void handleActivityMetadata(byte[] value) {
// first two bytes are whether our request was accepted
if (ArrayUtils.equals(value, MiBand2Service.RESPONSE_ACTIVITY_DATA_START_DATE_SUCCESS, 0)) {
// the third byte (0x01 on success) = ?
- // the 4th - 7th bytes probably somehow represent the number of bytes/packets to expect
+ // the 4th - 7th bytes epresent the number of bytes/packets to expect, excluding the counter bytes
+ expectedDataLength = BLETypeConversions.toUint32(Arrays.copyOfRange(value, 3, 7));
// last 8 bytes are the start date
Calendar startTimestamp = getSupport().fromTimeBytes(Arrays.copyOfRange(value, 7, value.length));
@@ -149,18 +152,18 @@ protected void handleActivityMetadata(byte[] value) {
DateFormat.getDateTimeInstance().format(startTimestamp.getTime())), Toast.LENGTH_LONG, GB.INFO);
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
- handleActivityFetchFinish();
+ handleActivityFetchFinish(false);
}
} else if (value.length == 3) {
if (Arrays.equals(MiBand2Service.RESPONSE_FINISH_SUCCESS, value)) {
- handleActivityFetchFinish();
+ handleActivityFetchFinish(true);
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
- handleActivityFetchFinish();
+ handleActivityFetchFinish(false);
}
} else {
LOG.warn("Unexpected activity metadata: " + Logging.formatBytes(value));
- handleActivityFetchFinish();
+ handleActivityFetchFinish(false);
}
}
@@ -183,7 +186,7 @@ protected GregorianCalendar getLastSuccessfulSyncTime() {
return calendar;
}
GregorianCalendar calendar = BLETypeConversions.createCalendar();
- calendar.add(Calendar.DAY_OF_MONTH, -10);
+ calendar.add(Calendar.DAY_OF_MONTH, - 100);
return calendar;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java
index b25b75766b..2d11799e48 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java
@@ -57,6 +57,7 @@ public class FetchActivityOperation extends AbstractFetchOperation {
public FetchActivityOperation(MiBand2Support support) {
super(support);
+ setName("fetching activity data");
}
@Override
@@ -74,24 +75,24 @@ protected void startFetching(TransactionBuilder builder) {
builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA});
}
- protected void handleActivityFetchFinish() {
- LOG.info("Fetching activity data has finished round " + fetchCount);
+ protected void handleActivityFetchFinish(boolean success) {
+ LOG.info(getName() + " has finished round " + fetchCount);
GregorianCalendar lastSyncTimestamp = saveSamples();
if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
try {
startFetching();
return;
} catch (IOException ex) {
- LOG.error("Error starting another round of fetching activity data", ex);
+ LOG.error("Error starting another round of " + getName(), ex);
}
}
- super.handleActivityFetchFinish();
+ super.handleActivityFetchFinish(success);
}
private boolean needsAnotherFetch(GregorianCalendar lastSyncTimestamp) {
if (fetchCount > 5) {
- LOG.warn("Already jave 5 fetch rounds, not doing another one.");
+ LOG.warn("Already have 5 fetch rounds, not doing another one.");
return false;
}
@@ -166,12 +167,13 @@ protected void handleActivityNotif(byte[] value) {
lastPacketCounter++;
bufferActivityData(value);
} else {
- GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
- handleActivityFetchFinish();
+ GB.toast("Error " + getName() + ", invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish(false);
return;
}
} else {
- GB.toast("Error fetching activity data, unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
+ GB.toast("Error " + getName() + ", unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish(false);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsDetailsOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsDetailsOperation.java
new file mode 100644
index 0000000000..de555449bf
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsDetailsOperation.java
@@ -0,0 +1,181 @@
+/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations;
+
+import android.support.annotation.NonNull;
+import android.widget.Toast;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.util.GregorianCalendar;
+import java.util.concurrent.TimeUnit;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.export.ActivityTrackExporter;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.ActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+/**
+ * An operation that fetches activity data. For every fetch, a new operation must
+ * be created, i.e. an operation may not be reused for multiple fetches.
+ */
+public class FetchSportsDetailsOperation extends AbstractFetchOperation {
+ private static final Logger LOG = LoggerFactory.getLogger(FetchSportsDetailsOperation.class);
+ private final BaseActivitySummary summary;
+ private final String lastSyncTimeKey;
+
+ private ByteArrayOutputStream buffer;
+
+ public FetchSportsDetailsOperation(@NonNull BaseActivitySummary summary, @NonNull MiBand2Support support, @NonNull String lastSyncTimeKey) {
+ super(support);
+ setName("fetching sport details");
+ this.summary = summary;
+ this.lastSyncTimeKey = lastSyncTimeKey;
+ }
+
+ @Override
+ protected void startFetching(TransactionBuilder builder) {
+ LOG.info("start " + getName());
+ buffer = new ByteArrayOutputStream(1024);
+ GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
+ builder.write(characteristicFetch, BLETypeConversions.join(new byte[] {
+ MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE,
+ AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS},
+ getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
+ builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
+ builder.notify(characteristicActivityData, true);
+ builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA });
+ }
+
+ @Override
+ protected void handleActivityFetchFinish(boolean success) {
+ LOG.info(getName() + " has finished round " + fetchCount);
+// GregorianCalendar lastSyncTimestamp = saveSamples();
+// if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
+// try {
+// startFetching();
+// return;
+// } catch (IOException ex) {
+// LOG.error("Error starting another round of fetching activity data", ex);
+// }
+// }
+
+
+ if (success) {
+ ActivityDetailsParser parser = new ActivityDetailsParser(summary);
+ parser.setSkipCounterByte(false); // is already stripped
+ try {
+ ActivityTrack track = parser.parse(buffer.toByteArray());
+ ActivityTrackExporter exporter = createExporter();
+ String fileName = FileUtils.makeValidFileName("gadgetbridge-track-" + DateTimeUtils.formatIso8601(summary.getStartTime()));
+ File targetFile = new File(FileUtils.getExternalFilesDir(), fileName);
+ exporter.performExport(track, targetFile);
+
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ summary.setGpxTrack(targetFile.getAbsolutePath());
+ dbHandler.getDaoSession().getBaseActivitySummaryDao().update(summary);
+ }
+ GregorianCalendar endTime = BLETypeConversions.createCalendar();
+ endTime.setTime(summary.getEndTime());
+ saveLastSyncTimestamp(endTime);
+ } catch (Exception ex) {
+ GB.toast(getContext(), "Error getting activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+ }
+ }
+
+ super.handleActivityFetchFinish(success);
+ }
+
+ protected ActivityTrackExporter createExporter() {
+ GPXExporter exporter = new GPXExporter();
+ exporter.setCreator(GBApplication.app().getNameAndVersion());
+ return exporter;
+ }
+
+ /**
+ * Method to handle the incoming activity data.
+ * There are two kind of messages we currently know:
+ * - the first one is 11 bytes long and contains metadata (how many bytes to expect, when the data starts, etc.)
+ * - the second one is 20 bytes long and contains the actual activity data
+ *
+ * The first message type is parsed by this method, for every other length of the value param, bufferActivityData is called.
+ *
+ * @param value
+ */
+ @Override
+ protected void handleActivityNotif(byte[] value) {
+ LOG.warn("sports details: " + Logging.formatBytes(value));
+
+ if (!isOperationRunning()) {
+ LOG.error("ignoring sports details notification because operation is not running. Data length: " + value.length);
+ getSupport().logMessageContent(value);
+ return;
+ }
+
+ if (value.length < 2) {
+ LOG.error("unexpected sports details data length: " + value.length);
+ getSupport().logMessageContent(value);
+ return;
+ }
+
+ if ((byte) (lastPacketCounter + 1) == value[0] ) {
+ lastPacketCounter++;
+ bufferActivityData(value);
+ } else {
+ GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish(false);
+ return;
+ }
+ }
+
+ /**
+ * Buffers the given activity summary data. If the total size is reached,
+ * it is converted to an object and saved in the database.
+ * @param value
+ */
+ @Override
+ protected void bufferActivityData(byte[] value) {
+ buffer.write(value, 1, value.length - 1); // skip the counter
+ }
+
+ @Override
+ protected String getLastSyncTimeKey() {
+ return lastSyncTimeKey;
+ }
+
+ protected GregorianCalendar getLastSuccessfulSyncTime() {
+ GregorianCalendar calendar = BLETypeConversions.createCalendar();
+ calendar.setTime(summary.getStartTime());
+ return calendar;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java
index 2441f6f11d..ffa69729d0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchSportsSummaryOperation.java
@@ -23,12 +23,30 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.util.Calendar;
+import java.util.Date;
import java.util.GregorianCalendar;
+import java.util.concurrent.TimeUnit;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Logging;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
+import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service;
+import nodomain.freeyourgadget.gadgetbridge.entities.BaseActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.BipActivityType;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -39,29 +57,30 @@
public class FetchSportsSummaryOperation extends AbstractFetchOperation {
private static final Logger LOG = LoggerFactory.getLogger(FetchSportsSummaryOperation.class);
-// private List samples = new ArrayList<>(60*24); // 1day per default
-
- private byte lastPacketCounter;
+ private ByteArrayOutputStream buffer = new ByteArrayOutputStream(140);
public FetchSportsSummaryOperation(MiBand2Support support) {
super(support);
+ setName("fetching sport summaries");
}
@Override
protected void startFetching(TransactionBuilder builder) {
+ LOG.info("start" + getName());
GregorianCalendar sinceWhen = getLastSuccessfulSyncTime();
-// builder.write(characteristicFetch, BLETypeConversions.join(new byte[] {
-// MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE,
-// AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES},
-// getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
-// builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
-// builder.notify(characteristicActivityData, true);
-// builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA });
+ builder.write(characteristicFetch, BLETypeConversions.join(new byte[] {
+ MiBand2Service.COMMAND_ACTIVITY_DATA_START_DATE,
+ AmazfitBipService.COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES},
+ getSupport().getTimeBytes(sinceWhen, TimeUnit.MINUTES)));
+ builder.add(new WaitAction(1000)); // TODO: actually wait for the success-reply
+ builder.notify(characteristicActivityData, true);
+ builder.write(characteristicFetch, new byte[] { MiBand2Service.COMMAND_FETCH_DATA });
}
@Override
- protected void handleActivityFetchFinish() {
- LOG.info("Fetching activity data has finished round " + fetchCount);
+ protected void handleActivityFetchFinish(boolean success) {
+ LOG.info(getName() + " has finished round " + fetchCount);
+
// GregorianCalendar lastSyncTimestamp = saveSamples();
// if (lastSyncTimestamp != null && needsAnotherFetch(lastSyncTimestamp)) {
// try {
@@ -72,7 +91,31 @@ protected void handleActivityFetchFinish() {
// }
// }
- super.handleActivityFetchFinish();
+ BaseActivitySummary summary = null;
+ if (success) {
+ summary = parseSummary(buffer);
+ try (DBHandler dbHandler = GBApplication.acquireDB()) {
+ DaoSession session = dbHandler.getDaoSession();
+ Device device = DBHelper.getDevice(getDevice(), session);
+ User user = DBHelper.getUser(session);
+ summary.setDevice(device);
+ summary.setUser(user);
+ session.getBaseActivitySummaryDao().insertOrReplace(summary);
+ } catch (Exception ex) {
+ GB.toast(getContext(), "Error saving activity summary", Toast.LENGTH_LONG, GB.ERROR, ex);
+ }
+ }
+
+ super.handleActivityFetchFinish(success);
+
+ if (summary != null) {
+ FetchSportsDetailsOperation nextOperation = new FetchSportsDetailsOperation(summary, getSupport(), getLastSyncTimeKey());
+ try {
+ nextOperation.perform();
+ } catch (IOException ex) {
+ GB.toast(getContext(), "Unable to fetch activity details: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
+ }
+ }
}
@Override
@@ -93,7 +136,7 @@ public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteris
*/
@Override
protected void handleActivityNotif(byte[] value) {
- LOG.warn("sports data: " + Logging.formatBytes(value));
+ LOG.warn("sports summary data: " + Logging.formatBytes(value));
if (!isOperationRunning()) {
LOG.error("ignoring activity data notification because operation is not running. Data length: " + value.length);
@@ -101,48 +144,99 @@ protected void handleActivityNotif(byte[] value) {
return;
}
- if ((value.length % 4) == 1) {
- if ((byte) (lastPacketCounter + 1) == value[0] ) {
- lastPacketCounter++;
- bufferActivityData(value);
- } else {
- GB.toast("Error fetching activity data, invalid package counter: " + value[0], Toast.LENGTH_LONG, GB.ERROR);
- handleActivityFetchFinish();
- return;
- }
+ if (value.length < 2) {
+ LOG.error("unexpected sports summary data length: " + value.length);
+ getSupport().logMessageContent(value);
+ return;
+ }
+
+ if ((byte) (lastPacketCounter + 1) == value[0] ) {
+ lastPacketCounter++;
+ bufferActivityData(value);
} else {
- GB.toast("Error fetching activity data, unexpected package length: " + value.length, Toast.LENGTH_LONG, GB.ERROR);
- LOG.warn("Unexpected activity data: " + Logging.formatBytes(value));
+ GB.toast("Error " + getName() + ", invalid package counter: " + value[0] + ", last was: " + lastPacketCounter, Toast.LENGTH_LONG, GB.ERROR);
+ handleActivityFetchFinish(false);
+ return;
}
}
/**
- * Creates samples from the given 17-length array
+ * Buffers the given activity summary data. If the total size is reached,
+ * it is converted to an object and saved in the database.
* @param value
*/
@Override
protected void bufferActivityData(byte[] value) {
- // TODO: implement
-// int len = value.length;
+ buffer.write(value, 1, value.length - 1); // skip the counter
+ }
+
+ private BaseActivitySummary parseSummary(ByteArrayOutputStream stream) {
+ BaseActivitySummary summary = new BaseActivitySummary();
+ ByteBuffer buffer = ByteBuffer.wrap(stream.toByteArray()).order(ByteOrder.LITTLE_ENDIAN);
+// summary.setVersion(BLETypeConversions.toUnsigned(buffer.getShort()));
+ buffer.getShort(); // version
+ int activityKind = ActivityKind.TYPE_UNKNOWN;
+ try {
+ int rawKind = BLETypeConversions.toUnsigned(buffer.getShort());
+ BipActivityType activityType = BipActivityType.fromCode(rawKind);
+ activityKind = activityType.toActivityKind();
+ } catch (Exception ex) {
+ LOG.error("Error mapping acivity kind: " + ex.getMessage(), ex);
+ }
+ summary.setActivityKind(activityKind);
+ // FIXME: should save timezone etc.
+ summary.setStartTime(new Date(BLETypeConversions.toUnsigned(buffer.getInt()) * 1000));
+ summary.setEndTime(new Date(BLETypeConversions.toUnsigned(buffer.getInt()) * 1000));
+ int baseLongitude = buffer.getInt();
+ int baseLatitude = buffer.getInt();
+ int baseAltitude = buffer.getInt();
+ summary.setBaseLongitude(baseLongitude);
+ summary.setBaseLatitude(baseLatitude);
+ summary.setBaseAltitude(baseAltitude);
+// summary.setBaseCoordinate(new GPSCoordinate(baseLatitude, baseLongitude, baseAltitude));
+
+// summary.setDistanceMeters(Float.intBitsToFloat(buffer.getInt()));
+// summary.setAscentMeters(Float.intBitsToFloat(buffer.getInt()));
+// summary.setDescentMeters(Float.intBitsToFloat(buffer.getInt()));
//
-// if (len % 4 != 1) {
-// throw new AssertionError("Unexpected activity array size: " + len);
-// }
+// summary.setMinAltitude(Float.intBitsToFloat(buffer.getInt()));
+// summary.setMaxAltitude(Float.intBitsToFloat(buffer.getInt()));
+// summary.setMinLatitude(buffer.getInt());
+// summary.setMaxLatitude(buffer.getInt());
+// summary.setMinLongitude(buffer.getInt());
+// summary.setMaxLongitude(buffer.getInt());
//
-// for (int i = 1; i < len; i+=4) {
-// }
+// summary.setSteps(BLETypeConversions.toUnsigned(buffer.getInt()));
+// summary.setActiveTimeSeconds(BLETypeConversions.toUnsigned(buffer.getInt()));
+//
+// summary.setCaloriesBurnt(Float.intBitsToFloat(buffer.get()));
+// summary.setMaxSpeed(Float.intBitsToFloat(buffer.get()));
+// summary.setMinPace(Float.intBitsToFloat(buffer.get()));
+// summary.setMaxPace(Float.intBitsToFloat(buffer.get()));
+// summary.setTotalStride(Float.intBitsToFloat(buffer.get()));
+
+ buffer.getInt(); //
+ buffer.getInt(); //
+ buffer.getInt(); //
+
+// summary.setTimeAscent(BLETypeConversions.toUnsigned(buffer.getInt()));
+// buffer.getInt(); //
+// summary.setTimeDescent(BLETypeConversions.toUnsigned(buffer.getInt()));
+// buffer.getInt(); //
+// summary.setTimeFlat(BLETypeConversions.toUnsigned(buffer.getInt()));
+//
+// summary.setAverageHR(BLETypeConversions.toUnsigned(buffer.getShort()));
+//
+// summary.setAveragePace(BLETypeConversions.toUnsigned(buffer.getShort()));
+// summary.setAverageStride(BLETypeConversions.toUnsigned(buffer.getShort()));
+
+ buffer.getShort(); //
+
+ return summary;
}
@Override
protected String getLastSyncTimeKey() {
- return getDevice().getAddress() + "_" + "lastSportsSyncTimeMillis";
- }
-
-
- protected GregorianCalendar getLastSuccessfulSyncTime() {
- // FIXME: remove this!
- GregorianCalendar calendar = BLETypeConversions.createCalendar();
- calendar.add(Calendar.DAY_OF_MONTH, -1);
- return calendar;
+ return getDevice().getAddress() + "_" + "lastSportsActivityTimeMillis";
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
index ccbf155530..9c5afcb0dd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java
@@ -21,6 +21,7 @@
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
@@ -33,9 +34,12 @@
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.content.FileProvider;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
+import java.io.File;
+import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Locale;
@@ -43,6 +47,12 @@
import nodomain.freeyourgadget.gadgetbridge.R;
public class AndroidUtils {
+ /**
+ * Creates a new {@link ParcelUuid} array with the contents of the given uuids.
+ * The given array is expected to contain only {@link ParcelUuid} elements.
+ * @param uuids an array of {@link ParcelUuid} elements
+ * @return a {@link ParcelUuid} array instance with the same contents
+ */
public static ParcelUuid[] toParcelUUids(Parcelable[] uuids) {
if (uuids == null) {
return null;
@@ -207,4 +217,15 @@ private static String colorToHex(int color) {
}
throw new IllegalArgumentException("Unable to decode the given uri to a file path: " + uri);
}
+
+ public static void viewFile(String path, String action, Context context) throws IOException {
+ Intent intent = new Intent(action);
+ File file = new File(path);
+
+ Uri contentUri = FileProvider.getUriForFile(context,
+ context.getApplicationContext().getPackageName() + ".screenshot_provider", file);
+ intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setData(contentUri);
+ context.startActivity(intent);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
index 2adab36c5d..7a43250d19 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
@@ -33,9 +33,14 @@
public class DateTimeUtils {
private static SimpleDateFormat DAY_STORAGE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
+ public static SimpleDateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ssXXX", Locale.US);
public static String formatDateTime(Date date) {
- return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
+ return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NO_YEAR);
+ }
+
+ public static String formatIso8601(Date date) {
+ return ISO_8601_FORMAT.format(date);
}
public static String formatDate(Date date) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
index ccf2d397a6..5fb11e3c34 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/FileUtils.java
@@ -310,4 +310,14 @@ public static File createTempDir(String prefix) throws IOException {
}
throw new IOException("Cannot create temporary directory in " + parent);
}
+
+ /**
+ * Replaces some wellknown invalid characters in the given filename
+ * to underscrores.
+ * @param name the file name to make valid
+ * @return the valid file name
+ */
+ public static String makeValidFileName(String name) {
+ return name.replaceAll("\0/:\\r\\n\\\\", "_");
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_biking.xml b/app/src/main/res/drawable/ic_activity_biking.xml
new file mode 100644
index 0000000000..7001d100b9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_biking.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_deep_sleep.xml b/app/src/main/res/drawable/ic_activity_deep_sleep.xml
new file mode 100644
index 0000000000..abdaea833d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_deep_sleep.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_light_sleep.xml b/app/src/main/res/drawable/ic_activity_light_sleep.xml
new file mode 100644
index 0000000000..abdaea833d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_light_sleep.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_not_measured.xml b/app/src/main/res/drawable/ic_activity_not_measured.xml
new file mode 100644
index 0000000000..7a6bb2d018
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_not_measured.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_running.xml b/app/src/main/res/drawable/ic_activity_running.xml
new file mode 100644
index 0000000000..14c06266a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_running.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_tracks.xml b/app/src/main/res/drawable/ic_activity_tracks.xml
new file mode 100644
index 0000000000..14c06266a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_tracks.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_unknown.xml b/app/src/main/res/drawable/ic_activity_unknown.xml
new file mode 100644
index 0000000000..4e323aeaa5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_unknown.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_activity_walking.xml b/app/src/main/res/drawable/ic_activity_walking.xml
new file mode 100644
index 0000000000..831c0b3456
--- /dev/null
+++ b/app/src/main/res/drawable/ic_activity_walking.xml
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml
new file mode 100644
index 0000000000..170019522d
--- /dev/null
+++ b/app/src/main/res/layout/activity_list.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/device_itemv2.xml b/app/src/main/res/layout/device_itemv2.xml
index 517cd04446..09fcb4e20c 100644
--- a/app/src/main/res/layout/device_itemv2.xml
+++ b/app/src/main/res/layout/device_itemv2.xml
@@ -231,12 +231,27 @@
card_view:srcCompat="@drawable/ic_activity_graphs" />
+
+ Mute
Reply
+ Your activity tracks
+ Not measured
+ Activity
+ Light sleep
+ Deep sleep
+ Device not worn
+ Running
+ Walking
+ Swimming
+ Unknown activity
+ Activities
+ Biking
+ Treadmill
+
Firmware
Invalid data
Font
diff --git a/app/src/main/res/xml/screenshot_provider_paths.xml b/app/src/main/res/xml/shared_paths.xml
similarity index 63%
rename from app/src/main/res/xml/screenshot_provider_paths.xml
rename to app/src/main/res/xml/shared_paths.xml
index ffa74ab562..2d4b2d4c1d 100644
--- a/app/src/main/res/xml/screenshot_provider_paths.xml
+++ b/app/src/main/res/xml/shared_paths.xml
@@ -1,4 +1,6 @@
+
+
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ActivityDetailsParserTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ActivityDetailsParserTest.java
new file mode 100644
index 0000000000..6b9fa25f24
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/ActivityDetailsParserTest.java
@@ -0,0 +1,110 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBException;
+import nodomain.freeyourgadget.gadgetbridge.devices.amazfitbip.BipActivitySummary;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.export.GPXExporter;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
+import nodomain.freeyourgadget.gadgetbridge.model.ActivityTrack;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.amazfitbip.ActivityDetailsParser;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class ActivityDetailsParserTest extends TestBase {
+ private static final URL DETAILS_1 = ActivityDetailsParserTest.class.getClassLoader().getResource("ActivityDetailsDump1.txt");
+ private static final long MAX_DETAILS = 1024 * 1024;
+ private static Date baseTime;
+
+ @BeforeClass
+ public static void setUpSuite() throws Exception {
+ baseTime = DateTimeUtils.ISO_8601_FORMAT.parse("2017-01-20T14:00:00-00:00"); // yyyy-mm-dd'T'hh:mm:ssZ
+ }
+
+ @Test
+ public void testActivityDetails() throws Exception {
+ BipActivitySummary summary = createSummary();
+
+ ActivityDetailsParser parser = new ActivityDetailsParser(summary);
+ parser.setSkipCounterByte(true);
+ try (InputStream in = getContents(DETAILS_1)) {
+ ActivityTrack track = parser.parse(FileUtils.readAll(in, MAX_DETAILS));
+ assertEquals("SuperBand 2000", track.getDevice().getName());
+ assertEquals("Elvis", track.getUser().getName());
+
+ List trackPoints = track.getTrackPoints();
+ assertEquals(1208, trackPoints.size());
+ }
+ }
+
+ private BipActivitySummary createSummary() {
+ BipActivitySummary summary = new BipActivitySummary();
+ summary.setBaseLongitude(1);
+ summary.setBaseLatitude(1);
+ summary.setBaseAltitude(1);
+ summary.setStartTime(baseTime);
+ User dummyUser = new User(0L);
+ dummyUser.setName("Elvis");
+ summary.setName("testtrack");
+ summary.setUser(dummyUser);
+ Device device = new Device(0l);
+ device.setName("SuperBand 2000");
+ summary.setDevice(device);
+
+ return summary;
+ }
+
+ @Test
+ public void testGPXExport() throws Exception {
+ BipActivitySummary summary = createSummary();
+
+ int baseLongi = BLETypeConversions.toUint32((byte) 0xd6, (byte) 0xc4,(byte) 0x62,(byte) 0x02);
+ int baseLati = BLETypeConversions.toUint32((byte) 0xff, (byte) 0xa9, (byte) 0x61, (byte) 0x9);
+ int baseAlti = BLETypeConversions.toUint32((byte) 0x30, (byte) 0x0, (byte) 0x0, (byte) 0x0);
+
+ summary.setBaseLongitude(baseLongi);
+ summary.setBaseLatitude(baseLati);
+ summary.setBaseAltitude(baseAlti);
+
+ ActivityDetailsParser parser = new ActivityDetailsParser(summary);
+ parser.setSkipCounterByte(true);
+ try (InputStream in = getContents(DETAILS_1)) {
+ ActivityTrack track = parser.parse(FileUtils.readAll(in, MAX_DETAILS));
+
+ List trackPoints = track.getTrackPoints();
+ assertEquals(1208, trackPoints.size());
+
+
+ GPXExporter exporter = new GPXExporter();
+ exporter.setIncludeHeartRate(false);
+ exporter.setCreator(getClass().getName());
+ File targetFile = File.createTempFile("gadgetbridge-track", ".gpx");
+ System.out.println("Writing GPX file: " + targetFile);
+ exporter.performExport(track, targetFile);
+
+ assertTrue(targetFile.length() > 1024);
+ }
+
+ }
+
+ private InputStream getContents(URL hexFile) throws IOException {
+ return new HexToBinaryInputStream(hexFile.openStream());
+ }
+
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java
new file mode 100644
index 0000000000..70c39e3521
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/BLETypeConversionsTest.java
@@ -0,0 +1,39 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Test;
+
+import java.util.GregorianCalendar;
+
+import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
+
+import static org.junit.Assert.assertTrue;
+
+public class BLETypeConversionsTest extends TestBase {
+ @Test
+ public void testTimeParsing1() {
+ byte[] requested = new byte[] {
+ (byte) 0xe1, 0x07, 0x0a, 0x1d, 0x00, 0x1c, 0x00,0x08
+ };
+ byte[] received = new byte[] {
+ (byte) 0xe1, 0x07, 0x0a, 0x1c, 0x17, 0x1c, 0x00, 0x04
+ };
+ GregorianCalendar calRequested = BLETypeConversions.rawBytesToCalendar(requested, false);
+ GregorianCalendar calReceived = BLETypeConversions.rawBytesToCalendar(received, false);
+
+ assertTrue(calRequested.getTime().equals(calReceived.getTime()));
+ }
+
+ @Test
+ public void testTimeParsingWithDST() {
+ byte[] requested = new byte[] {
+ (byte) 0xe1,0x07,0x0a,0x09,0x11,0x23,0x00,0x08
+ };
+ byte[] received = new byte[] {
+ (byte) 0xe1,0x07,0x0a,0x09,0x10,0x23,0x00,0x04
+ };
+ GregorianCalendar calRequested = BLETypeConversions.rawBytesToCalendar(requested, false);
+ GregorianCalendar calReceived = BLETypeConversions.rawBytesToCalendar(received, false);
+
+ assertTrue(calRequested.getTime().equals(calReceived.getTime()));
+ }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java
new file mode 100644
index 0000000000..da22ee0eb2
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStream.java
@@ -0,0 +1,52 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import android.support.annotation.NonNull;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+class HexToBinaryInputStream extends FilterInputStream {
+ HexToBinaryInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int value;
+ StringBuilder buffer = new StringBuilder(4);
+
+ loop:
+ while (true) {
+ value = super.read();
+ switch (value) {
+ case -1:
+ case ' ':
+ case '\r':
+ case '\n':
+ break loop;
+ default:
+ buffer.append((char) value);
+ }
+ }
+ if (buffer.length() > 0) {
+ return Integer.decode(buffer.toString());
+ }
+ return -1;
+ }
+
+ @Override
+ public int read(@NonNull byte[] b, int off, int len) throws IOException {
+ if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < len; i++) {
+ int value = read();
+ if (value == -1) {
+ return i;
+ }
+ b[off + i] = (byte) value;
+ }
+ return len;
+ }
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java
new file mode 100644
index 0000000000..a4b5f5b954
--- /dev/null
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/HexToBinaryInputStreamTest.java
@@ -0,0 +1,50 @@
+package nodomain.freeyourgadget.gadgetbridge.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
+
+import static org.junit.Assert.assertTrue;
+
+public class HexToBinaryInputStreamTest extends TestBase {
+
+ @Test
+ public void testConversion() throws IOException {
+ byte[] hexString;
+ byte[] binString;
+
+ try (InputStream in = ActivityDetailsParserTest.class.getClassLoader().getResourceAsStream("ActivityDetailsDump1.txt")) {
+ hexString = FileUtils.readAll(in, 1024 * 1024);
+ assertTrue(hexString.length > 1);
+ try (InputStream in2 = getContents(ActivityDetailsParserTest.class.getClassLoader().getResource("ActivityDetailsDump1.txt"))) {
+ binString = FileUtils.readAll(in2, 1024 * 1024);
+ assertTrue(binString.length > 1);
+ }
+ }
+ Assert.assertTrue(hexString.length > binString.length);
+ ByteArrayOutputStream binToHexOut = new ByteArrayOutputStream(hexString.length);
+ for (int i = 0; i < binString.length; i++) {
+ String hexed = String.format("0x%x", binString[i]);
+ binToHexOut.write(hexed.getBytes("US-ASCII"));
+ if ((i + 1) % 17 == 0) {
+ binToHexOut.write('\n');
+ } else {
+ binToHexOut.write(' ');
+ }
+ }
+
+ byte[] hexedBytes = binToHexOut.toByteArray();
+ Assert.assertArrayEquals(hexString, hexedBytes);
+ }
+
+ private InputStream getContents(URL hexFile) throws IOException {
+ return new HexToBinaryInputStream(hexFile.openStream());
+ }
+
+}
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java
index 17a720b697..9aca15ed71 100644
--- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/Tryout.java
@@ -23,6 +23,11 @@ public void blah() {
byte b = (byte) v;
LOG.info("v: " + v);
Logging.logBytes(LOG, new byte[] { b });
+
+ byte[] bs = new byte[] {(byte) 0xf0,0x28,0x00,0x00 };
+ LOG.warn("uint32: " + BLETypeConversions.toUint32(bs));
+ LOG.warn("uint16: " + BLETypeConversions.toUint16(bs));
+
}
@Test
diff --git a/app/src/test/resources/ActivityDetailsDump1.txt b/app/src/test/resources/ActivityDetailsDump1.txt
new file mode 100644
index 0000000000..f5c33455ad
--- /dev/null
+++ b/app/src/test/resources/ActivityDetailsDump1.txt
@@ -0,0 +1,861 @@
+0x0 0x1 0x7 0x47 0x0 0x0 0x0 0x0 0x0 0x1 0x8 0x46 0x0 0x0 0x0 0x0 0x0
+0x1 0x1 0xa 0x43 0x0 0x0 0x0 0x0 0x0 0x1 0xc 0x46 0x0 0x0 0x0 0x0 0x0
+0x2 0x1 0xd 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0xf 0x3e 0x0 0x0 0x0 0x0 0x0
+0x3 0x1 0x11 0x3b 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x3e 0x0 0x0 0x0 0x0 0x0
+0x4 0x1 0x17 0x3d 0x0 0x0 0x0 0x0 0x0 0x1 0x18 0x3f 0x0 0x0 0x0 0x0 0x0
+0x5 0x1 0x1b 0x41 0x0 0x0 0x0 0x0 0x0 0x1 0x1d 0x42 0x0 0x0 0x0 0x0 0x0
+0x6 0x1 0x20 0x43 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x44 0x0 0x0 0x0 0x0 0x0
+0x7 0x1 0x22 0x45 0x0 0x0 0x0 0x0 0x0 0x0 0x23 0x0 0x0 0x0 0x0 0x0 0x0
+0x8 0x0 0x23 0x79 0xff 0xcd 0x0 0x0 0x0 0x0 0x23 0x88 0xff 0xc3 0x0 0x0 0x0
+0x9 0x0 0x23 0xbf 0xff 0xeb 0x0 0x0 0x0 0x0 0x23 0xec 0xff 0x4 0x1 0x0 0x0
+0xa 0x0 0x23 0x5 0x0 0x4 0x1 0x0 0x0 0x0 0x23 0xf6 0xff 0x4f 0x1 0x0 0x0
+0xb 0x0 0x23 0xec 0xff 0x18 0x1 0x0 0x0 0x0 0x23 0xf1 0xff 0xff 0x0 0x0 0x0
+0xc 0x0 0x23 0xf1 0xff 0xf5 0x0 0x0 0x0 0x0 0x23 0xe2 0xff 0xa0 0x0 0x0 0x0
+0xd 0x0 0x23 0x9c 0xff 0xeb 0x0 0x0 0x0 0x0 0x23 0xf1 0xff 0xeb 0x0 0x0 0x0
+0xe 0x0 0x23 0xfb 0xff 0xe 0x1 0x0 0x0 0x0 0x23 0xf6 0xff 0x13 0x1 0x0 0x0
+0xf 0x0 0x23 0xfb 0xff 0x68 0x1 0x0 0x0 0x0 0x23 0xe2 0xff 0x18 0x1 0x0 0x0
+0x10 0x0 0x23 0xb5 0xff 0xd2 0x0 0x0 0x0 0x0 0x23 0x97 0xff 0x87 0x0 0x0 0x0
+0x11 0x0 0x23 0xba 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xe2 0xff 0xfa 0x0 0x0 0x0
+0x12 0x0 0x23 0xe2 0xff 0xb9 0x0 0x0 0x0 0x0 0x23 0xfb 0xff 0x4 0x1 0x0 0x0
+0x13 0x0 0x23 0xec 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xf1 0xff 0x2c 0x1 0x0 0x0
+0x14 0x0 0x23 0x5 0x0 0xe 0x1 0x0 0x0 0x0 0x23 0xec 0xff 0x9b 0x0 0x0 0x0
+0x15 0x0 0x23 0xf1 0xff 0xa5 0x0 0x0 0x0 0x0 0x23 0xf6 0xff 0xb4 0x0 0x0 0x0
+0x16 0x0 0x23 0xec 0xff 0xc3 0x0 0x0 0x0 0x0 0x23 0x7e 0xff 0xd2 0x0 0x0 0x0
+0x17 0x6 0x23 0x81 0x0 0xd2 0x0 0x0 0x0 0x0 0x24 0xd3 0xff 0x7d 0x0 0x0 0x0
+0x18 0x6 0x24 0x81 0x0 0x7d 0x0 0x0 0x0 0x0 0x25 0xc1 0xff 0xb1 0x0 0x0 0x0
+0x19 0x6 0x25 0x81 0x0 0xb1 0x0 0x0 0x0 0x0 0x26 0xca 0xff 0xc9 0x0 0x0 0x0
+0x1a 0x6 0x26 0x81 0x0 0xc9 0x0 0x0 0x0 0x0 0x27 0xd7 0xff 0xdd 0x0 0x0 0x0
+0x1b 0x6 0x27 0x81 0x0 0xdd 0x0 0x0 0x0 0x1 0x27 0x46 0x0 0x0 0x0 0x0 0x0
+0x1c 0x0 0x28 0xe4 0xff 0xf8 0x0 0x0 0x0 0x6 0x28 0x81 0x0 0xf8 0x0 0x0 0x0
+0x1d 0x0 0x29 0xef 0xff 0xf 0x1 0x0 0x0 0x6 0x29 0x81 0x0 0xf 0x1 0x0 0x0
+0x1e 0x1 0x29 0x43 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0xf1 0xff 0x2 0x1 0x0 0x0
+0x1f 0x6 0x2a 0x81 0x0 0x2 0x1 0x0 0x0 0x1 0x2a 0x45 0x0 0x0 0x0 0x0 0x0
+0x20 0x0 0x2b 0xeb 0xff 0xf4 0x0 0xfe 0xff 0x6 0x2b 0x81 0x0 0xf4 0x0 0xfe 0xff
+0x21 0x0 0x2c 0xe8 0xff 0xdc 0x0 0x0 0x0 0x6 0x2c 0x81 0x0 0xdc 0x0 0x0 0x0
+0x22 0x0 0x2d 0xe9 0xff 0xca 0x0 0x0 0x0 0x6 0x2d 0x81 0x0 0xca 0x0 0x0 0x0
+0x23 0x0 0x2e 0xeb 0xff 0xc9 0x0 0x0 0x0 0x6 0x2e 0x81 0x0 0xc9 0x0 0x0 0x0
+0x24 0x1 0x2e 0x46 0x0 0x0 0x0 0x0 0x0 0x0 0x2f 0xed 0xff 0xcb 0x0 0x0 0x0
+0x25 0x6 0x2f 0x81 0x0 0xcb 0x0 0x0 0x0 0x1 0x2f 0x47 0x0 0x0 0x0 0x0 0x0
+0x26 0x0 0x30 0xf0 0xff 0xc5 0x0 0x0 0x0 0x6 0x30 0x81 0x0 0xc5 0x0 0x0 0x0
+0x27 0x0 0x31 0xf2 0xff 0xb7 0x0 0x0 0x0 0x6 0x31 0x81 0x0 0xb7 0x0 0x0 0x0
+0x28 0x0 0x32 0xf1 0xff 0xb1 0x0 0x0 0x0 0x6 0x32 0x81 0x0 0xb1 0x0 0x0 0x0
+0x29 0x0 0x33 0xf7 0xff 0xb5 0x0 0x0 0x0 0x6 0x33 0x81 0x0 0xb5 0x0 0x0 0x0
+0x2a 0x1 0x33 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0x34 0xfa 0xff 0xc8 0x0 0x0 0x0
+0x2b 0x6 0x34 0x81 0x0 0xc8 0x0 0x0 0x0 0x1 0x34 0x4a 0x0 0x0 0x0 0x0 0x0
+0x2c 0x0 0x35 0xf7 0xff 0xda 0x0 0x0 0x0 0x6 0x35 0x81 0x0 0xda 0x0 0x0 0x0
+0x2d 0x1 0x35 0x4b 0x0 0x0 0x0 0x0 0x0 0x0 0x36 0xf3 0xff 0xdb 0x0 0x0 0x0
+0x2e 0x6 0x36 0x81 0x0 0xdb 0x0 0x0 0x0 0x0 0x37 0xe9 0xff 0xed 0x0 0x0 0x0
+0x2f 0x6 0x37 0x81 0x0 0xed 0x0 0x0 0x0 0x1 0x38 0x4c 0x0 0x0 0x0 0x0 0x0
+0x30 0x0 0x38 0xe9 0xff 0xf6 0x0 0x0 0x0 0x6 0x38 0x81 0x0 0xf6 0x0 0x0 0x0
+0x31 0x0 0x39 0xeb 0xff 0xe4 0x0 0xfe 0xff 0x6 0x39 0x81 0x0 0xe4 0x0 0xfe 0xff
+0x32 0x1 0x39 0x4d 0x0 0x0 0x0 0x0 0x0 0x0 0x3a 0xef 0xff 0xbd 0x0 0x0 0x0
+0x33 0x6 0x3a 0x81 0x0 0xbd 0x0 0x0 0x0 0x1 0x3a 0x4e 0x0 0x0 0x0 0x0 0x0
+0x34 0x0 0x3c 0xed 0xff 0xcc 0x0 0x0 0x0 0x6 0x3c 0x81 0x0 0xcc 0x0 0x0 0x0
+0x35 0x1 0x3d 0x4f 0x0 0x0 0x0 0x0 0x0 0x1 0x3e 0x50 0x0 0x0 0x0 0x0 0x0
+0x36 0x0 0x3e 0x1 0x0 0xcd 0x0 0x0 0x0 0x6 0x3e 0x81 0x0 0xcd 0x0 0x0 0x0
+0x37 0x0 0x3f 0x14 0x0 0x91 0x0 0x0 0x0 0x6 0x3f 0x81 0x0 0x91 0x0 0x0 0x0
+0x38 0x1 0x3f 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0x41 0x86 0x1 0x6e 0x0 0x0 0x0
+0x39 0x6 0x41 0x81 0x0 0x6e 0x0 0x0 0x0 0x1 0x41 0x52 0x0 0x0 0x0 0x0 0x0
+0x3a 0x0 0x42 0x5e 0x1 0x55 0x0 0x0 0x0 0x6 0x42 0x81 0x0 0x55 0x0 0x0 0x0
+0x3b 0x0 0x43 0x36 0x1 0x48 0x0 0x0 0x0 0x6 0x43 0x81 0x0 0x48 0x0 0x0 0x0
+0x3c 0x0 0x44 0x31 0x1 0x36 0x0 0x0 0x0 0x6 0x44 0x81 0x0 0x36 0x0 0x0 0x0
+0x3d 0x1 0x44 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0x3d 0x1 0x2f 0x0 0x0 0x0
+0x3e 0x6 0x45 0x81 0x0 0x2f 0x0 0x0 0x0 0x0 0x46 0x35 0x1 0x38 0x0 0x0 0x0
+0x3f 0x6 0x46 0x81 0x0 0x38 0x0 0x0 0x0 0x1 0x46 0x54 0x0 0x0 0x0 0x0 0x0
+0x40 0x0 0x47 0x76 0x1 0x37 0x0 0x0 0x0 0x6 0x47 0x81 0x0 0x37 0x0 0x0 0x0
+0x41 0x0 0x48 0xbd 0x1 0x32 0x0 0x0 0x0 0x6 0x48 0x81 0x0 0x32 0x0 0x0 0x0
+0x42 0x1 0x49 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x49 0xb0 0x1 0xd 0x0 0x0 0x0
+0x43 0x6 0x49 0x81 0x0 0xd 0x0 0x0 0x0 0x0 0x4a 0x9b 0x1 0x1a 0x0 0x0 0x0
+0x44 0x6 0x4a 0x81 0x0 0x1a 0x0 0x0 0x0 0x0 0x4b 0x7a 0x1 0x1c 0x0 0xfe 0xff
+0x45 0x6 0x4b 0x81 0x0 0x1c 0x0 0xfe 0xff 0x0 0x4c 0x53 0x1 0x34 0x0 0x0 0x0
+0x46 0x6 0x4c 0x81 0x0 0x34 0x0 0x0 0x0 0x1 0x4c 0x56 0x0 0x0 0x0 0x0 0x0
+0x47 0x0 0x4d 0x24 0x1 0xfe 0xff 0x0 0x0 0x6 0x4d 0x81 0x0 0xfe 0xff 0x0 0x0
+0x48 0x1 0x4e 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x4f 0x3c 0x1 0xff 0xff 0x0 0x0
+0x49 0x6 0x4f 0x81 0x0 0xff 0xff 0x0 0x0 0x0 0x51 0x5 0x1 0x2a 0x0 0x0 0x0
+0x4a 0x6 0x51 0x81 0x0 0x2a 0x0 0x0 0x0 0x1 0x52 0x54 0x0 0x0 0x0 0x0 0x0
+0x4b 0x0 0x53 0xd5 0x0 0x77 0x0 0x0 0x0 0x6 0x53 0x81 0x0 0x77 0x0 0x0 0x0
+0x4c 0x0 0x54 0x86 0x0 0x89 0x0 0x0 0x0 0x6 0x54 0x81 0x0 0x89 0x0 0x0 0x0
+0x4d 0x1 0x55 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x55 0x59 0x0 0xa3 0x0 0x0 0x0
+0x4e 0x6 0x55 0x95 0x0 0xa3 0x0 0x0 0x0 0x0 0x56 0x3c 0x0 0xc2 0x0 0x0 0x0
+0x4f 0x6 0x56 0x95 0x0 0xc2 0x0 0x0 0x0 0x1 0x56 0x4f 0x0 0x0 0x0 0x0 0x0
+0x50 0x0 0x57 0x23 0x0 0xe8 0x0 0x0 0x0 0x6 0x57 0x95 0x0 0xe8 0x0 0x0 0x0
+0x51 0x1 0x57 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x58 0x11 0x0 0x5 0x1 0x0 0x0
+0x52 0x6 0x58 0x95 0x0 0x5 0x1 0x0 0x0 0x0 0x59 0xfc 0xff 0xf 0x1 0x0 0x0
+0x53 0x6 0x59 0x95 0x0 0xf 0x1 0x0 0x0 0x1 0x5a 0x51 0x0 0x0 0x0 0x0 0x0
+0x54 0x0 0x5a 0xed 0xff 0x25 0x1 0x0 0x0 0x6 0x5a 0x95 0x0 0x25 0x1 0x0 0x0
+0x55 0x0 0x5b 0xe7 0xff 0x32 0x1 0x0 0x0 0x6 0x5b 0x95 0x0 0x32 0x1 0x0 0x0
+0x56 0x0 0x5c 0xb8 0xff 0xa5 0x1 0x0 0x0 0x6 0x5c 0x95 0x0 0xa5 0x1 0x0 0x0
+0x57 0x0 0x5d 0xc0 0xff 0x7c 0x1 0x0 0x0 0x6 0x5d 0x95 0x0 0x7c 0x1 0x0 0x0
+0x58 0x0 0x5e 0xde 0xff 0x3e 0x1 0x0 0x0 0x6 0x5e 0x82 0x0 0x3e 0x1 0x0 0x0
+0x59 0x1 0x5f 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x5f 0xd8 0xff 0x46 0x1 0x0 0x0
+0x5a 0x6 0x5f 0x82 0x0 0x46 0x1 0x0 0x0 0x1 0x60 0x53 0x0 0x0 0x0 0x0 0x0
+0x5b 0x0 0x60 0xd3 0xff 0x39 0x1 0x0 0x0 0x6 0x60 0x82 0x0 0x39 0x1 0x0 0x0
+0x5c 0x0 0x61 0xc9 0xff 0x1f 0x1 0x0 0x0 0x6 0x61 0x82 0x0 0x1f 0x1 0x0 0x0
+0x5d 0x1 0x61 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x63 0xbf 0xff 0xdd 0x0 0x0 0x0
+0x5e 0x6 0x63 0x82 0x0 0xdd 0x0 0x0 0x0 0x1 0x63 0x56 0x0 0x0 0x0 0x0 0x0
+0x5f 0x1 0x66 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x6a 0xea 0xff 0xc6 0x0 0x0 0x0
+0x60 0x6 0x6a 0x82 0x0 0xc6 0x0 0x0 0x0 0x1 0x6b 0x56 0x0 0x0 0x0 0x0 0x0
+0x61 0x0 0x6c 0x59 0x0 0x9f 0x0 0x0 0x0 0x6 0x6c 0x96 0x0 0x9f 0x0 0x0 0x0
+0x62 0x1 0x6d 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x6e 0x24 0x1 0x61 0x0 0x0 0x0
+0x63 0x6 0x6e 0x96 0x0 0x61 0x0 0x0 0x0 0x1 0x6e 0x54 0x0 0x0 0x0 0x0 0x0
+0x64 0x0 0x6f 0x39 0x1 0x24 0x0 0x0 0x0 0x6 0x6f 0x96 0x0 0x24 0x0 0x0 0x0
+0x65 0x1 0x70 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x70 0x5b 0x1 0xfb 0xff 0x0 0x0
+0x66 0x6 0x70 0x96 0x0 0xfb 0xff 0x0 0x0 0x0 0x71 0x7f 0x1 0xe0 0xff 0x0 0x0
+0x67 0x6 0x71 0x96 0x0 0xe0 0xff 0x0 0x0 0x0 0x72 0xa3 0x1 0xd4 0xff 0x0 0x0
+0x68 0x6 0x72 0x96 0x0 0xd4 0xff 0x0 0x0 0x1 0x72 0x54 0x0 0x0 0x0 0x0 0x0
+0x69 0x0 0x73 0xc2 0x1 0xcf 0xff 0x0 0x0 0x6 0x73 0x96 0x0 0xcf 0xff 0x0 0x0
+0x6a 0x0 0x74 0xd8 0x1 0xc7 0xff 0x0 0x0 0x6 0x74 0x96 0x0 0xc7 0xff 0x0 0x0
+0x6b 0x1 0x74 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x75 0xe6 0x1 0xbe 0xff 0x0 0x0
+0x6c 0x6 0x75 0x96 0x0 0xbe 0xff 0x0 0x0 0x0 0x76 0xeb 0x1 0xb2 0xff 0x0 0x0
+0x6d 0x6 0x76 0x96 0x0 0xb2 0xff 0x0 0x0 0x1 0x77 0x56 0x0 0x0 0x0 0x0 0x0
+0x6e 0x0 0x77 0x8a 0x2 0x70 0xff 0x0 0x0 0x6 0x77 0x96 0x0 0x70 0xff 0x0 0x0
+0x6f 0x0 0x78 0x92 0x2 0x9a 0xff 0x0 0x0 0x6 0x78 0x96 0x0 0x9a 0xff 0x0 0x0
+0x70 0x1 0x78 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x79 0x7c 0x2 0xb9 0xff 0x0 0x0
+0x71 0x6 0x79 0x96 0x0 0xb9 0xff 0x0 0x0 0x0 0x7a 0x59 0x2 0xb9 0xff 0x0 0x0
+0x72 0x6 0x7a 0x83 0x0 0xb9 0xff 0x0 0x0 0x0 0x7b 0x33 0x2 0xa3 0xff 0x0 0x0
+0x73 0x6 0x7b 0x83 0x0 0xa3 0xff 0x0 0x0 0x0 0x7c 0x27 0x2 0xac 0xff 0x0 0x0
+0x74 0x6 0x7c 0x83 0x0 0xac 0xff 0x0 0x0 0x1 0x7d 0x56 0x0 0x0 0x0 0x0 0x0
+0x75 0x0 0x7d 0x29 0x2 0xb1 0xff 0x0 0x0 0x6 0x7d 0x83 0x0 0xb1 0xff 0x0 0x0
+0x76 0x0 0x7e 0x16 0x2 0xa9 0xff 0x2 0x0 0x6 0x7e 0x83 0x0 0xa9 0xff 0x2 0x0
+0x77 0x1 0x7e 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x7f 0xd 0x2 0xaf 0xff 0x0 0x0
+0x78 0x6 0x7f 0x83 0x0 0xaf 0xff 0x0 0x0 0x0 0x80 0x0 0x2 0xb5 0xff 0x0 0x0
+0x79 0x6 0x80 0x70 0x0 0xb5 0xff 0x0 0x0 0x1 0x80 0x54 0x0 0x0 0x0 0x0 0x0
+0x7a 0x0 0x81 0x7 0x2 0xb7 0xff 0x0 0x0 0x6 0x81 0x70 0x0 0xb7 0xff 0x0 0x0
+0x7b 0x0 0x82 0xa 0x2 0xc2 0xff 0x0 0x0 0x6 0x82 0x70 0x0 0xc2 0xff 0x0 0x0
+0x7c 0x4 0x83 0x83 0x0 0x15 0x0 0x0 0x0 0x0 0x83 0x6 0x2 0xc6 0xff 0x0 0x0
+0x7d 0x6 0x83 0x70 0x0 0xc6 0xff 0x0 0x0 0x0 0x84 0x0 0x2 0xc2 0xff 0x0 0x0
+0x7e 0x6 0x84 0x70 0x0 0xc2 0xff 0x0 0x0 0x0 0x85 0x9 0x2 0xba 0xff 0x0 0x0
+0x7f 0x6 0x85 0x70 0x0 0xba 0xff 0x0 0x0 0x1 0x85 0x54 0x0 0x0 0x0 0x0 0x0
+0x80 0x0 0x86 0x8 0x2 0xb6 0xff 0x0 0x0 0x6 0x86 0x70 0x0 0xb6 0xff 0x0 0x0
+0x81 0x0 0x87 0xfe 0x1 0xaf 0xff 0x0 0x0 0x6 0x87 0x70 0x0 0xaf 0xff 0x0 0x0
+0x82 0x1 0x88 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x88 0xfc 0x1 0xae 0xff 0x0 0x0
+0x83 0x6 0x88 0x70 0x0 0xae 0xff 0x0 0x0 0x0 0x89 0xf3 0x1 0xad 0xff 0x0 0x0
+0x84 0x6 0x89 0x70 0x0 0xad 0xff 0x0 0x0 0x0 0x8a 0xe4 0x1 0xae 0xff 0x0 0x0
+0x85 0x6 0x8a 0x70 0x0 0xae 0xff 0x0 0x0 0x1 0x8a 0x56 0x0 0x0 0x0 0x0 0x0
+0x86 0x0 0x8b 0xd3 0x1 0xad 0xff 0x0 0x0 0x6 0x8b 0x70 0x0 0xad 0xff 0x0 0x0
+0x87 0x0 0x8c 0xcd 0x1 0xb3 0xff 0x0 0x0 0x6 0x8c 0x70 0x0 0xb3 0xff 0x0 0x0
+0x88 0x1 0x8d 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0xcf 0x1 0xae 0xff 0x0 0x0
+0x89 0x6 0x8d 0x70 0x0 0xae 0xff 0x0 0x0 0x0 0x8e 0xdf 0x1 0xb0 0xff 0x0 0x0
+0x8a 0x6 0x8e 0x70 0x0 0xb0 0xff 0x0 0x0 0x0 0x8f 0xec 0x1 0xb9 0xff 0x2 0x0
+0x8b 0x6 0x8f 0x70 0x0 0xb9 0xff 0x2 0x0 0x0 0x90 0xfa 0x1 0xb5 0xff 0x0 0x0
+0x8c 0x6 0x90 0x70 0x0 0xb5 0xff 0x0 0x0 0x1 0x90 0x59 0x0 0x0 0x0 0x0 0x0
+0x8d 0x0 0x91 0x1 0x2 0xcd 0xff 0x0 0x0 0x6 0x91 0x70 0x0 0xcd 0xff 0x0 0x0
+0x8e 0x0 0x92 0xa 0x2 0xda 0xff 0x0 0x0 0x6 0x92 0x70 0x0 0xda 0xff 0x0 0x0
+0x8f 0x0 0x93 0xe 0x2 0xd7 0xff 0x0 0x0 0x6 0x93 0x70 0x0 0xd7 0xff 0x0 0x0
+0x90 0x1 0x94 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x94 0x15 0x2 0xda 0xff 0x0 0x0
+0x91 0x6 0x94 0x70 0x0 0xda 0xff 0x0 0x0 0x0 0x95 0x13 0x2 0xed 0xff 0x0 0x0
+0x92 0x6 0x95 0x70 0x0 0xed 0xff 0x0 0x0 0x1 0x95 0x5b 0x0 0x0 0x0 0x0 0x0
+0x93 0x0 0x96 0xe1 0x1 0xf4 0xff 0x0 0x0 0x6 0x96 0x70 0x0 0xf4 0xff 0x0 0x0
+0x94 0x1 0x96 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0x97 0x38 0x2 0x4 0x0 0x0 0x0
+0x95 0x6 0x97 0x70 0x0 0x4 0x0 0x0 0x0 0x0 0x98 0x2f 0x2 0x13 0x0 0x0 0x0
+0x96 0x6 0x98 0x5d 0x0 0x13 0x0 0x0 0x0 0x1 0x99 0x5d 0x0 0x0 0x0 0x0 0x0
+0x97 0x0 0x99 0x2a 0x2 0x23 0x0 0x0 0x0 0x6 0x99 0x5d 0x0 0x23 0x0 0x0 0x0
+0x98 0x0 0x9a 0x28 0x2 0x25 0x0 0x0 0x0 0x6 0x9a 0x5d 0x0 0x25 0x0 0x0 0x0
+0x99 0x1 0x9a 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0x9b 0x22 0x2 0x2c 0x0 0x0 0x0
+0x9a 0x6 0x9b 0x5d 0x0 0x2c 0x0 0x0 0x0 0x1 0x9b 0x5f 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0x9c 0x18 0x2 0x34 0x0 0x0 0x0 0x6 0x9c 0x5d 0x0 0x34 0x0 0x0 0x0
+0x9c 0x0 0x9d 0xe3 0x1 0x38 0x0 0x0 0x0 0x6 0x9d 0x5d 0x0 0x38 0x0 0x0 0x0
+0x9d 0x1 0x9e 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0x9e 0x2f 0x2 0x50 0x0 0x0 0x0
+0x9e 0x6 0x9e 0x5d 0x0 0x50 0x0 0x0 0x0 0x1 0x9f 0x61 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0x9f 0x23 0x2 0x5a 0x0 0x0 0x0 0x6 0x9f 0x5d 0x0 0x5a 0x0 0x0 0x0
+0xa0 0x0 0xa0 0x1d 0x2 0x6c 0x0 0x0 0x0 0x6 0xa0 0x5d 0x0 0x6c 0x0 0x0 0x0
+0xa1 0x1 0xa0 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xa1 0x15 0x2 0x72 0x0 0x0 0x0
+0xa2 0x6 0xa1 0x5d 0x0 0x72 0x0 0x0 0x0 0x0 0xa2 0xe 0x2 0x7f 0x0 0x0 0x0
+0xa3 0x6 0xa2 0x5d 0x0 0x7f 0x0 0x0 0x0 0x0 0xa3 0x6 0x2 0x88 0x0 0x0 0x0
+0xa4 0x6 0xa3 0x5d 0x0 0x88 0x0 0x0 0x0 0x1 0xa4 0x61 0x0 0x0 0x0 0x0 0x0
+0xa5 0x0 0xa4 0xfd 0x1 0x96 0x0 0x0 0x0 0x6 0xa4 0x5d 0x0 0x96 0x0 0x0 0x0
+0xa6 0x0 0xa5 0xc2 0x1 0x91 0x0 0x0 0x0 0x6 0xa5 0x5d 0x0 0x91 0x0 0x0 0x0
+0xa7 0x0 0xa6 0x0 0x2 0xaf 0x0 0x0 0x0 0x6 0xa6 0x5d 0x0 0xaf 0x0 0x0 0x0
+0xa8 0x0 0xa7 0xe4 0x1 0xb2 0x0 0x0 0x0 0x6 0xa7 0x5d 0x0 0xb2 0x0 0x0 0x0
+0xa9 0x1 0xa7 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xa8 0xc9 0x1 0xb3 0x0 0x0 0x0
+0xaa 0x6 0xa8 0x5d 0x0 0xb3 0x0 0x0 0x0 0x1 0xa9 0x61 0x0 0x0 0x0 0x0 0x0
+0xab 0x0 0xa9 0xaf 0x1 0xb6 0x0 0x2 0x0 0x6 0xa9 0x5d 0x0 0xb6 0x0 0x2 0x0
+0xac 0x0 0xaa 0xa2 0x1 0xbc 0x0 0x0 0x0 0x6 0xaa 0x5d 0x0 0xbc 0x0 0x0 0x0
+0xad 0x0 0xab 0x69 0x1 0xae 0x0 0x0 0x0 0x6 0xab 0x5d 0x0 0xae 0x0 0x0 0x0
+0xae 0x0 0xac 0x93 0x1 0xcf 0x0 0x0 0x0 0x6 0xac 0x5d 0x0 0xcf 0x0 0x0 0x0
+0xaf 0x1 0xac 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xad 0x4c 0x1 0xb8 0x0 0x0 0x0
+0xb0 0x6 0xad 0x5d 0x0 0xb8 0x0 0x0 0x0 0x1 0xad 0x61 0x0 0x0 0x0 0x0 0x0
+0xb1 0x0 0xae 0x43 0x1 0xc0 0x0 0x0 0x0 0x6 0xae 0x5d 0x0 0xc0 0x0 0x0 0x0
+0xb2 0x0 0xaf 0x60 0x1 0xdd 0x0 0x0 0x0 0x6 0xaf 0x5d 0x0 0xdd 0x0 0x0 0x0
+0xb3 0x0 0xb0 0x48 0x1 0xd5 0x0 0x0 0x0 0x6 0xb0 0x5d 0x0 0xd5 0x0 0x0 0x0
+0xb4 0x0 0xb1 0x3f 0x1 0xe0 0x0 0x0 0x0 0x6 0xb1 0x5d 0x0 0xe0 0x0 0x0 0x0
+0xb5 0x0 0xb2 0x1a 0x1 0xd4 0x0 0x0 0x0 0x6 0xb2 0x5d 0x0 0xd4 0x0 0x0 0x0
+0xb6 0x1 0xb2 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0xb3 0x26 0x1 0xe7 0x0 0x0 0x0
+0xb7 0x6 0xb3 0x5d 0x0 0xe7 0x0 0x0 0x0 0x0 0xb4 0x47 0x1 0x4 0x1 0x0 0x0
+0xb8 0x6 0xb4 0x5d 0x0 0x4 0x1 0x0 0x0 0x0 0xb5 0x3c 0x1 0xff 0x0 0x0 0x0
+0xb9 0x6 0xb5 0x5d 0x0 0xff 0x0 0x0 0x0 0x1 0xb6 0x60 0x0 0x0 0x0 0x0 0x0
+0xba 0x0 0xb6 0x2f 0x1 0xf8 0x0 0x0 0x0 0x6 0xb6 0x5d 0x0 0xf8 0x0 0x0 0x0
+0xbb 0x0 0xb7 0x32 0x1 0xf9 0x0 0x0 0x0 0x6 0xb7 0x5d 0x0 0xf9 0x0 0x0 0x0
+0xbc 0x5 0xb7 0xb7 0x0 0xf9 0x0 0x0 0x0 0x1 0xb7 0x61 0x0 0x0 0x0 0x0 0x0
+0xbd 0x0 0xb8 0x32 0x1 0xf2 0x0 0x0 0x0 0x6 0xb8 0x5d 0x0 0xf2 0x0 0x0 0x0
+0xbe 0x0 0xb9 0x25 0x1 0xee 0x0 0x0 0x0 0x6 0xb9 0x5d 0x0 0xee 0x0 0x0 0x0
+0xbf 0x0 0xba 0x14 0x1 0xe3 0x0 0x0 0x0 0x6 0xba 0x5d 0x0 0xe3 0x0 0x0 0x0
+0xc0 0x1 0xbb 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xbb 0x0 0x1 0xd3 0x0 0x0 0x0
+0xc1 0x6 0xbb 0x5d 0x0 0xd3 0x0 0x0 0x0 0x0 0xbc 0xe4 0x0 0xbe 0x0 0x0 0x0
+0xc2 0x6 0xbc 0x5d 0x0 0xbe 0x0 0x0 0x0 0x1 0xbc 0x61 0x0 0x0 0x0 0x0 0x0
+0xc3 0x0 0xbd 0xc7 0x0 0xaa 0x0 0x0 0x0 0x6 0xbd 0x5d 0x0 0xaa 0x0 0x0 0x0
+0xc4 0x1 0xbd 0x62 0x0 0x0 0x0 0x0 0x0 0x0 0xbe 0xa0 0x0 0x8c 0x0 0x0 0x0
+0xc5 0x6 0xbe 0x5d 0x0 0x8c 0x0 0x0 0x0 0x0 0xbf 0xbc 0x0 0xa3 0x0 0x0 0x0
+0xc6 0x6 0xbf 0x5d 0x0 0xa3 0x0 0x0 0x0 0x1 0xc0 0x63 0x0 0x0 0x0 0x0 0x0
+0xc7 0x0 0xc0 0xbf 0x0 0xa4 0x0 0x0 0x0 0x6 0xc0 0x5d 0x0 0xa4 0x0 0x0 0x0
+0xc8 0x0 0xc1 0xbe 0x0 0x9f 0x0 0x0 0x0 0x6 0xc1 0x5d 0x0 0x9f 0x0 0x0 0x0
+0xc9 0x0 0xc2 0xb4 0x0 0x93 0x0 0x0 0x0 0x6 0xc2 0x5d 0x0 0x93 0x0 0x0 0x0
+0xca 0x1 0xc2 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xc3 0xc6 0x0 0xa1 0x0 0x0 0x0
+0xcb 0x6 0xc3 0x5d 0x0 0xa1 0x0 0x0 0x0 0x0 0xc4 0xd0 0x0 0xa6 0x0 0x0 0x0
+0xcc 0x6 0xc4 0x5d 0x0 0xa6 0x0 0x0 0x0 0x1 0xc4 0x5b 0x0 0x0 0x0 0x0 0x0
+0xcd 0x0 0xc5 0xe8 0x0 0xb9 0x0 0x0 0x0 0x6 0xc5 0x5d 0x0 0xb9 0x0 0x0 0x0
+0xce 0x1 0xc6 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0xc6 0xe7 0x0 0xb7 0x0 0x0 0x0
+0xcf 0x6 0xc6 0x5d 0x0 0xb7 0x0 0x0 0x0 0x1 0xc7 0x5f 0x0 0x0 0x0 0x0 0x0
+0xd0 0x0 0xc7 0xe9 0x0 0xba 0x0 0x0 0x0 0x6 0xc7 0x5d 0x0 0xba 0x0 0x0 0x0
+0xd1 0x0 0xc8 0xf5 0x0 0xc6 0x0 0x0 0x0 0x6 0xc8 0x5d 0x0 0xc6 0x0 0x0 0x0
+0xd2 0x0 0xc9 0x2 0x1 0xcf 0x0 0x0 0x0 0x6 0xc9 0x5d 0x0 0xcf 0x0 0x0 0x0
+0xd3 0x1 0xc9 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xca 0x13 0x1 0xdd 0x0 0x0 0x0
+0xd4 0x6 0xca 0x5d 0x0 0xdd 0x0 0x0 0x0 0x1 0xcb 0x5d 0x0 0x0 0x0 0x0 0x0
+0xd5 0x0 0xcb 0x18 0x1 0xdf 0x0 0x0 0x0 0x6 0xcb 0x5d 0x0 0xdf 0x0 0x0 0x0
+0xd6 0x1 0xcc 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0xcc 0x27 0x1 0xea 0x0 0x0 0x0
+0xd7 0x6 0xcc 0x5d 0x0 0xea 0x0 0x0 0x0 0x0 0xcd 0x2a 0x1 0xed 0x0 0x0 0x0
+0xd8 0x6 0xcd 0x5d 0x0 0xed 0x0 0x0 0x0 0x0 0xce 0x35 0x1 0xf3 0x0 0x0 0x0
+0xd9 0x6 0xce 0x5d 0x0 0xf3 0x0 0x0 0x0 0x1 0xce 0x55 0x0 0x0 0x0 0x0 0x0
+0xda 0x0 0xcf 0x44 0x1 0xfe 0x0 0x0 0x0 0x6 0xcf 0x5d 0x0 0xfe 0x0 0x0 0x0
+0xdb 0x1 0xcf 0x44 0x0 0x0 0x0 0x0 0x0 0x0 0xd0 0x4f 0x1 0x9 0x1 0x0 0x0
+0xdc 0x6 0xd0 0x5d 0x0 0x9 0x1 0x0 0x0 0x1 0xd1 0x4a 0x0 0x0 0x0 0x0 0x0
+0xdd 0x0 0xd1 0x37 0x1 0xf2 0x0 0x0 0x0 0x6 0xd1 0x5d 0x0 0xf2 0x0 0x0 0x0
+0xde 0x0 0xd2 0x71 0x1 0x1e 0x1 0x0 0x0 0x6 0xd2 0x5d 0x0 0x1e 0x1 0x0 0x0
+0xdf 0x0 0xd3 0x42 0x1 0xf8 0x0 0xfe 0xff 0x6 0xd3 0x5d 0x0 0xf8 0x0 0xfe 0xff
+0xe0 0x1 0xd3 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xd4 0x59 0x1 0x8 0x1 0x0 0x0
+0xe1 0x6 0xd4 0x5d 0x0 0x8 0x1 0x0 0x0 0x1 0xd4 0x48 0x0 0x0 0x0 0x0 0x0
+0xe2 0x0 0xd5 0x75 0x1 0x22 0x1 0x0 0x0 0x6 0xd5 0x5d 0x0 0x22 0x1 0x0 0x0
+0xe3 0x1 0xd5 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0xd6 0x64 0x1 0x10 0x1 0x0 0x0
+0xe4 0x6 0xd6 0x5d 0x0 0x10 0x1 0x0 0x0 0x0 0xd7 0x38 0x1 0xec 0x0 0xff 0xff
+0xe5 0x6 0xd7 0x5d 0x0 0xec 0x0 0xff 0xff 0x1 0xd8 0x48 0x0 0x0 0x0 0x0 0x0
+0xe6 0x0 0xd8 0x66 0x1 0x14 0x1 0xff 0xff 0x6 0xd8 0x5d 0x0 0x14 0x1 0xff 0xff
+0xe7 0x0 0xd9 0x58 0x1 0xc 0x1 0x0 0x0 0x6 0xd9 0x5d 0x0 0xc 0x1 0x0 0x0
+0xe8 0x1 0xd9 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xda 0x3b 0x1 0xf6 0x0 0x0 0x0
+0xe9 0x6 0xda 0x5d 0x0 0xf6 0x0 0x0 0x0 0x0 0xdb 0x53 0x1 0xa 0x1 0x0 0x0
+0xea 0x6 0xdb 0x5d 0x0 0xa 0x1 0x0 0x0 0x0 0xdc 0x38 0x1 0xf5 0x0 0x0 0x0
+0xeb 0x6 0xdc 0x5d 0x0 0xf5 0x0 0x0 0x0 0x4 0xdd 0x5a 0x0 0x15 0x0 0x0 0x0
+0xec 0x1 0xdd 0x4a 0x0 0x0 0x0 0x0 0x0 0x0 0xdd 0x7 0x1 0xd5 0x0 0x0 0x0
+0xed 0x6 0xdd 0x5d 0x0 0xd5 0x0 0x0 0x0 0x0 0xde 0x11 0x1 0xdb 0x0 0xfe 0xff
+0xee 0x6 0xde 0x5d 0x0 0xdb 0x0 0xfe 0xff 0x1 0xde 0x4b 0x0 0x0 0x0 0x0 0x0
+0xef 0x0 0xdf 0x7 0x1 0xd1 0x0 0x0 0x0 0x6 0xdf 0x5d 0x0 0xd1 0x0 0x0 0x0
+0xf0 0x1 0xdf 0x4c 0x0 0x0 0x0 0x0 0x0 0x0 0xe0 0xc 0x1 0xd4 0x0 0x0 0x0
+0xf1 0x6 0xe0 0x5d 0x0 0xd4 0x0 0x0 0x0 0x0 0xe1 0xd6 0x0 0xa9 0x0 0x0 0x0
+0xf2 0x6 0xe1 0x5d 0x0 0xa9 0x0 0x0 0x0 0x1 0xe2 0x4d 0x0 0x0 0x0 0x0 0x0
+0xf3 0x0 0xe2 0xdb 0x0 0xac 0x0 0x0 0x0 0x6 0xe2 0x5d 0x0 0xac 0x0 0x0 0x0
+0xf4 0x0 0xe4 0x2d 0x1 0xad 0x0 0x0 0x0 0x6 0xe4 0x5d 0x0 0xad 0x0 0x0 0x0
+0xf5 0x1 0xe4 0x4e 0x0 0x0 0x0 0x0 0x0 0x0 0xe6 0xf3 0x0 0x8b 0x0 0x0 0x0
+0xf6 0x6 0xe6 0x5d 0x0 0x8b 0x0 0x0 0x0 0x1 0xe6 0x4f 0x0 0x0 0x0 0x0 0x0
+0xf7 0x1 0xe8 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xeb 0x52 0x0 0x0 0x0 0x0 0x0
+0xf8 0x1 0xee 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0xf0 0xf7 0x0 0x87 0x0 0x0 0x0
+0xf9 0x6 0xf0 0x74 0x0 0x87 0x0 0x0 0x0 0x1 0xf0 0x54 0x0 0x0 0x0 0x0 0x0
+0xfa 0x0 0xf2 0xd8 0x0 0x7f 0x0 0x0 0x0 0x6 0xf2 0x8c 0x0 0x7f 0x0 0x0 0x0
+0xfb 0x1 0xf2 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xf4 0xcb 0x0 0x7c 0x0 0x0 0x0
+0xfc 0x6 0xf4 0x8c 0x0 0x7c 0x0 0x0 0x0 0x1 0xf5 0x56 0x0 0x0 0x0 0x0 0x0
+0xfd 0x0 0xf6 0x1c 0x1 0x75 0x0 0x0 0x0 0x6 0xf6 0xa1 0x0 0x75 0x0 0x0 0x0
+0xfe 0x1 0xf6 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xf7 0x57 0x0 0x0 0x0 0x0 0x0
+0xff 0x0 0xf8 0x67 0x1 0x3a 0x0 0x2 0x0 0x6 0xf8 0xa1 0x0 0x3a 0x0 0x2 0x0
+0x0 0x0 0xf9 0x24 0x1 0x2 0x0 0x0 0x0 0x6 0xf9 0xa1 0x0 0x2 0x0 0x0 0x0
+0x1 0x1 0xfa 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xfa 0x19 0x1 0xd6 0xff 0x0 0x0
+0x2 0x6 0xfa 0xa1 0x0 0xd6 0xff 0x0 0x0 0x0 0xfb 0x1e 0x1 0xb9 0xff 0x0 0x0
+0x3 0x6 0xfb 0xa1 0x0 0xb9 0xff 0x0 0x0 0x0 0xfc 0x22 0x1 0xa6 0xff 0x0 0x0
+0x4 0x6 0xfc 0xa1 0x0 0xa6 0xff 0x0 0x0 0x1 0xfc 0x59 0x0 0x0 0x0 0x0 0x0
+0x5 0x0 0xfd 0x68 0x1 0x47 0xff 0x0 0x0 0x6 0xfd 0xa1 0x0 0x47 0xff 0x0 0x0
+0x6 0x0 0xfe 0x3e 0x1 0x58 0xff 0x0 0x0 0x6 0xfe 0xa1 0x0 0x58 0xff 0x0 0x0
+0x7 0x0 0xff 0x32 0x1 0x5f 0xff 0x0 0x0 0x6 0xff 0xa1 0x0 0x5f 0xff 0x0 0x0
+0x8 0x0 0x0 0x19 0x1 0x67 0xff 0x0 0x0 0x6 0x0 0xa1 0x0 0x67 0xff 0x0 0x0
+0x9 0x0 0x1 0xff 0x0 0x7f 0xff 0x0 0x0 0x6 0x1 0xa1 0x0 0x7f 0xff 0x0 0x0
+0xa 0x1 0x1 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x2 0xf5 0x0 0x92 0xff 0x0 0x0
+0xb 0x6 0x2 0xa1 0x0 0x92 0xff 0x0 0x0 0x1 0x3 0x57 0x0 0x0 0x0 0x0 0x0
+0xc 0x0 0x4 0xe1 0x0 0x9f 0xff 0xfe 0xff 0x6 0x4 0xa1 0x0 0x9f 0xff 0xfe 0xff
+0xd 0x1 0x5 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xb 0x59 0x0 0x0 0x0 0x0 0x0
+0xe 0x0 0xc 0xa3 0x0 0x91 0xff 0x0 0x0 0x6 0xc 0xb8 0x0 0x91 0xff 0x0 0x0
+0xf 0x1 0xd 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xe 0x60 0x0 0x0 0x0 0x0 0x0
+0x10 0x1 0x10 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x13 0x68 0x0 0x0 0x0 0x0 0x0
+0x11 0x1 0x14 0x6d 0x0 0x0 0x0 0x0 0x0 0x1 0x17 0x6c 0x0 0x0 0x0 0x0 0x0
+0x12 0x1 0x18 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0x19 0x6a 0x0 0x0 0x0 0x0 0x0
+0x13 0x0 0x1b 0xe5 0x0 0xa0 0xff 0x0 0x0 0x6 0x1b 0x54 0x1 0xa0 0xff 0x0 0x0
+0x14 0x1 0x1c 0x6c 0x0 0x0 0x0 0x0 0x0 0x0 0x1d 0x12 0x1 0xa6 0xff 0x0 0x0
+0x15 0x6 0x1d 0x54 0x1 0xa6 0xff 0x0 0x0 0x1 0x1d 0x6d 0x0 0x0 0x0 0x0 0x0
+0x16 0x0 0x1e 0x10 0x1 0xaa 0xff 0x0 0x0 0x6 0x1e 0x6b 0x1 0xaa 0xff 0x0 0x0
+0x17 0x1 0x1e 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0x1f 0x23 0x1 0xb2 0xff 0x0 0x0
+0x18 0x6 0x1f 0x6b 0x1 0xb2 0xff 0x0 0x0 0x0 0x20 0x46 0x1 0xac 0xff 0x0 0x0
+0x19 0x6 0x20 0x6b 0x1 0xac 0xff 0x0 0x0 0x1 0x21 0x6b 0x0 0x0 0x0 0x0 0x0
+0x1a 0x0 0x21 0x66 0x1 0xa7 0xff 0x0 0x0 0x6 0x21 0x4c 0x1 0xa7 0xff 0x0 0x0
+0x1b 0x1 0x22 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x22 0x8f 0x1 0x9f 0xff 0x0 0x0
+0x1c 0x6 0x22 0x36 0x1 0x9f 0xff 0x0 0x0 0x0 0x23 0xdf 0x1 0x8b 0xff 0x0 0x0
+0x1d 0x6 0x23 0x36 0x1 0x8b 0xff 0x0 0x0 0x1 0x23 0x70 0x0 0x0 0x0 0x0 0x0
+0x1e 0x0 0x24 0x92 0x1 0x9d 0xff 0xff 0xff 0x6 0x24 0x19 0x1 0x9d 0xff 0xff 0xff
+0x1f 0x0 0x25 0x82 0x1 0xa1 0xff 0x0 0x0 0x6 0x25 0x2 0x1 0xa1 0xff 0x0 0x0
+0x20 0x1 0x25 0x6a 0x0 0x0 0x0 0x0 0x0 0x0 0x26 0x2a 0x1 0xb7 0xff 0x0 0x0
+0x21 0x6 0x26 0xef 0x0 0xb7 0xff 0x0 0x0 0x1 0x27 0x67 0x0 0x0 0x0 0x0 0x0
+0x22 0x0 0x27 0x6 0x1 0xbf 0xff 0x0 0x0 0x6 0x27 0xef 0x0 0xbf 0xff 0x0 0x0
+0x23 0x1 0x28 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x29 0x1b 0x1 0xb9 0xff 0x0 0x0
+0x24 0x6 0x29 0xd8 0x0 0xb9 0xff 0x0 0x0 0x1 0x2a 0x74 0x0 0x0 0x0 0x0 0x0
+0x25 0x1 0x2b 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x2c 0xf9 0x0 0xbc 0xff 0x0 0x0
+0x26 0x6 0x2c 0xd8 0x0 0xbc 0xff 0x0 0x0 0x1 0x2d 0x72 0x0 0x0 0x0 0x0 0x0
+0x27 0x1 0x2f 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x30 0xa5 0x0 0x86 0xff 0x0 0x0
+0x28 0x6 0x30 0xeb 0x0 0x86 0xff 0x0 0x0 0x1 0x30 0x64 0x0 0x0 0x0 0x0 0x0
+0x29 0x1 0x32 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x34 0x5c 0x0 0x0 0x0 0x0 0x0
+0x2a 0x1 0x35 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0x36 0x54 0x0 0x0 0x0 0x0 0x0
+0x2b 0x0 0x38 0x78 0x0 0x7b 0xff 0x0 0x0 0x6 0x38 0x30 0x1 0x7b 0xff 0x0 0x0
+0x2c 0x1 0x39 0x4d 0x0 0x0 0x0 0x0 0x0 0x1 0x3a 0x4c 0x0 0x0 0x0 0x0 0x0
+0x2d 0x1 0x3b 0x45 0x0 0x0 0x0 0x0 0x0 0x1 0x3e 0x47 0x0 0x0 0x0 0x0 0x0
+0x2e 0x0 0x3f 0xbe 0x0 0xbb 0xff 0x0 0x0 0x6 0x3f 0x64 0x1 0xbb 0xff 0x0 0x0
+0x2f 0x1 0x3f 0x46 0x0 0x0 0x0 0x0 0x0 0x1 0x40 0x45 0x0 0x0 0x0 0x0 0x0
+0x30 0x0 0x41 0xd4 0x0 0xc4 0xff 0x0 0x0 0x6 0x41 0x81 0x1 0xc4 0xff 0x0 0x0
+0x31 0x1 0x43 0x44 0x0 0x0 0x0 0x0 0x0 0x0 0x43 0x37 0x1 0xb0 0xff 0x0 0x0
+0x32 0x6 0x43 0x97 0x1 0xb0 0xff 0x0 0x0 0x1 0x44 0x46 0x0 0x0 0x0 0x0 0x0
+0x33 0x0 0x44 0x39 0x1 0xab 0xff 0x0 0x0 0x6 0x44 0x97 0x1 0xab 0xff 0x0 0x0
+0x34 0x0 0x45 0x53 0x1 0x9f 0xff 0x0 0x0 0x6 0x45 0x97 0x1 0x9f 0xff 0x0 0x0
+0x35 0x1 0x45 0x48 0x0 0x0 0x0 0x0 0x0 0x0 0x46 0x74 0x1 0x89 0xff 0x0 0x0
+0x36 0x6 0x46 0x7b 0x1 0x89 0xff 0x0 0x0 0x0 0x47 0x6c 0x1 0x90 0xff 0x0 0x0
+0x37 0x6 0x47 0x5c 0x1 0x90 0xff 0x0 0x0 0x1 0x48 0x47 0x0 0x0 0x0 0x0 0x0
+0x38 0x0 0x48 0x4e 0x1 0x99 0xff 0x0 0x0 0x6 0x48 0x3f 0x1 0x99 0xff 0x0 0x0
+0x39 0x0 0x49 0x6d 0x1 0x95 0xff 0x0 0x0 0x6 0x49 0x25 0x1 0x95 0xff 0x0 0x0
+0x3a 0x1 0x4a 0x48 0x0 0x0 0x0 0x0 0x0 0x1 0x4c 0x49 0x0 0x0 0x0 0x0 0x0
+0x3b 0x1 0x4e 0x4a 0x0 0x0 0x0 0x0 0x0 0x0 0x4e 0x4a 0x6 0xc3 0xfd 0x0 0x0
+0x3c 0x6 0x4e 0xdf 0x0 0xc3 0xfd 0x0 0x0 0x1 0x4f 0x4b 0x0 0x0 0x0 0x0 0x0
+0x3d 0x0 0x4f 0x62 0x4 0x58 0xfe 0x0 0x0 0x6 0x4f 0xdf 0x0 0x58 0xfe 0x0 0x0
+0x3e 0x0 0x50 0x44 0x3 0xaa 0xfe 0x0 0x0 0x6 0x50 0xdf 0x0 0xaa 0xfe 0x0 0x0
+0x3f 0x0 0x51 0x95 0x2 0xe2 0xfe 0x0 0x0 0x6 0x51 0xc1 0x0 0xe2 0xfe 0x0 0x0
+0x40 0x1 0x51 0x4c 0x0 0x0 0x0 0x0 0x0 0x0 0x52 0x24 0x2 0x8 0xff 0x0 0x0
+0x41 0x6 0x52 0xc1 0x0 0x8 0xff 0x0 0x0 0x1 0x52 0x4d 0x0 0x0 0x0 0x0 0x0
+0x42 0x0 0x53 0xcd 0x1 0x2c 0xff 0x0 0x0 0x6 0x53 0xa4 0x0 0x2c 0xff 0x0 0x0
+0x43 0x0 0x54 0x96 0x1 0x4b 0xff 0x0 0x0 0x6 0x54 0xa4 0x0 0x4b 0xff 0x0 0x0
+0x44 0x0 0x55 0x66 0x1 0x5d 0xff 0x0 0x0 0x6 0x55 0xa4 0x0 0x5d 0xff 0x0 0x0
+0x45 0x0 0x56 0x44 0x1 0x6e 0xff 0x0 0x0 0x6 0x56 0xa4 0x0 0x6e 0xff 0x0 0x0
+0x46 0x0 0x57 0x1c 0x1 0x7b 0xff 0x1 0x0 0x6 0x57 0x90 0x0 0x7b 0xff 0x1 0x0
+0x47 0x1 0x57 0x4e 0x0 0x0 0x0 0x0 0x0 0x0 0x58 0xfc 0x0 0x8a 0xff 0x0 0x0
+0x48 0x6 0x58 0x90 0x0 0x8a 0xff 0x0 0x0 0x1 0x59 0x4f 0x0 0x0 0x0 0x0 0x0
+0x49 0x0 0x59 0xe2 0x0 0x97 0xff 0x0 0x0 0x6 0x59 0x90 0x0 0x97 0xff 0x0 0x0
+0x4a 0x0 0x5a 0xd8 0x0 0x9d 0xff 0x0 0x0 0x6 0x5a 0x90 0x0 0x9d 0xff 0x0 0x0
+0x4b 0x0 0x5b 0xc6 0x0 0xa5 0xff 0x0 0x0 0x6 0x5b 0x90 0x0 0xa5 0xff 0x0 0x0
+0x4c 0x1 0x5b 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x5c 0x57 0x0 0x0 0x0 0x0 0x0
+0x4d 0x0 0x5d 0xef 0x0 0x92 0xff 0x0 0x0 0x6 0x5d 0x90 0x0 0x92 0xff 0x0 0x0
+0x4e 0x1 0x5d 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x60 0x4f 0x0 0x0 0x0 0x0 0x0
+0x4f 0x0 0x61 0xdb 0x0 0x9d 0xff 0x0 0x0 0x6 0x61 0x90 0x0 0x9d 0xff 0x0 0x0
+0x50 0x1 0x61 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0x62 0x48 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x65 0x41 0x0 0x0 0x0 0x0 0x0 0x1 0x66 0x43 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x67 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0x6a 0x4b 0x0 0x0 0x0 0x0 0x0
+0x53 0x1 0x6b 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0x6c 0x47 0x0 0x0 0x0 0x0 0x0
+0x54 0x1 0x6e 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0x70 0x51 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x71 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x72 0xc5 0x0 0x99 0xff 0x1 0x0
+0x56 0x6 0x72 0x13 0x1 0x99 0xff 0x1 0x0 0x0 0x74 0xec 0x0 0xc2 0xff 0x1 0x0
+0x57 0x6 0x74 0x2f 0x1 0xc2 0xff 0x1 0x0 0x1 0x74 0x50 0x0 0x0 0x0 0x0 0x0
+0x58 0x1 0x76 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x76 0x2d 0x1 0x2f 0x0 0x0 0x0
+0x59 0x6 0x76 0x44 0x1 0x2f 0x0 0x0 0x0 0x0 0x77 0xeb 0x0 0x65 0x0 0x0 0x0
+0x5a 0x6 0x77 0x44 0x1 0x65 0x0 0x0 0x0 0x0 0x78 0xe9 0x0 0x67 0x0 0x0 0x0
+0x5b 0x6 0x78 0x44 0x1 0x67 0x0 0x0 0x0 0x1 0x78 0x51 0x0 0x0 0x0 0x0 0x0
+0x5c 0x0 0x79 0xdb 0x0 0xae 0x0 0x0 0x0 0x6 0x79 0x44 0x1 0xae 0x0 0x0 0x0
+0x5d 0x1 0x79 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x7a 0xe5 0x0 0xf4 0x0 0x2 0x0
+0x5e 0x6 0x7a 0x26 0x1 0xf4 0x0 0x2 0x0 0x0 0x7b 0xe3 0x0 0xfc 0x0 0x0 0x0
+0x5f 0x6 0x7b 0x13 0x1 0xfc 0x0 0x0 0x0 0x0 0x7c 0xc2 0x0 0xd 0x1 0x0 0x0
+0x60 0x6 0x7c 0xff 0x0 0xd 0x1 0x0 0x0 0x0 0x7d 0xc2 0x0 0x4c 0x1 0x0 0x0
+0x61 0x6 0x7d 0xff 0x0 0x4c 0x1 0x0 0x0 0x1 0x7d 0x4f 0x0 0x0 0x0 0x0 0x0
+0x62 0x0 0x7e 0x96 0x0 0x43 0x1 0x0 0x0 0x6 0x7e 0xe7 0x0 0x43 0x1 0x0 0x0
+0x63 0x1 0x7e 0x52 0x0 0x0 0x0 0x0 0x0 0x0 0x7f 0x48 0x0 0x5d 0x1 0x0 0x0
+0x64 0x6 0x7f 0xd4 0x0 0x5d 0x1 0x0 0x0 0x1 0x7f 0x55 0x0 0x0 0x0 0x0 0x0
+0x65 0x0 0x80 0xe8 0xff 0x63 0x1 0x0 0x0 0x6 0x80 0xd4 0x0 0x63 0x1 0x0 0x0
+0x66 0x0 0x81 0x9a 0xff 0x7c 0x1 0x0 0x0 0x6 0x81 0xb5 0x0 0x7c 0x1 0x0 0x0
+0x67 0x1 0x82 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x82 0x69 0xff 0x60 0x1 0x0 0x0
+0x68 0x6 0x82 0xb5 0x0 0x60 0x1 0x0 0x0 0x0 0x83 0x67 0xff 0x2b 0x1 0x0 0x0
+0x69 0x6 0x83 0xb5 0x0 0x2b 0x1 0x0 0x0 0x1 0x83 0x57 0x0 0x0 0x0 0x0 0x0
+0x6a 0x0 0x84 0x5f 0xff 0x1f 0x1 0x0 0x0 0x6 0x84 0x9f 0x0 0x1f 0x1 0x0 0x0
+0x6b 0x1 0x84 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x85 0x62 0xff 0xf 0x1 0x0 0x0
+0x6c 0x6 0x85 0x9f 0x0 0xf 0x1 0x0 0x0 0x0 0x86 0x6a 0xff 0xf8 0x0 0x0 0x0
+0x6d 0x6 0x86 0x9f 0x0 0xf8 0x0 0x0 0x0 0x1 0x87 0x51 0x0 0x0 0x0 0x0 0x0
+0x6e 0x0 0x87 0xd6 0xfe 0xca 0x1 0x0 0x0 0x6 0x87 0x9f 0x0 0xca 0x1 0x0 0x0
+0x6f 0x1 0x88 0x4b 0x0 0x0 0x0 0x0 0x0 0x0 0x88 0xba 0xff 0x7d 0x0 0x0 0x0
+0x70 0x6 0x88 0x8c 0x0 0x7d 0x0 0x0 0x0 0x0 0x89 0xc1 0xff 0x96 0x0 0x0 0x0
+0x71 0x6 0x89 0x8c 0x0 0x96 0x0 0x0 0x0 0x1 0x89 0x4c 0x0 0x0 0x0 0x0 0x0
+0x72 0x0 0x8a 0xc5 0xff 0xb1 0x0 0x0 0x0 0x6 0x8a 0x8c 0x0 0xb1 0x0 0x0 0x0
+0x73 0x0 0x8b 0xcf 0xff 0xbf 0x0 0x0 0x0 0x6 0x8b 0x8c 0x0 0xbf 0x0 0x0 0x0
+0x74 0x0 0x8c 0xda 0xff 0xce 0x0 0x0 0x0 0x6 0x8c 0x8c 0x0 0xce 0x0 0x0 0x0
+0x75 0x1 0x8d 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0xd6 0xff 0xd0 0x0 0x0 0x0
+0x76 0x6 0x8d 0x8c 0x0 0xd0 0x0 0x0 0x0 0x0 0x8e 0xd8 0xff 0xd6 0x0 0x0 0x0
+0x77 0x6 0x8e 0x8c 0x0 0xd6 0x0 0x0 0x0 0x1 0x8e 0x43 0x0 0x0 0x0 0x0 0x0
+0x78 0x0 0x8f 0xe7 0xff 0xe3 0x0 0x0 0x0 0x6 0x8f 0x8c 0x0 0xe3 0x0 0x0 0x0
+0x79 0x0 0x90 0xfe 0xff 0xee 0x0 0x1 0x0 0x6 0x90 0x8c 0x0 0xee 0x0 0x1 0x0
+0x7a 0x1 0x90 0x41 0x0 0x0 0x0 0x0 0x0 0x0 0x91 0x10 0x0 0xf9 0x0 0x0 0x0
+0x7b 0x6 0x91 0x8c 0x0 0xf9 0x0 0x0 0x0 0x1 0x92 0x3f 0x0 0x0 0x0 0x0 0x0
+0x7c 0x0 0x92 0x17 0x0 0xe 0x1 0x0 0x0 0x6 0x92 0x8c 0x0 0xe 0x1 0x0 0x0
+0x7d 0x1 0x93 0x45 0x0 0x0 0x0 0x0 0x0 0x0 0x93 0x1b 0x0 0x22 0x1 0x1 0x0
+0x7e 0x6 0x93 0x8c 0x0 0x22 0x1 0x1 0x0 0x0 0x94 0x24 0x0 0x54 0x1 0x0 0x0
+0x7f 0x6 0x94 0x8c 0x0 0x54 0x1 0x0 0x0 0x0 0x95 0x1c 0x0 0x33 0x1 0x0 0x0
+0x80 0x6 0x95 0x8c 0x0 0x33 0x1 0x0 0x0 0x1 0x95 0x48 0x0 0x0 0x0 0x0 0x0
+0x81 0x0 0x96 0x29 0x0 0x49 0x1 0x0 0x0 0x6 0x96 0x8c 0x0 0x49 0x1 0x0 0x0
+0x82 0x1 0x96 0x47 0x0 0x0 0x0 0x0 0x0 0x0 0x97 0x2e 0x0 0x56 0x1 0x0 0x0
+0x83 0x6 0x97 0x8c 0x0 0x56 0x1 0x0 0x0 0x1 0x98 0x4e 0x0 0x0 0x0 0x0 0x0
+0x84 0x0 0x98 0x2e 0x0 0x5f 0x1 0x0 0x0 0x6 0x98 0x77 0x0 0x5f 0x1 0x0 0x0
+0x85 0x0 0x99 0x2a 0x0 0x86 0x1 0x0 0x0 0x6 0x99 0x77 0x0 0x86 0x1 0x0 0x0
+0x86 0x4 0x99 0xbc 0x0 0x86 0x1 0x0 0x0 0x0 0x9a 0x21 0x0 0x5c 0x1 0x0 0x0
+0x87 0x6 0x9a 0x77 0x0 0x5c 0x1 0x0 0x0 0x1 0x9a 0x4f 0x0 0x0 0x0 0x0 0x0
+0x88 0x0 0x9b 0x26 0x0 0x6e 0x1 0x0 0x0 0x6 0x9b 0x77 0x0 0x6e 0x1 0x0 0x0
+0x89 0x1 0x9b 0x50 0x0 0x0 0x0 0x0 0x0 0x0 0x9c 0x2f 0x0 0x7d 0x1 0x0 0x0
+0x8a 0x6 0x9c 0x77 0x0 0x7d 0x1 0x0 0x0 0x1 0x9d 0x52 0x0 0x0 0x0 0x0 0x0
+0x8b 0x0 0x9d 0x43 0x0 0x7f 0x1 0x0 0x0 0x6 0x9d 0x77 0x0 0x7f 0x1 0x0 0x0
+0x8c 0x0 0x9e 0x92 0x0 0x82 0x1 0x0 0x0 0x6 0x9e 0x77 0x0 0x82 0x1 0x0 0x0
+0x8d 0x1 0x9f 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x9f 0xba 0x0 0x87 0x1 0x0 0x0
+0x8e 0x6 0x9f 0x77 0x0 0x87 0x1 0x0 0x0 0x0 0xa0 0xe4 0x0 0xae 0x1 0x0 0x0
+0x8f 0x6 0xa0 0x77 0x0 0xae 0x1 0x0 0x0 0x1 0xa0 0x54 0x0 0x0 0x0 0x0 0x0
+0x90 0x0 0xa1 0xd6 0x0 0x77 0x1 0x0 0x0 0x6 0xa1 0x63 0x0 0x77 0x1 0x0 0x0
+0x91 0x1 0xa1 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xa2 0xe5 0x0 0x81 0x1 0x0 0x0
+0x92 0x6 0xa2 0x63 0x0 0x81 0x1 0x0 0x0 0x0 0xa3 0xeb 0x0 0x89 0x1 0x0 0x0
+0x93 0x6 0xa3 0x63 0x0 0x89 0x1 0x0 0x0 0x1 0xa4 0x54 0x0 0x0 0x0 0x0 0x0
+0x94 0x0 0xa4 0xf2 0x0 0x91 0x1 0x0 0x0 0x6 0xa4 0x63 0x0 0x91 0x1 0x0 0x0
+0x95 0x0 0xa5 0xef 0x0 0x98 0x1 0x1 0x0 0x6 0xa5 0x63 0x0 0x98 0x1 0x1 0x0
+0x96 0x1 0xa5 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xa6 0xef 0x0 0x99 0x1 0x0 0x0
+0x97 0x6 0xa6 0x63 0x0 0x99 0x1 0x0 0x0 0x1 0xa6 0x54 0x0 0x0 0x0 0x0 0x0
+0x98 0x0 0xa7 0xed 0x0 0x9a 0x1 0x0 0x0 0x6 0xa7 0x63 0x0 0x9a 0x1 0x0 0x0
+0x99 0x5 0xa7 0xf0 0x0 0x9a 0x1 0x0 0x0 0x0 0xa8 0xec 0x0 0x9a 0x1 0x0 0x0
+0x9a 0x6 0xa8 0x63 0x0 0x9a 0x1 0x0 0x0 0x1 0xa9 0x4d 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0xa9 0xa 0x1 0xc2 0x1 0x0 0x0 0x6 0xa9 0x63 0x0 0xc2 0x1 0x0 0x0
+0x9c 0x1 0xaa 0x49 0x0 0x0 0x0 0x0 0x0 0x0 0xaa 0xeb 0x0 0x86 0x1 0x0 0x0
+0x9d 0x6 0xaa 0x63 0x0 0x86 0x1 0x0 0x0 0x0 0xab 0x9 0x1 0xb5 0x1 0x0 0x0
+0x9e 0x6 0xab 0x63 0x0 0xb5 0x1 0x0 0x0 0x1 0xab 0x4a 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0xac 0xe9 0x0 0x7d 0x1 0x0 0x0 0x6 0xac 0x63 0x0 0x7d 0x1 0x0 0x0
+0xa0 0x0 0xad 0xf1 0x0 0x89 0x1 0x0 0x0 0x6 0xad 0x50 0x0 0x89 0x1 0x0 0x0
+0xa1 0x1 0xad 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0xae 0xf1 0x0 0x8f 0x1 0x0 0x0
+0xa2 0x6 0xae 0x50 0x0 0x8f 0x1 0x0 0x0 0x1 0xaf 0x52 0x0 0x0 0x0 0x0 0x0
+0xa3 0x0 0xaf 0x7 0x1 0xb8 0x1 0x0 0x0 0x6 0xaf 0x50 0x0 0xb8 0x1 0x0 0x0
+0xa4 0x0 0xb0 0xf7 0x0 0xa1 0x1 0x0 0x0 0x6 0xb0 0x50 0x0 0xa1 0x1 0x0 0x0
+0xa5 0x1 0xb0 0x51 0x0 0x0 0x0 0x0 0x0 0x0 0xb1 0xd5 0x0 0x6c 0x1 0x0 0x0
+0xa6 0x6 0xb1 0x50 0x0 0x6c 0x1 0x0 0x0 0x0 0xb2 0xf4 0x0 0x9e 0x1 0x0 0x0
+0xa7 0x6 0xb2 0x50 0x0 0x9e 0x1 0x0 0x0 0x1 0xb2 0x54 0x0 0x0 0x0 0x0 0x0
+0xa8 0x0 0xb3 0xed 0x0 0x92 0x1 0x0 0x0 0x6 0xb3 0x50 0x0 0x92 0x1 0x0 0x0
+0xa9 0x1 0xb3 0x57 0x0 0x0 0x0 0x0 0x0 0x0 0xb4 0xd5 0x0 0x69 0x1 0x0 0x0
+0xaa 0x6 0xb4 0x50 0x0 0x69 0x1 0x0 0x0 0x1 0xb5 0x56 0x0 0x0 0x0 0x0 0x0
+0xab 0x0 0xb5 0xe2 0x0 0x7f 0x1 0x0 0x0 0x6 0xb5 0x50 0x0 0x7f 0x1 0x0 0x0
+0xac 0x0 0xb6 0xde 0x0 0x80 0x1 0x0 0x0 0x6 0xb6 0x50 0x0 0x80 0x1 0x0 0x0
+0xad 0x0 0xb7 0xe1 0x0 0x83 0x1 0x0 0x0 0x6 0xb7 0x50 0x0 0x83 0x1 0x0 0x0
+0xae 0x0 0xb8 0xdd 0x0 0x7b 0x1 0x0 0x0 0x6 0xb8 0x50 0x0 0x7b 0x1 0x0 0x0
+0xaf 0x1 0xb8 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0xb9 0xf2 0x0 0x9c 0x1 0x0 0x0
+0xb0 0x6 0xb9 0x50 0x0 0x9c 0x1 0x0 0x0 0x1 0xba 0x56 0x0 0x0 0x0 0x0 0x0
+0xb1 0x0 0xba 0xd6 0x0 0x61 0x1 0x0 0x0 0x6 0xba 0x50 0x0 0x61 0x1 0x0 0x0
+0xb2 0x0 0xbb 0xf8 0x0 0x94 0x1 0x0 0x0 0x6 0xbb 0x50 0x0 0x94 0x1 0x0 0x0
+0xb3 0x0 0xbc 0xd7 0x0 0x60 0x1 0x0 0x0 0x6 0xbc 0x50 0x0 0x60 0x1 0x0 0x0
+0xb4 0x0 0xbd 0xfb 0x0 0x96 0x1 0x0 0x0 0x6 0xbd 0x50 0x0 0x96 0x1 0x0 0x0
+0xb5 0x0 0xbe 0xf2 0x0 0x8c 0x1 0x0 0x0 0x6 0xbe 0x50 0x0 0x8c 0x1 0x0 0x0
+0xb6 0x1 0xbe 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xbf 0xd6 0x0 0x61 0x1 0x0 0x0
+0xb7 0x6 0xbf 0x50 0x0 0x61 0x1 0x0 0x0 0x0 0xc0 0xe0 0x0 0x73 0x1 0x0 0x0
+0xb8 0x6 0xc0 0x50 0x0 0x73 0x1 0x0 0x0 0x0 0xc1 0xe1 0x0 0x78 0x1 0x0 0x0
+0xb9 0x6 0xc1 0x50 0x0 0x78 0x1 0x0 0x0 0x0 0xc2 0xe1 0x0 0x77 0x1 0x0 0x0
+0xba 0x6 0xc2 0x50 0x0 0x77 0x1 0x0 0x0 0x1 0xc2 0x5a 0x0 0x0 0x0 0x0 0x0
+0xbb 0x0 0xc3 0xe0 0x0 0x75 0x1 0x0 0x0 0x6 0xc3 0x50 0x0 0x75 0x1 0x0 0x0
+0xbc 0x1 0xc3 0x5b 0x0 0x0 0x0 0x0 0x0 0x0 0xc4 0xe3 0x0 0x7d 0x1 0x0 0x0
+0xbd 0x6 0xc4 0x50 0x0 0x7d 0x1 0x0 0x0 0x0 0xc5 0xe5 0x0 0x7f 0x1 0x0 0x0
+0xbe 0x6 0xc5 0x50 0x0 0x7f 0x1 0x0 0x0 0x1 0xc6 0x5c 0x0 0x0 0x0 0x0 0x0
+0xbf 0x0 0xc6 0xe4 0x0 0x7e 0x1 0x0 0x0 0x6 0xc6 0x50 0x0 0x7e 0x1 0x0 0x0
+0xc0 0x0 0xc7 0xe5 0x0 0x7f 0x1 0x0 0x0 0x6 0xc7 0x50 0x0 0x7f 0x1 0x0 0x0
+0xc1 0x1 0xc7 0x5d 0x0 0x0 0x0 0x0 0x0 0x0 0xc8 0xfb 0x0 0xa6 0x1 0x0 0x0
+0xc2 0x6 0xc8 0x50 0x0 0xa6 0x1 0x0 0x0 0x1 0xc8 0x5e 0x0 0x0 0x0 0x0 0x0
+0xc3 0x0 0xc9 0xd0 0x0 0x6e 0x1 0x0 0x0 0x6 0xc9 0x50 0x0 0x6e 0x1 0x0 0x0
+0xc4 0x0 0xca 0xdc 0x0 0x7a 0x1 0x0 0x0 0x6 0xca 0x50 0x0 0x7a 0x1 0x0 0x0
+0xc5 0x1 0xcb 0x5f 0x0 0x0 0x0 0x0 0x0 0x0 0xcb 0xe0 0x0 0x7d 0x1 0x0 0x0
+0xc6 0x6 0xcb 0x50 0x0 0x7d 0x1 0x0 0x0 0x1 0xcc 0x60 0x0 0x0 0x0 0x0 0x0
+0xc7 0x0 0xcc 0xe2 0x0 0x7f 0x1 0x0 0x0 0x6 0xcc 0x50 0x0 0x7f 0x1 0x0 0x0
+0xc8 0x0 0xcd 0xe4 0x0 0x80 0x1 0x0 0x0 0x6 0xcd 0x50 0x0 0x80 0x1 0x0 0x0
+0xc9 0x1 0xcd 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0xce 0xe4 0x0 0x81 0x1 0x0 0x0
+0xca 0x6 0xce 0x50 0x0 0x81 0x1 0x0 0x0 0x0 0xcf 0xe2 0x0 0x7e 0x1 0x0 0x0
+0xcb 0x6 0xcf 0x50 0x0 0x7e 0x1 0x0 0x0 0x1 0xd0 0x60 0x0 0x0 0x0 0x0 0x0
+0xcc 0x0 0xd0 0xf6 0x0 0x9e 0x1 0x0 0x0 0x6 0xd0 0x50 0x0 0x9e 0x1 0x0 0x0
+0xcd 0x0 0xd1 0xd1 0x0 0x60 0x1 0x0 0x0 0x6 0xd1 0x50 0x0 0x60 0x1 0x0 0x0
+0xce 0x0 0xd2 0xd2 0x0 0x61 0x1 0x0 0x0 0x6 0xd2 0x50 0x0 0x61 0x1 0x0 0x0
+0xcf 0x1 0xd2 0x5f 0x0 0x0 0x0 0x0 0x0 0x0 0xd3 0xd5 0x0 0x67 0x1 0x0 0x0
+0xd0 0x6 0xd3 0x50 0x0 0x67 0x1 0x0 0x0 0x0 0xd4 0xec 0x0 0x8d 0x1 0x0 0x0
+0xd1 0x6 0xd4 0x50 0x0 0x8d 0x1 0x0 0x0 0x1 0xd4 0x5e 0x0 0x0 0x0 0x0 0x0
+0xd2 0x0 0xd5 0xc9 0x0 0x53 0x1 0x0 0x0 0x6 0xd5 0x50 0x0 0x53 0x1 0x0 0x0
+0xd3 0x1 0xd6 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xd6 0xcd 0x0 0x58 0x1 0x0 0x0
+0xd4 0x6 0xd6 0x50 0x0 0x58 0x1 0x0 0x0 0x1 0xd7 0x5b 0x0 0x0 0x0 0x0 0x0
+0xd5 0x0 0xd7 0xca 0x0 0x54 0x1 0x0 0x0 0x6 0xd7 0x50 0x0 0x54 0x1 0x0 0x0
+0xd6 0x0 0xd8 0xd2 0x0 0x5e 0x1 0x0 0x0 0x6 0xd8 0x50 0x0 0x5e 0x1 0x0 0x0
+0xd7 0x0 0xd9 0x2c 0x1 0x15 0x1 0x0 0x0 0x6 0xd9 0x50 0x0 0x15 0x1 0x0 0x0
+0xd8 0x0 0xda 0x60 0x1 0x1c 0x1 0x0 0x0 0x6 0xda 0x50 0x0 0x1c 0x1 0x0 0x0
+0xd9 0x1 0xda 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0xdb 0x5d 0x1 0xd6 0x0 0x0 0x0
+0xda 0x6 0xdb 0x50 0x0 0xd6 0x0 0x0 0x0 0x4 0xdc 0x43 0x0 0xd6 0x0 0x0 0x0
+0xdb 0x1 0xdc 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xdc 0x9c 0x1 0xc8 0x0 0x0 0x0
+0xdc 0x6 0xdc 0x50 0x0 0xc8 0x0 0x0 0x0 0x0 0xdd 0x70 0x1 0x8e 0x0 0x0 0x0
+0xdd 0x6 0xdd 0x50 0x0 0x8e 0x0 0x0 0x0 0x0 0xde 0x83 0x1 0x86 0x0 0x0 0x0
+0xde 0x6 0xde 0x50 0x0 0x86 0x0 0x0 0x0 0x0 0xdf 0x93 0x1 0x86 0x0 0x0 0x0
+0xdf 0x6 0xdf 0x50 0x0 0x86 0x0 0x0 0x0 0x1 0xdf 0x57 0x0 0x0 0x0 0x0 0x0
+0xe0 0x0 0xe0 0x74 0x1 0x7d 0x0 0x0 0x0 0x6 0xe0 0x50 0x0 0x7d 0x0 0x0 0x0
+0xe1 0x0 0xe1 0x2d 0x1 0x7c 0x0 0x0 0x0 0x6 0xe1 0x50 0x0 0x7c 0x0 0x0 0x0
+0xe2 0x0 0xe3 0xf0 0x0 0xa5 0x0 0xff 0xff 0x6 0xe3 0x50 0x0 0xa5 0x0 0xff 0xff
+0xe3 0x1 0xe4 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xe4 0x73 0x0 0x99 0x0 0x0 0x0
+0xe4 0x6 0xe4 0x50 0x0 0x99 0x0 0x0 0x0 0x0 0xe6 0x14 0x0 0xb5 0x0 0x0 0x0
+0xe5 0x6 0xe6 0x50 0x0 0xb5 0x0 0x0 0x0 0x0 0xe8 0xfb 0xff 0xbf 0x0 0x0 0x0
+0xe6 0x6 0xe8 0x50 0x0 0xbf 0x0 0x0 0x0 0x1 0xe9 0x59 0x0 0x0 0x0 0x0 0x0
+0xe7 0x0 0xea 0x8 0x0 0xd5 0x0 0x0 0x0 0x6 0xea 0x50 0x0 0xd5 0x0 0x0 0x0
+0xe8 0x1 0xec 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xee 0x68 0x0 0xae 0x0 0x0 0x0
+0xe9 0x6 0xee 0x64 0x0 0xae 0x0 0x0 0x0 0x0 0xf0 0xc5 0x0 0x5e 0x0 0x0 0x0
+0xea 0x6 0xf0 0x64 0x0 0x5e 0x0 0x0 0x0 0x1 0xf1 0x59 0x0 0x0 0x0 0x0 0x0
+0xeb 0x0 0xf2 0x1b 0x1 0x2f 0x0 0x1 0x0 0x6 0xf2 0x64 0x0 0x2f 0x0 0x1 0x0
+0xec 0x1 0xf2 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0xf3 0x15 0x1 0x15 0x0 0x0 0x0
+0xed 0x6 0xf3 0x64 0x0 0x15 0x0 0x0 0x0 0x0 0xf4 0x2a 0x1 0x8 0x0 0x0 0x0
+0xee 0x6 0xf4 0x76 0x0 0x8 0x0 0x0 0x0 0x1 0xf5 0x59 0x0 0x0 0x0 0x0 0x0
+0xef 0x0 0xf5 0x6c 0x1 0x13 0x0 0x0 0x0 0x6 0xf5 0x76 0x0 0x13 0x0 0x0 0x0
+0xf0 0x0 0xf6 0x63 0x1 0xa 0x0 0x0 0x0 0x6 0xf6 0x76 0x0 0xa 0x0 0x0 0x0
+0xf1 0x0 0xf7 0x8d 0x1 0x2 0x0 0x0 0x0 0x6 0xf7 0x76 0x0 0x2 0x0 0x0 0x0
+0xf2 0x1 0xf7 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0xf8 0xa5 0x1 0xf7 0xff 0x0 0x0
+0xf3 0x6 0xf8 0x76 0x0 0xf7 0xff 0x0 0x0 0x0 0xf9 0x22 0x2 0xd6 0xff 0x0 0x0
+0xf4 0x6 0xf9 0x76 0x0 0xd6 0xff 0x0 0x0 0x1 0xfa 0x57 0x0 0x0 0x0 0x0 0x0
+0xf5 0x0 0xfa 0x1 0x2 0xdf 0xff 0x0 0x0 0x6 0xfa 0x76 0x0 0xdf 0xff 0x0 0x0
+0xf6 0x1 0xfb 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0xfb 0xd2 0x1 0xe8 0xff 0x0 0x0
+0xf7 0x6 0xfb 0x76 0x0 0xe8 0xff 0x0 0x0 0x0 0xfc 0xaf 0x1 0xf4 0xff 0x0 0x0
+0xf8 0x6 0xfc 0x76 0x0 0xf4 0xff 0x0 0x0 0x1 0xfc 0x57 0x0 0x0 0x0 0x0 0x0
+0xf9 0x0 0xfd 0x93 0x1 0x4 0x0 0x0 0x0 0x6 0xfd 0x76 0x0 0x4 0x0 0x0 0x0
+0xfa 0x0 0xfe 0x8a 0x1 0x7 0x0 0x0 0x0 0x6 0xfe 0x76 0x0 0x7 0x0 0x0 0x0
+0xfb 0x0 0xff 0x6f 0x1 0x7 0x0 0x0 0x0 0x6 0xff 0x76 0x0 0x7 0x0 0x0 0x0
+0xfc 0x1 0x0 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x5a 0x1 0x5 0x0 0x0 0x0
+0xfd 0x6 0x0 0x76 0x0 0x5 0x0 0x0 0x0 0x1 0x1 0x57 0x0 0x0 0x0 0x0 0x0
+0xfe 0x0 0x1 0x22 0x1 0x1 0x0 0x0 0x0 0x6 0x1 0x76 0x0 0x1 0x0 0x0 0x0
+0xff 0x0 0x3 0x20 0x1 0x5 0x0 0x0 0x0 0x6 0x3 0x76 0x0 0x5 0x0 0x0 0x0
+0x0 0x1 0x3 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x4 0x59 0x0 0x0 0x0 0x0 0x0
+0x1 0x0 0x8 0xfe 0x0 0x1b 0x0 0x0 0x0 0x6 0x8 0x76 0x0 0x1b 0x0 0x0 0x0
+0x2 0x1 0x9 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xe 0x58 0x0 0x0 0x0 0x0 0x0
+0x3 0x0 0xf 0xc0 0x0 0x53 0x0 0x0 0x0 0x6 0xf 0xa5 0x0 0x53 0x0 0x0 0x0
+0x4 0x0 0x11 0x5d 0x0 0x7f 0x0 0x0 0x0 0x6 0x11 0xa5 0x0 0x7f 0x0 0x0 0x0
+0x5 0x1 0x15 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x16 0x16 0x0 0xb7 0x0 0x0 0x0
+0x6 0x6 0x16 0xb9 0x0 0xb7 0x0 0x0 0x0 0x1 0x16 0x59 0x0 0x0 0x0 0x0 0x0
+0x7 0x0 0x16 0x1 0x0 0xa4 0x0 0x0 0x0 0x6 0x16 0xb9 0x0 0xa4 0x0 0x0 0x0
+0x8 0x0 0x17 0x14 0x0 0xe6 0x0 0x0 0x0 0x6 0x17 0xb9 0x0 0xe6 0x0 0x0 0x0
+0x9 0x1 0x17 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x18 0x14 0x0 0xff 0x0 0x0 0x0
+0xa 0x6 0x18 0xb9 0x0 0xff 0x0 0x0 0x0 0x1 0x19 0x57 0x0 0x0 0x0 0x0 0x0
+0xb 0x0 0x19 0xd 0x0 0xe3 0x0 0x0 0x0 0x6 0x19 0xb9 0x0 0xe3 0x0 0x0 0x0
+0xc 0x0 0x1a 0xe 0x0 0xef 0x0 0xff 0xff 0x6 0x1a 0xb9 0x0 0xef 0x0 0xff 0xff
+0xd 0x0 0x1b 0x13 0x0 0xe5 0x0 0x0 0x0 0x6 0x1b 0xb9 0x0 0xe5 0x0 0x0 0x0
+0xe 0x0 0x1c 0xc 0x0 0xca 0x0 0x0 0x0 0x6 0x1c 0xb9 0x0 0xca 0x0 0x0 0x0
+0xf 0x0 0x1d 0xff 0xff 0xc4 0x0 0x0 0x0 0x6 0x1d 0xb9 0x0 0xc4 0x0 0x0 0x0
+0x10 0x1 0x1d 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x1e 0x9 0x0 0xe2 0x0 0x0 0x0
+0x11 0x6 0x1e 0xb9 0x0 0xe2 0x0 0x0 0x0 0x1 0x1f 0x54 0x0 0x0 0x0 0x0 0x0
+0x12 0x0 0x1f 0xc 0x0 0xd7 0x0 0x0 0x0 0x6 0x1f 0xa6 0x0 0xd7 0x0 0x0 0x0
+0x13 0x0 0x20 0x11 0x0 0xd2 0x0 0x0 0x0 0x6 0x20 0xa6 0x0 0xd2 0x0 0x0 0x0
+0x14 0x0 0x21 0x12 0x0 0xb7 0x0 0x0 0x0 0x6 0x21 0xa6 0x0 0xb7 0x0 0x0 0x0
+0x15 0x0 0x22 0xc 0x0 0xc2 0x0 0x0 0x0 0x6 0x22 0xa6 0x0 0xc2 0x0 0x0 0x0
+0x16 0x1 0x22 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x23 0x6 0x0 0xc5 0x0 0x0 0x0
+0x17 0x6 0x23 0xa6 0x0 0xc5 0x0 0x0 0x0 0x1 0x23 0x54 0x0 0x0 0x0 0x0 0x0
+0x18 0x0 0x24 0xff 0xff 0xc9 0x0 0x0 0x0 0x6 0x24 0xa6 0x0 0xc9 0x0 0x0 0x0
+0x19 0x1 0x25 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x25 0xfe 0xff 0xdf 0x0 0x0 0x0
+0x1a 0x6 0x25 0xa6 0x0 0xdf 0x0 0x0 0x0 0x0 0x26 0x4 0x0 0xc2 0x0 0x0 0x0
+0x1b 0x6 0x26 0xa6 0x0 0xc2 0x0 0x0 0x0 0x0 0x27 0x8 0x0 0xc2 0x0 0x0 0x0
+0x1c 0x6 0x27 0xa6 0x0 0xc2 0x0 0x0 0x0 0x1 0x27 0x52 0x0 0x0 0x0 0x0 0x0
+0x1d 0x0 0x28 0xe 0x0 0xd7 0x0 0x0 0x0 0x6 0x28 0xa6 0x0 0xd7 0x0 0x0 0x0
+0x1e 0x0 0x29 0x10 0x0 0xbb 0x0 0x0 0x0 0x6 0x29 0xa6 0x0 0xbb 0x0 0x0 0x0
+0x1f 0x1 0x29 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0x11 0x0 0xc4 0x0 0x0 0x0
+0x20 0x6 0x2a 0xa6 0x0 0xc4 0x0 0x0 0x0 0x0 0x2b 0x13 0x0 0xd5 0x0 0x0 0x0
+0x21 0x6 0x2b 0xa6 0x0 0xd5 0x0 0x0 0x0 0x1 0x2c 0x54 0x0 0x0 0x0 0x0 0x0
+0x22 0x0 0x2c 0xe 0x0 0xb6 0x0 0x0 0x0 0x6 0x2c 0xa6 0x0 0xb6 0x0 0x0 0x0
+0x23 0x0 0x2d 0x9 0x0 0xbc 0x0 0x0 0x0 0x6 0x2d 0xa6 0x0 0xbc 0x0 0x0 0x0
+0x24 0x0 0x2e 0x5 0x0 0xbb 0x0 0x0 0x0 0x6 0x2e 0xa6 0x0 0xbb 0x0 0x0 0x0
+0x25 0x0 0x2f 0x3 0x0 0xd5 0x0 0x0 0x0 0x6 0x2f 0xa6 0x0 0xd5 0x0 0x0 0x0
+0x26 0x0 0x30 0xfd 0xff 0xd6 0x0 0x0 0x0 0x6 0x30 0xa6 0x0 0xd6 0x0 0x0 0x0
+0x27 0x1 0x31 0x55 0x0 0x0 0x0 0x0 0x0 0x0 0x31 0xfc 0xff 0xbe 0x0 0x0 0x0
+0x28 0x6 0x31 0x94 0x0 0xbe 0x0 0x0 0x0 0x0 0x32 0xfd 0xff 0xcd 0x0 0x0 0x0
+0x29 0x6 0x32 0x94 0x0 0xcd 0x0 0x0 0x0 0x1 0x32 0x56 0x0 0x0 0x0 0x0 0x0
+0x2a 0x0 0x33 0xfc 0xff 0xd7 0x0 0x0 0x0 0x6 0x33 0x94 0x0 0xd7 0x0 0x0 0x0
+0x2b 0x0 0x34 0xfb 0xff 0xc8 0x0 0xff 0xff 0x6 0x34 0x94 0x0 0xc8 0x0 0xff 0xff
+0x2c 0x0 0x35 0xff 0xff 0xb4 0x0 0x0 0x0 0x6 0x35 0x94 0x0 0xb4 0x0 0x0 0x0
+0x2d 0x1 0x36 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0x37 0x58 0x0 0x0 0x0 0x0 0x0
+0x2e 0x0 0x37 0xf8 0xff 0xb6 0x0 0x0 0x0 0x6 0x37 0x94 0x0 0xb6 0x0 0x0 0x0
+0x2f 0x1 0x38 0x59 0x0 0x0 0x0 0x0 0x0 0x0 0x39 0xf9 0xff 0xa1 0x0 0x0 0x0
+0x30 0x6 0x39 0x94 0x0 0xa1 0x0 0x0 0x0 0x1 0x3a 0x5a 0x0 0x0 0x0 0x0 0x0
+0x31 0x1 0x3c 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x3d 0x5b 0x0 0x0 0x0 0x0 0x0
+0x32 0x0 0x3d 0xd 0x0 0x94 0x0 0x0 0x0 0x6 0x3d 0x94 0x0 0x94 0x0 0x0 0x0
+0x33 0x1 0x40 0x62 0x0 0x0 0x0 0x0 0x0 0x1 0x42 0x61 0x0 0x0 0x0 0x0 0x0
+0x34 0x1 0x44 0x68 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0xbf 0xff 0x84 0x0 0x0 0x0
+0x35 0x6 0x45 0xc0 0x0 0x84 0x0 0x0 0x0 0x1 0x45 0x6e 0x0 0x0 0x0 0x0 0x0
+0x36 0x1 0x47 0x66 0x0 0x0 0x0 0x0 0x0 0x0 0x47 0x74 0xff 0x56 0x0 0x0 0x0
+0x37 0x6 0x47 0xdb 0x0 0x56 0x0 0x0 0x0 0x0 0x49 0xd7 0xfe 0x3a 0x0 0x0 0x0
+0x38 0x6 0x49 0xdb 0x0 0x3a 0x0 0x0 0x0 0x1 0x49 0x68 0x0 0x0 0x0 0x0 0x0
+0x39 0x0 0x4a 0xa 0xff 0x60 0x0 0x0 0x0 0x6 0x4a 0xf1 0x0 0x60 0x0 0x0 0x0
+0x3a 0x1 0x4b 0x61 0x0 0x0 0x0 0x0 0x0 0x0 0x4c 0xdd 0xfe 0x3f 0x0 0x0 0x0
+0x3b 0x6 0x4c 0xf1 0x0 0x3f 0x0 0x0 0x0 0x1 0x4e 0x60 0x0 0x0 0x0 0x0 0x0
+0x3c 0x0 0x4e 0xd6 0xfe 0x3d 0x0 0x0 0x0 0x6 0x4e 0xf1 0x0 0x3d 0x0 0x0 0x0
+0x3d 0x1 0x4f 0x59 0x0 0x0 0x0 0x0 0x0 0x0 0x50 0xf6 0xfe 0x24 0x0 0x0 0x0
+0x3e 0x6 0x50 0xf1 0x0 0x24 0x0 0x0 0x0 0x1 0x50 0x54 0x0 0x0 0x0 0x0 0x0
+0x3f 0x0 0x52 0x8 0xff 0x26 0x0 0x0 0x0 0x6 0x52 0xf1 0x0 0x26 0x0 0x0 0x0
+0x40 0x1 0x53 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x54 0x4e 0x0 0x0 0x0 0x0 0x0
+0x41 0x0 0x55 0x7 0xff 0x40 0x0 0x0 0x0 0x6 0x55 0xf1 0x0 0x40 0x0 0x0 0x0
+0x42 0x1 0x55 0x4f 0x0 0x0 0x0 0x0 0x0 0x0 0x57 0xe 0xff 0x20 0x0 0x0 0x0
+0x43 0x6 0x57 0x6 0x1 0x20 0x0 0x0 0x0 0x1 0x58 0x52 0x0 0x0 0x0 0x0 0x0
+0x44 0x0 0x59 0xb5 0xfe 0x12 0x0 0x0 0x0 0x6 0x59 0x6 0x1 0x12 0x0 0x0 0x0
+0x45 0x0 0x5a 0xc4 0xfe 0xb 0x0 0x0 0x0 0x6 0x5a 0x6 0x1 0xb 0x0 0x0 0x0
+0x46 0x1 0x5a 0x53 0x0 0x0 0x0 0x0 0x0 0x0 0x5b 0xbb 0xfe 0xb 0x0 0x2 0x0
+0x47 0x6 0x5b 0x6 0x1 0xb 0x0 0x2 0x0 0x0 0x5c 0xac 0xfe 0xd 0x0 0x0 0x0
+0x48 0x6 0x5c 0x6 0x1 0xd 0x0 0x0 0x0 0x1 0x5c 0x54 0x0 0x0 0x0 0x0 0x0
+0x49 0x0 0x5d 0x2c 0xfe 0x1a 0x0 0x0 0x0 0x6 0x5d 0x6 0x1 0x1a 0x0 0x0 0x0
+0x4a 0x1 0x5e 0x56 0x0 0x0 0x0 0x0 0x0 0x0 0x5e 0x44 0xfe 0x22 0x0 0x0 0x0
+0x4b 0x6 0x5e 0xf2 0x0 0x22 0x0 0x0 0x0 0x1 0x5f 0x55 0x0 0x0 0x0 0x0 0x0
+0x4c 0x0 0x5f 0x76 0xfe 0x27 0x0 0x0 0x0 0x6 0x5f 0xf2 0x0 0x27 0x0 0x0 0x0
+0x4d 0x0 0x60 0xa2 0xfe 0x26 0x0 0x0 0x0 0x6 0x60 0xf2 0x0 0x26 0x0 0x0 0x0
+0x4e 0x0 0x61 0xd2 0xfe 0x1d 0x0 0x0 0x0 0x6 0x61 0xd9 0x0 0x1d 0x0 0x0 0x0
+0x4f 0x1 0x62 0x54 0x0 0x0 0x0 0x0 0x0 0x0 0x63 0xc6 0xfe 0x19 0x0 0x0 0x0
+0x50 0x6 0x63 0xd9 0x0 0x19 0x0 0x0 0x0 0x1 0x64 0x55 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x66 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x67 0x57 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x68 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x6b 0x59 0x0 0x0 0x0 0x0 0x0
+0x53 0x0 0x6b 0x2c 0xff 0x4a 0x0 0x0 0x0 0x6 0x6b 0xf3 0x0 0x4a 0x0 0x0 0x0
+0x54 0x1 0x6d 0x5b 0x0 0x0 0x0 0x0 0x0 0x1 0x6e 0x5a 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x73 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x74 0xe5 0xfe 0x37 0x0 0x0 0x0
+0x56 0x6 0x74 0x3a 0x1 0x37 0x0 0x0 0x0 0x1 0x75 0x59 0x0 0x0 0x0 0x0 0x0
+0x57 0x1 0x76 0x58 0x0 0x0 0x0 0x0 0x0 0x0 0x77 0xee 0xfe 0x5 0x0 0x0 0x0
+0x58 0x6 0x77 0x56 0x1 0x5 0x0 0x0 0x0 0x1 0x78 0x59 0x0 0x0 0x0 0x0 0x0
+0x59 0x1 0x7a 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x7c 0x59 0x0 0x0 0x0 0x0 0x0
+0x5a 0x1 0x80 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x81 0x59 0x0 0x0 0x0 0x0 0x0
+0x5b 0x1 0x84 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x84 0xf 0x0 0xf8 0xff 0x0 0x0
+0x5c 0x6 0x84 0xfc 0x1 0xf8 0xff 0x0 0x0 0x1 0x85 0x5b 0x0 0x0 0x0 0x0 0x0
+0x5d 0x1 0x86 0x5a 0x0 0x0 0x0 0x0 0x0 0x0 0x86 0x9e 0x0 0xeb 0xff 0x0 0x0
+0x5e 0x6 0x86 0x13 0x2 0xeb 0xff 0x0 0x0 0x1 0x88 0x5b 0x0 0x0 0x0 0x0 0x0
+0x5f 0x0 0x89 0xbf 0x0 0xf0 0xff 0x0 0x0 0x6 0x89 0x13 0x2 0xf0 0xff 0x0 0x0
+0x60 0x1 0x8a 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x8b 0x5b 0x0 0x0 0x0 0x0 0x0
+0x61 0x1 0x8d 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x8e 0x5b 0x0 0x0 0x0 0x0 0x0
+0x62 0x0 0x91 0xea 0xff 0xf6 0xff 0x0 0x0 0x6 0x91 0x57 0x2 0xf6 0xff 0x0 0x0
+0x63 0x1 0x92 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x93 0x5d 0x0 0x0 0x0 0x0 0x0
+0x64 0x1 0x98 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x99 0x5f 0x0 0x0 0x0 0x0 0x0
+0x65 0x1 0x9c 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x9d 0x5f 0x0 0x0 0x0 0x0 0x0
+0x66 0x1 0xa1 0x60 0x0 0x0 0x0 0x0 0x0 0x1 0xa2 0x61 0x0 0x0 0x0 0x0 0x0
+0x67 0x1 0xa3 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xa5 0x64 0x0 0x0 0x0 0x0 0x0
+0x68 0x1 0xaa 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xad 0x61 0x0 0x0 0x0 0x0 0x0
+0x69 0x1 0xaf 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0xb0 0x5d 0x0 0x0 0x0 0x0 0x0
+0x6a 0x1 0xb1 0x5c 0x0 0x0 0x0 0x0 0x0 0x0 0xb3 0x85 0xff 0xfb 0xff 0x0 0x0
+0x6b 0x6 0xb3 0xa8 0x6 0xfb 0xff 0x0 0x0 0x1 0xb4 0x5b 0x0 0x0 0x0 0x0 0x0
+0x6c 0x1 0xb6 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xb9 0x58 0x0 0x0 0x0 0x0 0x0
+0x6d 0x1 0xba 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xbe 0x58 0x0 0x0 0x0 0x0 0x0
+0x6e 0x1 0xbf 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0xc0 0x5a 0x0 0x0 0x0 0x0 0x0
+0x6f 0x1 0xc3 0x5b 0x0 0x0 0x0 0x0 0x0 0x0 0xc3 0x30 0x0 0xec 0xff 0x0 0x0
+0x70 0x6 0xc3 0x0 0x0 0xec 0xff 0x0 0x0 0x1 0xc4 0x5d 0x0 0x0 0x0 0x0 0x0
+0x71 0x1 0xc7 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xc8 0x8b 0x0 0xda 0xff 0x0 0x0
+0x72 0x6 0xc8 0x0 0x0 0xda 0xff 0x0 0x0 0x1 0xc9 0x5d 0x0 0x0 0x0 0x0 0x0
+0x73 0x1 0xcc 0x5e 0x0 0x0 0x0 0x0 0x0 0x0 0xcc 0xc0 0x0 0xeb 0xff 0x0 0x0
+0x74 0x6 0xcc 0xf0 0x6 0xeb 0xff 0x0 0x0 0x1 0xcf 0x5d 0x0 0x0 0x0 0x0 0x0
+0x75 0x0 0xd0 0xd8 0x0 0xeb 0xff 0x0 0x0 0x6 0xd0 0xc4 0x5 0xeb 0xff 0x0 0x0
+0x76 0x1 0xd1 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0xd4 0x5d 0x0 0x0 0x0 0x0 0x0
+0x77 0x0 0xd4 0xe4 0x0 0xfd 0xff 0x0 0x0 0x6 0xd4 0xc 0x5 0xfd 0xff 0x0 0x0
+0x78 0x1 0xd6 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0xd7 0x5f 0x0 0x0 0x0 0x0 0x0
+0x79 0x0 0xd7 0xed 0x0 0xf7 0xff 0x0 0x0 0x6 0xd7 0x82 0x4 0xf7 0xff 0x0 0x0
+0x7a 0x1 0xd8 0x60 0x0 0x0 0x0 0x0 0x0 0x0 0xda 0xec 0x0 0xef 0xff 0x0 0x0
+0x7b 0x6 0xda 0x1b 0x4 0xef 0xff 0x0 0x0 0x1 0xdb 0x62 0x0 0x0 0x0 0x0 0x0
+0x7c 0x1 0xdc 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x64 0x0 0x0 0x0 0x0 0x0
+0x7d 0x0 0xdd 0xeb 0x0 0xef 0xff 0x0 0x0 0x6 0xdd 0xa5 0x3 0xef 0xff 0x0 0x0
+0x7e 0x1 0xe1 0x65 0x0 0x0 0x0 0x0 0x0 0x0 0xe1 0xff 0x0 0xef 0xff 0x0 0x0
+0x7f 0x6 0xe1 0x52 0x3 0xef 0xff 0x0 0x0 0x1 0xe2 0x64 0x0 0x0 0x0 0x0 0x0
+0x80 0x0 0xe5 0xe6 0x0 0xe3 0xff 0x0 0x0 0x6 0xe5 0x27 0x3 0xe3 0xff 0x0 0x0
+0x81 0x1 0xe6 0x63 0x0 0x0 0x0 0x0 0x0 0x0 0xe9 0xfa 0x0 0xcc 0xff 0x0 0x0
+0x82 0x6 0xe9 0x27 0x3 0xcc 0xff 0x0 0x0 0x1 0xeb 0x62 0x0 0x0 0x0 0x0 0x0
+0x83 0x0 0xed 0xee 0x0 0xfa 0xff 0x0 0x0 0x6 0xed 0x14 0x3 0xfa 0xff 0x0 0x0
+0x84 0x1 0xee 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0xf0 0x65 0x0 0x0 0x0 0x0 0x0
+0x85 0x0 0xf2 0xfe 0x0 0xf 0x0 0x0 0x0 0x6 0xf2 0x14 0x3 0xf 0x0 0x0 0x0
+0x86 0x1 0xf3 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0xf4 0x67 0x0 0x0 0x0 0x0 0x0
+0x87 0x1 0xf6 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0xf7 0xef 0x0 0xf3 0xff 0xfe 0xff
+0x88 0x6 0xf7 0x14 0x3 0xf3 0xff 0xfe 0xff 0x1 0xf8 0x6a 0x0 0x0 0x0 0x0 0x0
+0x89 0x1 0xf9 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0xfa 0x6d 0x0 0x0 0x0 0x0 0x0
+0x8a 0x0 0xfb 0xe6 0x0 0xd7 0xff 0x0 0x0 0x6 0xfb 0x14 0x3 0xd7 0xff 0x0 0x0
+0x8b 0x1 0xfc 0x6f 0x0 0x0 0x0 0x0 0x0 0x1 0xfd 0x70 0x0 0x0 0x0 0x0 0x0
+0x8c 0x0 0xfe 0xe3 0x0 0xe6 0xff 0x0 0x0 0x6 0xfe 0x14 0x3 0xe6 0xff 0x0 0x0
+0x8d 0x1 0xff 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x2 0x72 0x0 0x0 0x0 0x0 0x0
+0x8e 0x0 0x3 0xe7 0x0 0xda 0xff 0x0 0x0 0x6 0x3 0x14 0x3 0xda 0xff 0x0 0x0
+0x8f 0x1 0x4 0x73 0x0 0x0 0x0 0x0 0x0 0x1 0x6 0x72 0x0 0x0 0x0 0x0 0x0
+0x90 0x0 0x7 0xfb 0x0 0xfc 0xff 0x0 0x0 0x6 0x7 0x14 0x3 0xfc 0xff 0x0 0x0
+0x91 0x1 0x7 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x8 0x70 0x0 0x0 0x0 0x0 0x0
+0x92 0x0 0xb 0xee 0x0 0xd 0x0 0x0 0x0 0x6 0xb 0x14 0x3 0xd 0x0 0x0 0x0
+0x93 0x1 0xb 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0xc 0x70 0x0 0x0 0x0 0x0 0x0
+0x94 0x1 0xd 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0xe 0xef 0x0 0x13 0x0 0x0 0x0
+0x95 0x6 0xe 0x14 0x3 0x13 0x0 0x0 0x0 0x1 0x10 0x71 0x0 0x0 0x0 0x0 0x0
+0x96 0x1 0x11 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x71 0x0 0x0 0x0 0x0 0x0
+0x97 0x0 0x14 0x6 0x1 0xf1 0xff 0x0 0x0 0x6 0x14 0x1 0x3 0xf1 0xff 0x0 0x0
+0x98 0x1 0x15 0x72 0x0 0x0 0x0 0x0 0x0 0x1 0x17 0x73 0x0 0x0 0x0 0x0 0x0
+0x99 0x0 0x18 0x7 0x1 0xa 0x0 0xff 0xff 0x6 0x18 0x1 0x3 0xa 0x0 0xff 0xff
+0x9a 0x5 0x18 0x71 0x1 0xa 0x0 0xff 0xff 0x1 0x1b 0x75 0x0 0x0 0x0 0x0 0x0
+0x9b 0x0 0x1b 0x1 0x1 0x7 0x0 0x0 0x0 0x6 0x1b 0xd2 0x2 0x7 0x0 0x0 0x0
+0x9c 0x1 0x1c 0x74 0x0 0x0 0x0 0x0 0x0 0x0 0x1f 0xc 0x1 0x4 0x0 0x0 0x0
+0x9d 0x6 0x1f 0xba 0x2 0x4 0x0 0x0 0x0 0x1 0x1f 0x76 0x0 0x0 0x0 0x0 0x0
+0x9e 0x1 0x21 0x75 0x0 0x0 0x0 0x0 0x0 0x1 0x23 0x76 0x0 0x0 0x0 0x0 0x0
+0x9f 0x0 0x24 0xc7 0x0 0xb4 0xff 0x0 0x0 0x6 0x24 0xba 0x2 0xb4 0xff 0x0 0x0
+0xa0 0x1 0x24 0x75 0x0 0x0 0x0 0x0 0x0 0x0 0x27 0x78 0x0 0x8a 0xff 0x0 0x0
+0xa1 0x6 0x27 0xba 0x2 0x8a 0xff 0x0 0x0 0x1 0x28 0x74 0x0 0x0 0x0 0x0 0x0
+0xa2 0x1 0x29 0x73 0x0 0x0 0x0 0x0 0x0 0x0 0x2a 0x4b 0x0 0x7a 0xff 0x0 0x0
+0xa3 0x6 0x2a 0xba 0x2 0x7a 0xff 0x0 0x0 0x1 0x2a 0x72 0x0 0x0 0x0 0x0 0x0
+0xa4 0x0 0x2d 0x37 0x0 0x6d 0xff 0x0 0x0 0x6 0x2d 0xa1 0x2 0x6d 0xff 0x0 0x0
+0xa5 0x1 0x2d 0x71 0x0 0x0 0x0 0x0 0x0 0x1 0x2e 0x70 0x0 0x0 0x0 0x0 0x0
+0xa6 0x0 0x30 0x34 0x0 0x67 0xff 0x0 0x0 0x6 0x30 0x85 0x2 0x67 0xff 0x0 0x0
+0xa7 0x1 0x32 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x33 0x35 0x0 0x61 0xff 0x0 0x0
+0xa8 0x6 0x33 0x70 0x2 0x61 0xff 0x0 0x0 0x1 0x33 0x70 0x0 0x0 0x0 0x0 0x0
+0xa9 0x0 0x36 0x26 0x0 0x6a 0xff 0x0 0x0 0x6 0x36 0x5e 0x2 0x6a 0xff 0x0 0x0
+0xaa 0x1 0x37 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x39 0x28 0x0 0x68 0xff 0x0 0x0
+0xab 0x6 0x39 0x4b 0x2 0x68 0xff 0x0 0x0 0x1 0x39 0x6e 0x0 0x0 0x0 0x0 0x0
+0xac 0x1 0x3b 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x3c 0x24 0x0 0x66 0xff 0x0 0x0
+0xad 0x6 0x3c 0x4b 0x2 0x66 0xff 0x0 0x0 0x1 0x3d 0x6b 0x0 0x0 0x0 0x0 0x0
+0xae 0x0 0x3f 0x26 0x0 0x66 0xff 0x0 0x0 0x6 0x3f 0x4b 0x2 0x66 0xff 0x0 0x0
+0xaf 0x1 0x40 0x6a 0x0 0x0 0x0 0x0 0x0 0x1 0x41 0x68 0x0 0x0 0x0 0x0 0x0
+0xb0 0x0 0x42 0x1f 0x0 0x54 0xff 0x0 0x0 0x6 0x42 0x36 0x2 0x54 0xff 0x0 0x0
+0xb1 0x1 0x43 0x67 0x0 0x0 0x0 0x0 0x0 0x0 0x45 0xf 0x0 0x4d 0xff 0x0 0x0
+0xb2 0x6 0x45 0x36 0x2 0x4d 0xff 0x0 0x0 0x1 0x45 0x68 0x0 0x0 0x0 0x0 0x0
+0xb3 0x1 0x46 0x69 0x0 0x0 0x0 0x0 0x0 0x0 0x48 0xd 0x0 0x68 0xff 0x0 0x0
+0xb4 0x6 0x48 0x23 0x2 0x68 0xff 0x0 0x0 0x0 0x4b 0x13 0x0 0x62 0xff 0x0 0x0
+0xb5 0x6 0x4b 0x23 0x2 0x62 0xff 0x0 0x0 0x1 0x4b 0x6a 0x0 0x0 0x0 0x0 0x0
+0xb6 0x1 0x4c 0x6c 0x0 0x0 0x0 0x0 0x0 0x0 0x4e 0x0 0x0 0x4d 0xff 0x0 0x0
+0xb7 0x6 0x4e 0x23 0x2 0x4d 0xff 0x0 0x0 0x1 0x50 0x6e 0x0 0x0 0x0 0x0 0x0
+0xb8 0x0 0x51 0xf4 0xff 0x5b 0xff 0x0 0x0 0x6 0x51 0x23 0x2 0x5b 0xff 0x0 0x0
+0xb9 0x1 0x51 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x54 0x1 0x0 0x63 0xff 0x0 0x0
+0xba 0x6 0x54 0x23 0x2 0x63 0xff 0x0 0x0 0x1 0x54 0x6e 0x0 0x0 0x0 0x0 0x0
+0xbb 0x1 0x55 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x57 0xf6 0xff 0x6e 0xff 0x0 0x0
+0xbc 0x6 0x57 0x23 0x2 0x6e 0xff 0x0 0x0 0x1 0x5a 0x6e 0x0 0x0 0x0 0x0 0x0
+0xbd 0x0 0x5c 0xe5 0xff 0x64 0xff 0x0 0x0 0x6 0x5c 0x36 0x2 0x64 0xff 0x0 0x0
+0xbe 0x1 0x5d 0x71 0x0 0x0 0x0 0x0 0x0 0x0 0x5f 0x1a 0x0 0x71 0xff 0x0 0x0
+0xbf 0x6 0x5f 0x4e 0x2 0x71 0xff 0x0 0x0 0x0 0x62 0xb 0x0 0x5c 0xff 0x0 0x0
+0xc0 0x6 0x62 0x4e 0x2 0x5c 0xff 0x0 0x0 0x1 0x62 0x70 0x0 0x0 0x0 0x0 0x0
+0xc1 0x1 0x64 0x71 0x0 0x0 0x0 0x0 0x0 0x0 0x65 0xf1 0xff 0x5e 0xff 0x0 0x0
+0xc2 0x6 0x65 0x4e 0x2 0x5e 0xff 0x0 0x0 0x1 0x65 0x72 0x0 0x0 0x0 0x0 0x0
+0xc3 0x1 0x67 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x68 0x2 0x0 0x67 0xff 0x0 0x0
+0xc4 0x6 0x68 0x3b 0x2 0x67 0xff 0x0 0x0 0x1 0x68 0x6e 0x0 0x0 0x0 0x0 0x0
+0xc5 0x0 0x6b 0x16 0x0 0x59 0xff 0x0 0x0 0x6 0x6b 0x3b 0x2 0x59 0xff 0x0 0x0
+0xc6 0x1 0x6c 0x6f 0x0 0x0 0x0 0x0 0x0 0x0 0x6e 0x23 0x0 0x6c 0xff 0x0 0x0
+0xc7 0x6 0x6e 0x3b 0x2 0x6c 0xff 0x0 0x0 0x1 0x71 0x6e 0x0 0x0 0x0 0x0 0x0
+0xc8 0x0 0x72 0x3a 0x0 0x57 0xff 0x0 0x0 0x6 0x72 0x27 0x2 0x57 0xff 0x0 0x0
+0xc9 0x1 0x72 0x70 0x0 0x0 0x0 0x0 0x0 0x0 0x75 0x16 0x0 0x5b 0xff 0x0 0x0
+0xca 0x6 0x75 0x27 0x2 0x5b 0xff 0x0 0x0 0x1 0x76 0x6f 0x0 0x0 0x0 0x0 0x0
+0xcb 0x1 0x77 0x6e 0x0 0x0 0x0 0x0 0x0 0x0 0x78 0xf7 0xff 0x5b 0xff 0x0 0x0
+0xcc 0x6 0x78 0x27 0x2 0x5b 0xff 0x0 0x0 0x4 0x78 0x9c 0x1 0x5b 0xff 0x0 0x0
+0xcd 0x1 0x78 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x7b 0xf2 0xff 0x6d 0xff 0x0 0x0
+0xce 0x6 0x7b 0x27 0x2 0x6d 0xff 0x0 0x0 0x1 0x7c 0x6c 0x0 0x0 0x0 0x0 0x0
+0xcf 0x1 0x7d 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x7e 0xef 0xff 0x64 0xff 0x0 0x0
+0xd0 0x6 0x7e 0x27 0x2 0x64 0xff 0x0 0x0 0x1 0x7f 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd1 0x0 0x82 0xca 0xff 0x58 0xff 0x0 0x0 0x6 0x82 0x27 0x2 0x58 0xff 0x0 0x0
+0xd2 0x1 0x82 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x84 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd3 0x0 0x86 0xce 0xff 0x57 0xff 0x0 0x0 0x6 0x86 0x27 0x2 0x57 0xff 0x0 0x0
+0xd4 0x0 0x89 0xfe 0xff 0x65 0xff 0x0 0x0 0x6 0x89 0x27 0x2 0x65 0xff 0x0 0x0
+0xd5 0x1 0x89 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x8a 0x6c 0x0 0x0 0x0 0x0 0x0
+0xd6 0x1 0x8c 0x6d 0x0 0x0 0x0 0x0 0x0 0x0 0x8d 0x13 0x0 0x68 0xff 0x0 0x0
+0xd7 0x6 0x8d 0x27 0x2 0x68 0xff 0x0 0x0 0x1 0x8e 0x6c 0x0 0x0 0x0 0x0 0x0
+0xd8 0x1 0x8f 0x6b 0x0 0x0 0x0 0x0 0x0 0x0 0x91 0xf0 0xff 0x6b 0xff 0x0 0x0
+0xd9 0x6 0x91 0x54 0x2 0x6b 0xff 0x0 0x0 0x0 0x94 0xee 0xff 0x6b 0xff 0x0 0x0
+0xda 0x6 0x94 0x54 0x2 0x6b 0xff 0x0 0x0 0x1 0x94 0x6c 0x0 0x0 0x0 0x0 0x0
+0xdb 0x0 0x97 0xfc 0xff 0x6f 0xff 0x0 0x0 0x6 0x97 0x54 0x2 0x6f 0xff 0x0 0x0
+0xdc 0x1 0x98 0x6d 0x0 0x0 0x0 0x0 0x0 0x1 0x99 0x6e 0x0 0x0 0x0 0x0 0x0
+0xdd 0x0 0x9a 0x11 0x0 0x6f 0xff 0x0 0x0 0x6 0x9a 0x54 0x2 0x6f 0xff 0x0 0x0
+0xde 0x1 0x9a 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x9d 0x71 0x0 0x0 0x0 0x0 0x0
+0xdf 0x0 0x9d 0x25 0x0 0x72 0xff 0x0 0x0 0x6 0x9d 0x54 0x2 0x72 0xff 0x0 0x0
+0xe0 0x1 0x9e 0x72 0x0 0x0 0x0 0x0 0x0 0x0 0xa1 0xed 0xff 0x70 0xff 0x0 0x0
+0xe1 0x6 0xa1 0x54 0x2 0x70 0xff 0x0 0x0 0x1 0xa2 0x73 0x0 0x0 0x0 0x0 0x0
+0xe2 0x1 0xa3 0x74 0x0 0x0 0x0 0x0 0x0 0x1 0xa4 0x73 0x0 0x0 0x0 0x0 0x0
+0xe3 0x1 0xa6 0x72 0x0 0x0 0x0 0x0 0x0 0x1 0xa7 0x70 0x0 0x0 0x0 0x0 0x0
+0xe4 0x1 0xab 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0xac 0x6d 0x0 0x0 0x0 0x0 0x0
+0xe5 0x0 0xad 0x60 0x0 0xcc 0xff 0x0 0x0 0x6 0xad 0xfb 0x2 0xcc 0xff 0x0 0x0
+0xe6 0x1 0xaf 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0xb5 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe7 0x1 0xb6 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0xb8 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe8 0x1 0xbd 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0xc0 0x6f 0x0 0x0 0x0 0x0 0x0
+0xe9 0x1 0xc2 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0xc3 0x67 0x0 0x0 0x0 0x0 0x0
+0xea 0x1 0xc4 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0xc7 0x5f 0x0 0x0 0x0 0x0 0x0
+0xeb 0x1 0xc8 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0xc9 0x59 0x0 0x0 0x0 0x0 0x0
+0xec 0x1 0xcc 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0xcd 0x57 0x0 0x0 0x0 0x0 0x0
+0xed 0x1 0xce 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xd1 0x4d 0x0 0x0 0x0 0x0 0x0
+0xee 0x1 0xd2 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0xd3 0x4a 0x0 0x0 0x0 0x0 0x0
+0xef 0x1 0xd8 0x49 0x0 0x0 0x0 0x0 0x0 0x1 0xda 0x4a 0x0 0x0 0x0 0x0 0x0
+0xf0 0x1 0xdc 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x4a 0x0 0x0 0x0 0x0 0x0
+0xf1 0x1 0xdf 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xe0 0x52 0x0 0x0 0x0 0x0 0x0
+0xf2 0x1 0xe2 0x53 0x0 0x0 0x0 0x0 0x0 0x1 0xe4 0x5a 0x0 0x0 0x0 0x0 0x0
+0xf3 0x1 0xe5 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xe7 0x54 0x0 0x0 0x0 0x0 0x0
+0xf4 0x1 0xe9 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xea 0x58 0x0 0x0 0x0 0x0 0x0
+0xf5 0x1 0xeb 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xee 0x54 0x0 0x0 0x0 0x0 0x0
+0xf6 0x1 0xef 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xf4 0x56 0x0 0x0 0x0 0x0 0x0
+0xf7 0x1 0xf5 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xf8 0x58 0x0 0x0 0x0 0x0 0x0
+0xf8 0x1 0xfa 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xff 0x59 0x0 0x0 0x0 0x0 0x0
+0xf9 0x1 0x1 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x3 0x58 0x0 0x0 0x0 0x0 0x0
+0xfa 0x1 0x4 0x59 0x0 0x0 0x0 0x0 0x0 0x1 0x6 0x60 0x0 0x0 0x0 0x0 0x0
+0xfb 0x1 0x7 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x9 0x68 0x0 0x0 0x0 0x0 0x0
+0xfc 0x1 0xc 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x10 0x6f 0x0 0x0 0x0 0x0 0x0
+0xfd 0x1 0x11 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x6f 0x0 0x0 0x0 0x0 0x0
+0xfe 0x1 0x15 0x70 0x0 0x0 0x0 0x0 0x0 0x1 0x16 0x6f 0x0 0x0 0x0 0x0 0x0
+0xff 0x1 0x17 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x1c 0x6d 0x0 0x0 0x0 0x0 0x0
+0x0 0x1 0x1f 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x6a 0x0 0x0 0x0 0x0 0x0
+0x1 0x1 0x25 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x26 0x6c 0x0 0x0 0x0 0x0 0x0
+0x2 0x1 0x28 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x2a 0x6f 0x0 0x0 0x0 0x0 0x0
+0x3 0x1 0x2e 0x6e 0x0 0x0 0x0 0x0 0x0 0x1 0x30 0x6d 0x0 0x0 0x0 0x0 0x0
+0x4 0x1 0x32 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x33 0x6a 0x0 0x0 0x0 0x0 0x0
+0x5 0x1 0x34 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x38 0x68 0x0 0x0 0x0 0x0 0x0
+0x6 0x1 0x39 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x3d 0x66 0x0 0x0 0x0 0x0 0x0
+0x7 0x1 0x3e 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x41 0x6e 0x0 0x0 0x0 0x0 0x0
+0x8 0x1 0x42 0x6f 0x0 0x0 0x0 0x0 0x0 0x1 0x43 0x68 0x0 0x0 0x0 0x0 0x0
+0x9 0x0 0x44 0x9f 0x2 0x56 0xfe 0x0 0x0 0x6 0x44 0x4 0x4 0x56 0xfe 0x0 0x0
+0xa 0x0 0x45 0xfd 0xff 0x2 0x0 0x1 0x0 0x6 0x45 0x4 0x4 0x2 0x0 0x1 0x0
+0xb 0x1 0x46 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x47 0x6c 0x0 0x0 0x0 0x0 0x0
+0xc 0x1 0x48 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x4a 0x6a 0x0 0x0 0x0 0x0 0x0
+0xd 0x1 0x4c 0x67 0x0 0x0 0x0 0x0 0x0 0x1 0x4d 0x68 0x0 0x0 0x0 0x0 0x0
+0xe 0x1 0x4f 0x6b 0x0 0x0 0x0 0x0 0x0 0x1 0x50 0x6a 0x0 0x0 0x0 0x0 0x0
+0xf 0x1 0x52 0x69 0x0 0x0 0x0 0x0 0x0 0x1 0x54 0x68 0x0 0x0 0x0 0x0 0x0
+0x10 0x1 0x59 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0x5a 0x67 0x0 0x0 0x0 0x0 0x0
+0x11 0x1 0x5b 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x5e 0x65 0x0 0x0 0x0 0x0 0x0
+0x12 0x1 0x5f 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x64 0x63 0x0 0x0 0x0 0x0 0x0
+0x13 0x1 0x68 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x69 0x65 0x0 0x0 0x0 0x0 0x0
+0x14 0x1 0x6e 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x6f 0x68 0x0 0x0 0x0 0x0 0x0
+0x15 0x1 0x74 0x68 0x0 0x0 0x0 0x0 0x0 0x1 0x76 0x67 0x0 0x0 0x0 0x0 0x0
+0x16 0x1 0x77 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x7c 0x65 0x0 0x0 0x0 0x0 0x0
+0x17 0x1 0x7e 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x85 0x64 0x0 0x0 0x0 0x0 0x0
+0x18 0x1 0x86 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0x87 0x64 0x0 0x0 0x0 0x0 0x0
+0x19 0x1 0x8a 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x8c 0x61 0x0 0x0 0x0 0x0 0x0
+0x1a 0x1 0x90 0x60 0x0 0x0 0x0 0x0 0x0 0x1 0x91 0x5f 0x0 0x0 0x0 0x0 0x0
+0x1b 0x1 0x93 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x95 0x5d 0x0 0x0 0x0 0x0 0x0
+0x1c 0x1 0x96 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x9b 0x5d 0x0 0x0 0x0 0x0 0x0
+0x1d 0x1 0x9d 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x9e 0x5e 0x0 0x0 0x0 0x0 0x0
+0x1e 0x1 0xa0 0x65 0x0 0x0 0x0 0x0 0x0 0x1 0xa2 0x5e 0x0 0x0 0x0 0x0 0x0
+0x1f 0x1 0xa3 0x5d 0x0 0x0 0x0 0x0 0x0 0x1 0xa5 0x56 0x0 0x0 0x0 0x0 0x0
+0x20 0x1 0xa7 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xa8 0x51 0x0 0x0 0x0 0x0 0x0
+0x21 0x1 0xa9 0x4e 0x0 0x0 0x0 0x0 0x0 0x1 0xac 0x4d 0x0 0x0 0x0 0x0 0x0
+0x22 0x1 0xad 0x4c 0x0 0x0 0x0 0x0 0x0 0x1 0xae 0x4b 0x0 0x0 0x0 0x0 0x0
+0x23 0x1 0xb1 0x4a 0x0 0x0 0x0 0x0 0x0 0x1 0xb2 0x4c 0x0 0x0 0x0 0x0 0x0
+0x24 0x1 0xb3 0x4b 0x0 0x0 0x0 0x0 0x0 0x1 0xb6 0x4a 0x0 0x0 0x0 0x0 0x0
+0x25 0x1 0xb8 0x4b 0x0 0x0 0x0 0x0 0x0 0x1 0xba 0x4c 0x0 0x0 0x0 0x0 0x0
+0x26 0x1 0xbc 0x4d 0x0 0x0 0x0 0x0 0x0 0x1 0xbd 0x4e 0x0 0x0 0x0 0x0 0x0
+0x27 0x1 0xc1 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0xc2 0x51 0x0 0x0 0x0 0x0 0x0
+0x28 0x1 0xc4 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xc7 0x53 0x0 0x0 0x0 0x0 0x0
+0x29 0x1 0xc9 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xce 0x55 0x0 0x0 0x0 0x0 0x0
+0x2a 0x1 0xcf 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xd0 0x55 0x0 0x0 0x0 0x0 0x0
+0x2b 0x1 0xd3 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0xd4 0x57 0x0 0x0 0x0 0x0 0x0
+0x2c 0x1 0xd5 0x54 0x0 0x0 0x0 0x0 0x0 0x1 0xd8 0x53 0x0 0x0 0x0 0x0 0x0
+0x2d 0x1 0xd9 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xdd 0x54 0x0 0x0 0x0 0x0 0x0
+0x2e 0x1 0xdf 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0xe1 0x56 0x0 0x0 0x0 0x0 0x0
+0x2f 0x1 0xe3 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xe6 0x59 0x0 0x0 0x0 0x0 0x0
+0x30 0x1 0xe8 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xe9 0x5b 0x0 0x0 0x0 0x0 0x0
+0x31 0x1 0xeb 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0xec 0x53 0x0 0x0 0x0 0x0 0x0
+0x32 0x1 0xee 0x52 0x0 0x0 0x0 0x0 0x0 0x1 0xf0 0x51 0x0 0x0 0x0 0x0 0x0
+0x33 0x1 0xf1 0x4a 0x0 0x0 0x0 0x0 0x0 0x1 0xf3 0x49 0x0 0x0 0x0 0x0 0x0
+0x34 0x1 0xf5 0x42 0x0 0x0 0x0 0x0 0x0 0x1 0xf6 0x43 0x0 0x0 0x0 0x0 0x0
+0x35 0x1 0xf7 0x44 0x0 0x0 0x0 0x0 0x0 0x1 0xfa 0x4b 0x0 0x0 0x0 0x0 0x0
+0x36 0x1 0xfb 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0xfc 0x50 0x0 0x0 0x0 0x0 0x0
+0x37 0x1 0xff 0x4f 0x0 0x0 0x0 0x0 0x0 0x1 0x0 0x50 0x0 0x0 0x0 0x0 0x0
+0x38 0x1 0x1 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x5 0x50 0x0 0x0 0x0 0x0 0x0
+0x39 0x1 0x8 0x53 0x0 0x0 0x0 0x0 0x0 0x1 0xa 0x51 0x0 0x0 0x0 0x0 0x0
+0x3a 0x1 0xf 0x51 0x0 0x0 0x0 0x0 0x0 0x1 0x12 0x53 0x0 0x0 0x0 0x0 0x0
+0x3b 0x1 0x13 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x15 0x5f 0x0 0x0 0x0 0x0 0x0
+0x3c 0x1 0x17 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x18 0x5e 0x0 0x0 0x0 0x0 0x0
+0x3d 0x1 0x19 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x1c 0x60 0x0 0x0 0x0 0x0 0x0
+0x3e 0x1 0x1e 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x21 0x5d 0x0 0x0 0x0 0x0 0x0
+0x3f 0x1 0x22 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x23 0x5d 0x0 0x0 0x0 0x0 0x0
+0x40 0x1 0x26 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x27 0x5f 0x0 0x0 0x0 0x0 0x0
+0x41 0x1 0x2b 0x5e 0x0 0x0 0x0 0x0 0x0 0x1 0x2f 0x5d 0x0 0x0 0x0 0x0 0x0
+0x42 0x1 0x31 0x5c 0x0 0x0 0x0 0x0 0x0 0x1 0x32 0x5a 0x0 0x0 0x0 0x0 0x0
+0x43 0x1 0x34 0x5b 0x0 0x0 0x0 0x0 0x0 0x1 0x35 0x5c 0x0 0x0 0x0 0x0 0x0
+0x44 0x1 0x37 0x5d 0x0 0x0 0x0 0x0 0x0 0x1 0x39 0x5e 0x0 0x0 0x0 0x0 0x0
+0x45 0x1 0x3a 0x5f 0x0 0x0 0x0 0x0 0x0 0x1 0x3b 0x61 0x0 0x0 0x0 0x0 0x0
+0x46 0x1 0x3e 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x3f 0x64 0x0 0x0 0x0 0x0 0x0
+0x47 0x1 0x43 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x44 0x64 0x0 0x0 0x0 0x0 0x0
+0x48 0x1 0x45 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x47 0x62 0x0 0x0 0x0 0x0 0x0
+0x49 0x1 0x49 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x4e 0x60 0x0 0x0 0x0 0x0 0x0
+0x4a 0x1 0x4f 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x51 0x60 0x0 0x0 0x0 0x0 0x0
+0x4b 0x1 0x52 0x61 0x0 0x0 0x0 0x0 0x0 0x1 0x56 0x62 0x0 0x0 0x0 0x0 0x0
+0x4c 0x1 0x57 0x63 0x0 0x0 0x0 0x0 0x0 0x1 0x58 0x62 0x0 0x0 0x0 0x0 0x0
+0x4d 0x1 0x5b 0x64 0x0 0x0 0x0 0x0 0x0 0x1 0x5d 0x65 0x0 0x0 0x0 0x0 0x0
+0x4e 0x1 0x60 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x62 0x67 0x0 0x0 0x0 0x0 0x0
+0x4f 0x1 0x65 0x66 0x0 0x0 0x0 0x0 0x0 0x1 0x66 0x60 0x0 0x0 0x0 0x0 0x0
+0x50 0x1 0x67 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x69 0x57 0x0 0x0 0x0 0x0 0x0
+0x51 0x1 0x6b 0x50 0x0 0x0 0x0 0x0 0x0 0x1 0x6c 0x57 0x0 0x0 0x0 0x0 0x0
+0x52 0x1 0x6e 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x71 0x57 0x0 0x0 0x0 0x0 0x0
+0x53 0x1 0x73 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x74 0x5b 0x0 0x0 0x0 0x0 0x0
+0x54 0x1 0x78 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x7d 0x5a 0x0 0x0 0x0 0x0 0x0
+0x55 0x1 0x82 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x83 0x5b 0x0 0x0 0x0 0x0 0x0
+0x56 0x1 0x87 0x5a 0x0 0x0 0x0 0x0 0x0 0x1 0x88 0x59 0x0 0x0 0x0 0x0 0x0
+0x57 0x1 0x8b 0x58 0x0 0x0 0x0 0x0 0x0 0x1 0x8d 0x57 0x0 0x0 0x0 0x0 0x0
+0x58 0x1 0x8e 0x56 0x0 0x0 0x0 0x0 0x0 0x1 0x90 0x57 0x0 0x0 0x0 0x0 0x0
+0x59 0x1 0x93 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x96 0x54 0x0 0x0 0x0 0x0 0x0
+0x5a 0x1 0x98 0x55 0x0 0x0 0x0 0x0 0x0 0x1 0x9c 0x56 0x0 0x0 0x0 0x0 0x0
+0x5b 0x1 0x9f 0x57 0x0 0x0 0x0 0x0 0x0 0x1 0xa0 0x58 0x0 0x0 0x0 0x0 0x0
+0x5c 0x1 0xa4 0x59 0x0 0x0 0x0 0x0 0x0 0x2 0xa8 0x0 0x0 0x0 0x0 0x0 0x0