Misc improvements:
* Make command execution (RuffyScripter/ComoboPlugin.runCommand) more robust (I still suck at threading). * Return all possible states in PumpState * Add absolute TBR to PumpState * Add NoOpCommand to fetch state data from pump * Display returned pump state in Combo fragment/tab.
This commit is contained in:
parent
f251427d1b
commit
8ecf6922f7
6 changed files with 142 additions and 66 deletions
|
@ -108,17 +108,16 @@ public class RuffyScripter {
|
||||||
private volatile Command activeCmd = null;
|
private volatile Command activeCmd = null;
|
||||||
|
|
||||||
public CommandResult runCommand(final Command cmd) {
|
public CommandResult runCommand(final Command cmd) {
|
||||||
try {
|
synchronized (this) {
|
||||||
if (isPumpBusy()) {
|
try {
|
||||||
return new CommandResult().message("Pump is busy");
|
if (isPumpBusy()) {
|
||||||
}
|
return new CommandResult().message("Pump is busy");
|
||||||
ensureConnected();
|
}
|
||||||
|
ensureConnected();
|
||||||
|
|
||||||
// TODO reuse thread, scheduler ...
|
// TODO reuse thread, scheduler ...
|
||||||
Thread cmdThread;
|
Thread cmdThread;
|
||||||
|
|
||||||
// TODO make this a safe lock
|
|
||||||
synchronized (this) {
|
|
||||||
cmdResult = null;
|
cmdResult = null;
|
||||||
activeCmd = cmd;
|
activeCmd = cmd;
|
||||||
// wait till pump is ready for input
|
// wait till pump is ready for input
|
||||||
|
@ -126,7 +125,8 @@ public class RuffyScripter {
|
||||||
// check if pump is an an error state
|
// check if pump is an an error state
|
||||||
if (currentMenu != null && currentMenu.getType() == MenuType.WARNING_OR_ERROR) {
|
if (currentMenu != null && currentMenu.getType() == MenuType.WARNING_OR_ERROR) {
|
||||||
try {
|
try {
|
||||||
return new CommandResult().message("Pump is in an error state: " + currentMenu.getAttribute(MenuAttribute.MESSAGE));
|
PumpState pumpState = readPumpState();
|
||||||
|
return new CommandResult().message("Pump is in an error state: " + currentMenu.getAttribute(MenuAttribute.MESSAGE)).state(pumpState);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return new CommandResult().message("Pump is in an error state, reading the error state resulted in the attached exception").exception(e);
|
return new CommandResult().message("Pump is in an error state, reading the error state resulted in the attached exception").exception(e);
|
||||||
}
|
}
|
||||||
|
@ -147,33 +147,32 @@ public class RuffyScripter {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cmdThread.start();
|
cmdThread.start();
|
||||||
}
|
|
||||||
|
|
||||||
// TODO really?
|
// TODO really?
|
||||||
long timeout = System.currentTimeMillis() + 90 * 1000;
|
long timeout = System.currentTimeMillis() + 90 * 1000;
|
||||||
while (activeCmd != null) {
|
while (activeCmd != null) {
|
||||||
SystemClock.sleep(500);
|
SystemClock.sleep(500);
|
||||||
log.trace("Waiting for running command to complete");
|
log.trace("Waiting for running command to complete");
|
||||||
if (System.currentTimeMillis() > timeout) {
|
if (System.currentTimeMillis() > timeout) {
|
||||||
log.error("Running command " + activeCmd + " timed out");
|
log.error("Running command " + activeCmd + " timed out");
|
||||||
cmdThread.interrupt();
|
cmdThread.interrupt();
|
||||||
activeCmd = null;
|
activeCmd = null;
|
||||||
cmdResult = null;
|
return new CommandResult().success(false).enacted(false).message("Command timed out");
|
||||||
return new CommandResult().success(false).enacted(false).message("Command timed out");
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (cmdResult.state == null) {
|
if (cmdResult.state == null) {
|
||||||
cmdResult.state = readPumpState();
|
cmdResult.state = readPumpState();
|
||||||
|
}
|
||||||
|
log.debug("Command result: " + cmdResult);
|
||||||
|
return cmdResult;
|
||||||
|
} catch (CommandException e) {
|
||||||
|
return e.toCommandResult();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy");
|
||||||
|
} finally {
|
||||||
|
activeCmd = null;
|
||||||
}
|
}
|
||||||
log.debug("Command result: " + cmdResult);
|
|
||||||
CommandResult r = cmdResult;
|
|
||||||
cmdResult = null;
|
|
||||||
return r;
|
|
||||||
} catch (CommandException e) {
|
|
||||||
return e.toCommandResult();
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +287,7 @@ public class RuffyScripter {
|
||||||
if (System.currentTimeMillis() > timeout) {
|
if (System.currentTimeMillis() > timeout) {
|
||||||
throw new CommandException().message("Timeout waiting for menu " + menuType + " to be left");
|
throw new CommandException().message("Timeout waiting for menu " + menuType + " to be left");
|
||||||
}
|
}
|
||||||
SystemClock.sleep(50);
|
SystemClock.sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,16 +311,31 @@ public class RuffyScripter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private PumpState readPumpState() {
|
private PumpState readPumpState() {
|
||||||
verifyMenuIsDisplayed(MenuType.MAIN_MENU);
|
|
||||||
PumpState state = new PumpState();
|
PumpState state = new PumpState();
|
||||||
Double tbrPercentage = (Double) currentMenu.getAttribute(MenuAttribute.TBR);
|
Menu menu = this.currentMenu;
|
||||||
if (tbrPercentage != 100) {
|
MenuType menuType = menu.getType();
|
||||||
state.tbrActive = true;
|
if (menuType == MenuType.MAIN_MENU) {
|
||||||
Double displayedTbr = (Double) currentMenu.getAttribute(MenuAttribute.TBR);
|
Double tbrPercentage = (Double) menu.getAttribute(MenuAttribute.TBR);
|
||||||
state.tbrPercent = displayedTbr.intValue();
|
if (tbrPercentage != 100) {
|
||||||
MenuTime durationMenuTime = ((MenuTime) currentMenu.getAttribute(MenuAttribute.RUNTIME));
|
state.tbrActive = true;
|
||||||
state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute();
|
Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR);
|
||||||
state.tbrRate = ((double) currentMenu.getAttribute(MenuAttribute.BASAL_RATE));
|
state.tbrPercent = displayedTbr.intValue();
|
||||||
|
MenuTime durationMenuTime = ((MenuTime) menu.getAttribute(MenuAttribute.RUNTIME));
|
||||||
|
state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute();
|
||||||
|
state.tbrRate = ((double) menu.getAttribute(MenuAttribute.BASAL_RATE));
|
||||||
|
}
|
||||||
|
} else if (menuType == MenuType.WARNING_OR_ERROR) {
|
||||||
|
state.isErrorOrWarning = true;
|
||||||
|
state.errorMsg = (String) menu.getAttribute(MenuAttribute.MESSAGE);
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (MenuAttribute menuAttribute : menu.attributes()) {
|
||||||
|
sb.append(menuAttribute);
|
||||||
|
sb.append(": ");
|
||||||
|
sb.append(menu.getAttribute(menuAttribute));
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
state.errorMsg = "Pump is on menu " + menuType + ", listing attributes: \n" + sb.toString();
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
package de.jotomo.ruffyscripter.commands;
|
package de.jotomo.ruffyscripter.commands;
|
||||||
|
|
||||||
public class NoOpCommand {
|
import de.jotomo.ruffyscripter.RuffyScripter;
|
||||||
|
|
||||||
|
public class NoOpCommand implements Command {
|
||||||
|
@Override
|
||||||
|
public CommandResult execute(RuffyScripter ruffyScripter) {
|
||||||
|
return new CommandResult().success(true).enacted(false).message("Returning pump state only");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "NoOpCommand{}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ public class PumpState {
|
||||||
public Date timestamp = new Date();
|
public Date timestamp = new Date();
|
||||||
public boolean tbrActive = false;
|
public boolean tbrActive = false;
|
||||||
public int tbrPercent = -1;
|
public int tbrPercent = -1;
|
||||||
|
public double tbrRate = -1;
|
||||||
public int tbrRemainingDuration = -1;
|
public int tbrRemainingDuration = -1;
|
||||||
public boolean isErrorOrWarning = false;
|
public boolean isErrorOrWarning = false;
|
||||||
public String errorMsg;
|
public String errorMsg;
|
||||||
|
@ -23,6 +24,11 @@ public class PumpState {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PumpState tbrRate(double tbrRate) {
|
||||||
|
this.tbrRate = tbrRate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PumpState tbrRemainingDuration(int tbrRemainingDuration) {
|
public PumpState tbrRemainingDuration(int tbrRemainingDuration) {
|
||||||
this.tbrRemainingDuration = tbrRemainingDuration;
|
this.tbrRemainingDuration = tbrRemainingDuration;
|
||||||
return this;
|
return this;
|
||||||
|
@ -43,6 +49,7 @@ public class PumpState {
|
||||||
return "PumpState{" +
|
return "PumpState{" +
|
||||||
"tbrActive=" + tbrActive +
|
"tbrActive=" + tbrActive +
|
||||||
", tbrPercent=" + tbrPercent +
|
", tbrPercent=" + tbrPercent +
|
||||||
|
", tbrRate=" + tbrRate +
|
||||||
", tbrRemainingDuration=" + tbrRemainingDuration +
|
", tbrRemainingDuration=" + tbrRemainingDuration +
|
||||||
", isErrorOrWarning=" + isErrorOrWarning +
|
", isErrorOrWarning=" + isErrorOrWarning +
|
||||||
", errorMsg=" + errorMsg +
|
", errorMsg=" + errorMsg +
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.squareup.otto.Subscribe;
|
import com.squareup.otto.Subscribe;
|
||||||
|
@ -33,11 +34,15 @@ public class ComboFragment extends Fragment {
|
||||||
return comboPlugin;
|
return comboPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EditText statusText;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.combopump_fragment, container, false);
|
View view = inflater.inflate(R.layout.combopump_fragment, container, false);
|
||||||
|
|
||||||
|
statusText = (EditText) view.findViewById(R.id.comboStatusEditText);
|
||||||
|
|
||||||
updateGUI();
|
updateGUI();
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
@ -65,9 +70,17 @@ public class ComboFragment extends Fragment {
|
||||||
activity.runOnUiThread(new Runnable() {
|
activity.runOnUiThread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (getPlugin() == null) {
|
||||||
// your rendering code here
|
statusText.setText("Initializing");
|
||||||
|
} else {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(getPlugin().statusSummary);
|
||||||
|
if (getPlugin().pumpState != null) {
|
||||||
|
sb.append("\n\n");
|
||||||
|
sb.append(getPlugin().pumpState.toString().replaceAll(",", "\n"));
|
||||||
|
}
|
||||||
|
statusText.setText(sb.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import de.jotomo.ruffyscripter.commands.BolusCommand;
|
||||||
import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
|
import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
|
||||||
import de.jotomo.ruffyscripter.commands.Command;
|
import de.jotomo.ruffyscripter.commands.Command;
|
||||||
import de.jotomo.ruffyscripter.commands.CommandResult;
|
import de.jotomo.ruffyscripter.commands.CommandResult;
|
||||||
import de.jotomo.ruffyscripter.commands.ReadStateCommand;
|
import de.jotomo.ruffyscripter.commands.NoOpCommand;
|
||||||
import de.jotomo.ruffyscripter.commands.SetTbrCommand;
|
import de.jotomo.ruffyscripter.commands.SetTbrCommand;
|
||||||
import de.jotomo.ruffyscripter.commands.PumpState;
|
import de.jotomo.ruffyscripter.commands.PumpState;
|
||||||
import info.nightscout.androidaps.BuildConfig;
|
import info.nightscout.androidaps.BuildConfig;
|
||||||
|
@ -36,11 +36,11 @@ import info.nightscout.androidaps.data.PumpEnactResult;
|
||||||
import info.nightscout.androidaps.db.Source;
|
import info.nightscout.androidaps.db.Source;
|
||||||
import info.nightscout.androidaps.db.TemporaryBasal;
|
import info.nightscout.androidaps.db.TemporaryBasal;
|
||||||
import info.nightscout.androidaps.events.EventAppExit;
|
import info.nightscout.androidaps.events.EventAppExit;
|
||||||
import info.nightscout.androidaps.events.EventPumpStatusChanged;
|
|
||||||
import info.nightscout.androidaps.interfaces.PluginBase;
|
import info.nightscout.androidaps.interfaces.PluginBase;
|
||||||
import info.nightscout.androidaps.interfaces.PumpDescription;
|
import info.nightscout.androidaps.interfaces.PumpDescription;
|
||||||
import info.nightscout.androidaps.interfaces.PumpInterface;
|
import info.nightscout.androidaps.interfaces.PumpInterface;
|
||||||
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
|
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
|
||||||
|
import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;
|
||||||
import info.nightscout.utils.DateUtil;
|
import info.nightscout.utils.DateUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +59,9 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
private ServiceConnection mRuffyServiceConnection;
|
private ServiceConnection mRuffyServiceConnection;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private volatile PumpState pumpState;
|
volatile PumpState pumpState;
|
||||||
|
|
||||||
|
volatile String statusSummary = "Initializing";
|
||||||
|
|
||||||
private static PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult();
|
private static PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult();
|
||||||
|
|
||||||
|
@ -96,6 +98,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
ruffyScripter = new RuffyScripter(IRuffyService.Stub.asInterface(service));
|
ruffyScripter = new RuffyScripter(IRuffyService.Stub.asInterface(service));
|
||||||
log.debug("ruffy serivce connected");
|
log.debug("ruffy serivce connected");
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runCommand(new NoOpCommand());
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -299,18 +307,27 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandResult runCommand(Command command) {
|
private CommandResult runCommand(Command command) {
|
||||||
// TODO use this to dispatch methods to a service thread, like DanaRs executionService
|
synchronized (this) {
|
||||||
// will be required when doing multiple commands in sequence.
|
// TODO use this to dispatch methods to a service thread, like DanaRs executionService
|
||||||
// Alternatively provide 'composite commands' to return everything needed in one go?
|
// will be required when doing multiple commands in sequence.
|
||||||
try {
|
// Alternatively provide 'composite commands' to return everything needed in one go?
|
||||||
CommandResult commandResult = ruffyScripter.runCommand(command);
|
try {
|
||||||
if (commandResult.success && commandResult.state != null) {
|
statusSummary = "Busy running " + command;
|
||||||
pumpState = commandResult.state;
|
pumpState = null;
|
||||||
|
MainApp.bus().post(new EventComboPumpUpdateGUI());
|
||||||
|
CommandResult commandResult = ruffyScripter.runCommand(command);
|
||||||
|
if (commandResult.success && commandResult.state != null) {
|
||||||
|
pumpState = commandResult.state;
|
||||||
|
}
|
||||||
|
return commandResult;
|
||||||
|
} finally {
|
||||||
|
lastCmdTime = new Date();
|
||||||
|
statusSummary = pumpState != null && !pumpState.isErrorOrWarning
|
||||||
|
? "Idle"
|
||||||
|
: "Error: " + pumpState.errorMsg;
|
||||||
|
ruffyScripter.disconnect();
|
||||||
|
MainApp.bus().post(new EventComboPumpUpdateGUI());
|
||||||
}
|
}
|
||||||
return commandResult;
|
|
||||||
} finally {
|
|
||||||
lastCmdTime = new Date();
|
|
||||||
ruffyScripter.disconnect();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,7 +375,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
log.debug("Rounded requested percentage from " + percent + " to " + rounded);
|
log.debug("Rounded requested percentage from " + percent + " to " + rounded);
|
||||||
percent = rounded;
|
percent = rounded;
|
||||||
}
|
}
|
||||||
MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.settingtempbasal)));
|
|
||||||
CommandResult commandResult = runCommand(new SetTbrCommand(percent, durationInMinutes));
|
CommandResult commandResult = runCommand(new SetTbrCommand(percent, durationInMinutes));
|
||||||
if (commandResult.enacted) {
|
if (commandResult.enacted) {
|
||||||
TemporaryBasal tempStart = new TemporaryBasal(System.currentTimeMillis());
|
TemporaryBasal tempStart = new TemporaryBasal(System.currentTimeMillis());
|
||||||
|
@ -387,10 +403,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
return OPERATION_NOT_SUPPORTED;
|
return OPERATION_NOT_SUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO untested, probably not working
|
||||||
@Override
|
@Override
|
||||||
public PumpEnactResult cancelTempBasal() {
|
public PumpEnactResult cancelTempBasal() {
|
||||||
log.debug("cancelTempBasal called");
|
log.debug("cancelTempBasal called");
|
||||||
MainApp.bus().post(new EventPumpStatusChanged(MainApp.sResources.getString(R.string.stoppingtempbasal)));
|
|
||||||
CommandResult commandResult = runCommand(new CancelTbrCommand());
|
CommandResult commandResult = runCommand(new CancelTbrCommand());
|
||||||
if (commandResult.enacted) {
|
if (commandResult.enacted) {
|
||||||
TemporaryBasal tempStop = new TemporaryBasal(System.currentTimeMillis());
|
TemporaryBasal tempStop = new TemporaryBasal(System.currentTimeMillis());
|
||||||
|
@ -422,13 +438,20 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
JSONObject status = new JSONObject();
|
JSONObject status = new JSONObject();
|
||||||
JSONObject extended = new JSONObject();
|
JSONObject extended = new JSONObject();
|
||||||
try {
|
try {
|
||||||
status.put("status", "normal");
|
status.put("status", statusSummary);
|
||||||
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
|
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
|
||||||
try {
|
try {
|
||||||
extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
|
extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
}
|
}
|
||||||
status.put("timestamp", DateUtil.toISOString(new Date()));
|
status.put("timestamp", lastCmdTime);
|
||||||
|
|
||||||
|
if (pumpState != null) {
|
||||||
|
extended.put("TempBasalAbsoluteRate", pumpState.tbrRate);
|
||||||
|
// TODO best guess at this point ...
|
||||||
|
extended.put("TempBasalStart", DateUtil.dateAndTimeString(System.currentTimeMillis() - (pumpState.tbrRemainingDuration - 15 * 60 * 1000)));
|
||||||
|
extended.put("TempBasalRemaining", pumpState.tbrRemainingDuration);
|
||||||
|
}
|
||||||
|
|
||||||
// more info here .... look at dana plugin
|
// more info here .... look at dana plugin
|
||||||
|
|
||||||
|
@ -454,7 +477,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String shortStatus(boolean veryShort) {
|
public String shortStatus(boolean veryShort) {
|
||||||
return deviceID();
|
return statusSummary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,4 +17,12 @@
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/comboStatusEditText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:ems="10"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:layout_gravity="center_horizontal"/>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
Loading…
Add table
Reference in a new issue