package de.blau.android.imageryoffset; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import android.annotation.SuppressLint; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.net.Uri; import android.os.AsyncTask; import android.support.design.widget.FloatingActionButton; import android.support.v4.view.MenuItemCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog.Builder; import android.support.v7.app.AppCompatDialog; import android.support.v7.view.ActionMode; import android.support.v7.view.ActionMode.Callback; import android.support.v7.widget.ActionMenuView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; import android.widget.EditText; import android.widget.TextView; import de.blau.android.App; import de.blau.android.HelpViewer; import de.blau.android.Main; import de.blau.android.Map; import de.blau.android.Mode; import de.blau.android.PostAsyncActionHandler; import de.blau.android.R; import de.blau.android.dialogs.Progress; import de.blau.android.osm.BoundingBox; import de.blau.android.osm.Server; import de.blau.android.prefs.Preferences; import de.blau.android.resources.TileLayerServer; import de.blau.android.util.DateFormatter; import de.blau.android.util.GeoMath; import de.blau.android.util.MenuUtil; import de.blau.android.util.NetworkStatus; import de.blau.android.util.Offset; import de.blau.android.util.Snack; import de.blau.android.util.ThemeUtils; public class BackgroundAlignmentActionModeCallback implements Callback { private final static String DEBUG_TAG = "BackgroundAlign..."; private static final int MENUITEM_QUERYDB = 1; private static final int MENUITEM_QUERYLOCAL = 2; private static final int MENUITEM_APPLY2ALL = 3; private static final int MENUITEM_RESET = 4; private static final int MENUITEM_ZERO = 5; private static final int MENUITEM_SAVE2DB = 6; private static final int MENUITEM_SAVELOCAL = 7; private static final int MENUITEM_HELP = 8; private Mode oldMode; private final Preferences prefs; private final Uri offsetServerUri; private Offset[] oldOffsets; private TileLayerServer osmts; private final Map map; private final Main main; private ArrayList<ImageryOffset> offsetList; private ActionMenuView cabBottomBar; public BackgroundAlignmentActionModeCallback(Main main, Mode oldMode) { this.oldMode = oldMode; this.main = main; // currently we are only called from here map = main.getMap(); osmts = map.getOpenStreetMapTilesOverlay().getRendererInfo(); oldOffsets = osmts.getOffsets().clone(); prefs = new Preferences(main); String offsetServer = prefs.getOffsetServer(); offsetServerUri = Uri.parse(offsetServer); } @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { FloatingActionButton lock = main.getLock(); if (lock != null) { lock.hide(); } mode.setTitle(R.string.menu_tools_background_align); if (main.getBottomBar() != null) { main.hideBottomBar(); View v = main.findViewById(R.id.cab_stub); if (v instanceof ViewStub) { // only need to inflate once ViewStub stub = (ViewStub) v; stub.setLayoutResource(R.layout.toolbar); stub.setInflatedId(R.id.cab_stub); cabBottomBar = (ActionMenuView) stub.inflate(); } else if (v instanceof ActionMenuView) { cabBottomBar = (ActionMenuView) v; cabBottomBar.setVisibility(View.VISIBLE); cabBottomBar.getMenu().clear(); } } return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { if (cabBottomBar!=null) { menu = cabBottomBar.getMenu(); final ActionMode actionMode = mode; android.support.v7.widget.ActionMenuView.OnMenuItemClickListener listener = new android.support.v7.widget.ActionMenuView.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { return onActionItemClicked(actionMode,item); } }; cabBottomBar.setOnMenuItemClickListener(listener); MenuUtil.setupBottomBar(main, cabBottomBar, main.isFullScreen(), prefs.lightThemeEnabled()); } menu.clear(); MenuItem mi = menu.add(Menu.NONE, MENUITEM_QUERYDB, Menu.NONE, R.string.menu_tools_background_align_retrieve_from_db).setEnabled(NetworkStatus.isConnected(main)) .setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_download)); MenuItemCompat.setShowAsAction(mi,MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); // menu.add(Menu.NONE, MENUITEM_QUERYLOCAL, Menu.NONE, R.string.menu_tools_background_align_retrieve_from_device); mi = menu.add(Menu.NONE, MENUITEM_RESET, Menu.NONE, R.string.menu_tools_background_align_reset) .setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_undo)); MenuItemCompat.setShowAsAction(mi,MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); menu.add(Menu.NONE, MENUITEM_ZERO, Menu.NONE, R.string.menu_tools_background_align_zero); menu.add(Menu.NONE, MENUITEM_APPLY2ALL, Menu.NONE, R.string.menu_tools_background_align_apply2all); menu.add(Menu.NONE, MENUITEM_SAVE2DB, Menu.NONE, R.string.menu_tools_background_align_save_db).setEnabled(NetworkStatus.isConnected(main)); // menu.add(Menu.NONE, MENUITEM_SAVELOCAL, Menu.NONE, R.string.menu_tools_background_align_save_device); menu.add(Menu.NONE, MENUITEM_HELP, Menu.NONE, R.string.menu_help); // Toolbar toolbar = (Toolbar) Application.mainActivity.findViewById(R.id.mainToolbar); // toolbar.setVisibility(View.GONE);; return true; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case MENUITEM_ZERO: osmts.setOffset(map.getZoomLevel(),0.0d,0.0d); map.invalidate(); break; case MENUITEM_RESET: osmts.setOffsets(oldOffsets.clone()); map.invalidate(); break; case MENUITEM_APPLY2ALL: Offset o = osmts.getOffset(map.getZoomLevel()); if (o != null) osmts.setOffset(o.lon,o.lat); else osmts.setOffset(0.0d,0.0d); break; case MENUITEM_QUERYDB: getOffsetFromDB(); break; case MENUITEM_QUERYLOCAL: break; case MENUITEM_SAVE2DB: saveOffsetsToDB(); break; case MENUITEM_SAVELOCAL: break; case MENUITEM_HELP: HelpViewer.start(main, R.string.help_aligningbackgroundiamgery); return true; default: return false; } return true; } /** * Download offsets * @author simon * */ private class OffsetLoader extends AsyncTask<Double, Void, ArrayList<ImageryOffset>> { String error = null; final PostAsyncActionHandler handler; OffsetLoader(final PostAsyncActionHandler postLoadHandler) { handler = postLoadHandler; } ArrayList<ImageryOffset> getOffsetList(double lat, double lon, int radius) { Uri.Builder uriBuilder = offsetServerUri.buildUpon() .appendPath("get") .appendQueryParameter("lat", String.valueOf(lat)) .appendQueryParameter("lon", String.valueOf(lon)); if (radius > 0) { uriBuilder.appendQueryParameter("radius", String.valueOf(radius)); } uriBuilder.appendQueryParameter("imagery", osmts.getImageryOffsetId()) .appendQueryParameter("format", "json"); String urlString = uriBuilder.build().toString(); try { Log.d(DEBUG_TAG, "urlString " + urlString); URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("User-Agent", App.userAgent); JsonReader reader = new JsonReader(new InputStreamReader(conn.getInputStream())); ArrayList<ImageryOffset> result = new ArrayList<ImageryOffset>(); try { try { JsonToken token = reader.peek(); if (token.equals(JsonToken.BEGIN_ARRAY)) { reader.beginArray(); while (reader.hasNext()) { ImageryOffset imOffset = readOffset(reader); if (imOffset != null && imOffset.deprecated == null) //TODO handle deprecated result.add(imOffset); } reader.endArray(); } else if (token.equals(JsonToken.BEGIN_OBJECT)) { reader.beginObject(); while (reader.hasNext()) { String jsonName = reader.nextName(); if (jsonName.equals("error")) { error = reader.nextString(); Log.d(DEBUG_TAG, "search error " + error); } else { reader.skipValue(); } } return null; } // can't happen ? } catch (IOException e) { error = e.getMessage(); } catch (IllegalStateException e) { error = e.getMessage(); } if(error != null) { Log.d(DEBUG_TAG, "search error " + error); } return result; } finally { try { reader.close(); } catch (IOException ioex) { Log.d(DEBUG_TAG,"Ignoring " + ioex); } } } catch (MalformedURLException e) { error = e.getMessage(); } catch (IOException e) { error = e.getMessage(); } Log.d(DEBUG_TAG, "search error " + error); return null; } @Override protected void onPreExecute() { Progress.showDialog(main, Progress.PROGRESS_SEARCHING); } @Override protected ArrayList<ImageryOffset> doInBackground(Double... params) { if (params.length != 3) { Log.e(DEBUG_TAG, "wrong number of params in OffsetLoader " + params.length); return null; } double centerLat = params[0]; double centerLon = params[1]; int radius = (int) (params[2] == null ? 0 : params[2]); ArrayList<ImageryOffset> result = getOffsetList(centerLat, centerLon, radius); if (result == null || result.size() == 0) { // retry with max radius Log.d(DEBUG_TAG, "retrying search with max radius"); result = getOffsetList(centerLat, centerLon, 0); } return result; } @Override protected void onPostExecute(ArrayList<ImageryOffset> res) { Progress.dismissDialog(main, Progress.PROGRESS_SEARCHING); offsetList = res; if (handler != null) { handler.onSuccess(); } } String getError() { return error; } } /** * Save offsets * @author simon * */ private class OffsetSaver extends AsyncTask<ImageryOffset, Void, Integer> { String error = null; @Override protected void onPreExecute() { Progress.showDialog(main, Progress.PROGRESS_SAVING); } @Override protected Integer doInBackground(ImageryOffset... params) { try { ImageryOffset offset = params[0]; String urlString = offset.toSaveUrl(); Log.d(DEBUG_TAG,"urlString " + urlString); URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("User-Agent", App.userAgent); InputStream is = conn.getInputStream(); return conn.getResponseCode(); } catch (MalformedURLException e) { error = e.getMessage(); return -2; } catch (Exception /*IOException*/ e) { error = e.getMessage(); return -1; } } @Override protected void onPostExecute(Integer res) { Progress.dismissDialog(main, Progress.PROGRESS_SAVING); if (res == 200) { Snack.barInfo(main, R.string.toast_save_done); } else { Snack.barError(main, R.string.toast_save_failed); } } String getError() { return error; } } /** * Get offset from server. */ private void getOffsetFromDB() { // first try for our view box final BoundingBox bbox = map.getViewBox(); final double centerLat = bbox.getCenterLat(); final double centerLon = (bbox.getLeft() + bbox.getWidth()/2)/1E7d; final Comparator<ImageryOffset> cmp = new Comparator<ImageryOffset>() { @Override public int compare(ImageryOffset offset1, ImageryOffset offset2) { double d1 = GeoMath.haversineDistance(centerLon, centerLat, offset1.lon, offset1.lat); double d2 = GeoMath.haversineDistance(centerLon, centerLat, offset2.lon, offset2.lat); return Double.valueOf(d1).compareTo(Double.valueOf(d2)); } }; PostAsyncActionHandler handler = new PostAsyncActionHandler() { @Override public void onSuccess() { if (offsetList != null && offsetList.size() > 0) { Collections.sort(offsetList, cmp); AppCompatDialog d = createDisplayOffsetDialog(0); d.show(); } else { displayError(main.getString(R.string.imagery_offset_not_found)); } } @Override public void onError() { } }; OffsetLoader loader = new OffsetLoader(handler); double hm = GeoMath.haversineDistance(centerLon, bbox.getBottom()/1E7d, centerLon, bbox.getTop()/1E7d); double wm = GeoMath.haversineDistance(bbox.getLeft()/1E7d, centerLat, bbox.getRight()/1E7d, centerLat); int radius = (int)Math.max(1, Math.round(Math.min(hm,wm)/2000d)); // convert to km and make it at least 1 and /2 for radius loader.execute(centerLat,centerLon,Double.valueOf(radius)); } private void saveOffsetsToDB() { Offset[] offsets = osmts.getOffsets(); // current offset ArrayList<ImageryOffset> offsetList = new ArrayList<ImageryOffset>(); final BoundingBox bbox = map.getViewBox(); Offset lastOffset = null; ImageryOffset im = null; String author = null; String error = null; // try to find current display name final Server server = prefs.getServer(); if (server != null) { if (!server.needOAuthHandshake()) { try { AsyncTask<Void,Void,Server.UserDetails> loader = new AsyncTask<Void,Void,Server.UserDetails>() { @Override protected Server.UserDetails doInBackground(Void... params) { return server.getUserDetails(); } }; loader.execute(); Server.UserDetails user = loader.get(10, TimeUnit.SECONDS); if (user != null) { author = user.display_name; } else { author = server.getDisplayName(); // maybe it has been configured } } catch (InterruptedException e) { error = e.getMessage(); } catch (ExecutionException e) { error = e.getMessage(); } catch (TimeoutException e) { error = main.getString(R.string.toast_timeout); } displayError(error); error=null; } else { author = server.getDisplayName(); // maybe it has been configured } } for (int z = 0; z < offsets.length; z++) { // iterate through the list and generate a new offset when necessary Offset o = offsets[z]; if (o != null && (o.lon != 0 || o.lat !=0)) { // non-null zoom if (lastOffset != null && im != null) { if (lastOffset.lon == o.lon && lastOffset.lat == o.lat) { im.maxZoom++; lastOffset = o; continue; } } im = new ImageryOffset(); im.lon = (bbox.getLeft() + bbox.getWidth()/2)/1E7d; im.lat = bbox.getCenterLat(); im.imageryLon = im.lon - o.lon; im.imageryLat = im.lat - o.lat; im.minZoom = z + osmts.getMinZoomLevel(); im.maxZoom = im.minZoom; Calendar c = Calendar.getInstance(); im.date = DateFormatter.getFormattedString( ImageryOffset.DATE_PATTERN_IMAGERY_OFFSET_CREATED_AT, c.getTime()); im.author = author; offsetList.add(im); } lastOffset = o; } if (offsetList.size() > 0) { AppCompatDialog d = createSaveOffsetDialog(0, offsetList); d.show(); } } private void displayError(String error) { if (error != null) { // try to avoid code dup AlertDialog.Builder builder = new AlertDialog.Builder(main); builder.setMessage(error).setTitle(R.string.imagery_offset_title); builder.setPositiveButton(R.string.okay, null); AlertDialog dialog = builder.create(); dialog.show(); } } private ImageryOffset readOffset(JsonReader reader) throws IOException { String type = null; ImageryOffset result = new ImageryOffset(); reader.beginObject(); while (reader.hasNext()) { String jsonName = reader.nextName(); if (jsonName.equals("type")) { type = reader.nextString(); } else if (jsonName.equals("id")) { result.id = reader.nextLong(); } else if (jsonName.equals("lat")) { result.lat = reader.nextDouble(); } else if (jsonName.equals("lon")) { result.lon = reader.nextDouble(); } else if (jsonName.equals("author")) { result.author = reader.nextString(); } else if (jsonName.equals("date")) { result.date = reader.nextString(); } else if (jsonName.equals("imagery")) { result.imageryId = reader.nextString(); } else if (jsonName.equals("imlat")) { result.imageryLat = reader.nextDouble(); } else if (jsonName.equals("imlon")) { result.imageryLon = reader.nextDouble(); } else if (jsonName.equals("min-zoom")) { result.minZoom = reader.nextInt(); } else if (jsonName.equals("max-zoom")) { result.maxZoom = reader.nextInt(); } else if (jsonName.equals("description")) { result.description = reader.nextString(); } else if (jsonName.equals("deprecated")) { result.deprecated = readDeprecated(reader); }else { reader.skipValue(); } } reader.endObject(); if ("offset".equals(type)) return result; return null; } private DeprecationNote readDeprecated(JsonReader reader) throws IOException { DeprecationNote result = new DeprecationNote(); reader.beginObject(); while (reader.hasNext()) { String jsonName = reader.nextName(); if (jsonName.equals("author")) { result.author = reader.nextString(); } else if (jsonName.equals("reason")) { result.reason = reader.nextString(); }else if (jsonName.equals("date")) { result.date = reader.nextString(); } else{ reader.skipValue(); } } reader.endObject(); return result; } /** * Object to hold the output from the imagery DB see https://wiki.openstreetmap.org/wiki/Imagery_Offset_Database/API * @author simon * */ private class ImageryOffset { @SuppressWarnings("unused") long id; double lat = 0; double lon = 0; String author; String description; String date; String imageryId; int minZoom = 0; int maxZoom = 18; double imageryLat = 0; double imageryLon = 0; DeprecationNote deprecated = null; /** * Date pattern used to describe when the imagery offset was created. */ static final String DATE_PATTERN_IMAGERY_OFFSET_CREATED_AT = "yyyy-MM-dd"; public String toSaveUrl() { Uri uriBuilder = offsetServerUri.buildUpon() .appendPath("store") .appendQueryParameter("lat", String.format(Locale.US, "%.7f", lat)) .appendQueryParameter("lon", String.format(Locale.US, "%.7f", lon)) .appendQueryParameter("author", author) .appendQueryParameter("description", description) .appendQueryParameter("imagery", imageryId) .appendQueryParameter("imlat", String.format(Locale.US, "%.7f", imageryLat)) .appendQueryParameter("imlon", String.format(Locale.US, "%.7f", imageryLon)) .build(); return uriBuilder.toString(); } } /** * Object to hold the output from the imagery DB see https://wiki.openstreetmap.org/wiki/Imagery_Offset_Database/API * Currently we don't actually display the contents anywhere * @author simon * */ private class DeprecationNote { @SuppressWarnings("unused") String author; @SuppressWarnings("unused") String date; @SuppressWarnings("unused") String reason; } /** * Create the imagery offset display/apply dialog ... given that it has so much logic, done here instead of DialogFactory * @param index * @return */ @SuppressLint("InflateParams") private AppCompatDialog createSaveOffsetDialog(final int index, final ArrayList<ImageryOffset> saveOffsetList) { // Create some useful objects // final BoundingBox bbox = map.getViewBox(); final LayoutInflater inflater = ThemeUtils.getLayoutInflater(main); Builder dialog = new AlertDialog.Builder(main); dialog.setTitle(R.string.imagery_offset_title); final ImageryOffset offset = saveOffsetList.get(index); View layout = inflater.inflate(R.layout.save_imagery_offset, null); dialog.setView(layout); EditText description = (EditText) layout.findViewById(R.id.imagery_offset_description); EditText author = (EditText) layout.findViewById(R.id.imagery_offset_author); if (offset.author != null) { author.setText(offset.author); } TextView off = (TextView) layout.findViewById(R.id.imagery_offset_offset); off.setText(String.format(Locale.US,"%.2f",GeoMath.haversineDistance(offset.lon,offset.lat,offset.imageryLon,offset.imageryLat))+" meters"); if (offset.date != null) { TextView created = (TextView) layout.findViewById(R.id.imagery_offset_date); created.setText(offset.date); } TextView minmax = (TextView) layout.findViewById(R.id.imagery_offset_zoom); minmax.setText(offset.minZoom + "-" + offset.maxZoom); dialog.setPositiveButton(R.string.menu_tools_background_align_save_db, createSaveButtonListener(description, author, index, saveOffsetList)); if (index == (saveOffsetList.size() - 1)) dialog.setNegativeButton(R.string.cancel, null); else dialog.setNegativeButton(R.string.next, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { AppCompatDialog d = createSaveOffsetDialog(index+1,saveOffsetList); d.show(); } }); return dialog.create(); } /** * Create an onClick listener that saves the current offset to the offset DB and (if it exists) displays the next offset to be saved * @param description desciption of the offset in question * @param author author * @param index index in the list * @return the OnClickListnener */ private OnClickListener createSaveButtonListener(final EditText description, final EditText author, final int index, final ArrayList<ImageryOffset> saveOffsetList) { return new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String error = null; ImageryOffset offset = saveOffsetList.get(index); if (offset == null) return; offset.description = description.getText().toString(); offset.author = author.getText().toString(); offset.imageryId = osmts.getImageryOffsetId(); Log.d("Background...",offset.toSaveUrl()); OffsetSaver saver = new OffsetSaver(); saver.execute(offset); try { int result = saver.get(); if (result < 0) { error = saver.getError(); } } catch (InterruptedException e) { error = e.getMessage(); } catch (ExecutionException e) { error = e.getMessage(); } if (error != null) { displayError(error); return; // don't continue is something went wrong } if (index < (saveOffsetList.size()-1)) { // save retyping if it stays the same saveOffsetList.get(index+1).description = offset.description; saveOffsetList.get(index+1).author = offset.author; AppCompatDialog d = createSaveOffsetDialog(index+1,saveOffsetList); d.show(); } } }; } @SuppressLint("InflateParams") private AppCompatDialog createDisplayOffsetDialog(final int index) { // Create some useful objects final BoundingBox bbox = map.getViewBox(); final LayoutInflater inflater = ThemeUtils.getLayoutInflater(main); Builder dialog = new AlertDialog.Builder(main); dialog.setTitle(R.string.imagery_offset_title); final ImageryOffset offset = offsetList.get(index); View layout = inflater.inflate(R.layout.imagery_offset, null); dialog.setView(layout); if (offset.description != null) { TextView description = (TextView) layout.findViewById(R.id.imagery_offset_description); description.setText(offset.description); } if (offset.author != null) { TextView author = (TextView) layout.findViewById(R.id.imagery_offset_author); author.setText(offset.author); } TextView off = (TextView) layout.findViewById(R.id.imagery_offset_offset); off.setText(String.format(Locale.US,"%.2f",GeoMath.haversineDistance(offset.lon,offset.lat,offset.imageryLon,offset.imageryLat))+" meters"); if (offset.date != null) { TextView created = (TextView) layout.findViewById(R.id.imagery_offset_date); created.setText(offset.date); } TextView minmax = (TextView) layout.findViewById(R.id.imagery_offset_zoom); minmax.setText(offset.minZoom + "-" + offset.maxZoom); TextView distance = (TextView) layout.findViewById(R.id.imagery_offset_distance); distance.setText(String.format(Locale.US,"%.3f",GeoMath.haversineDistance((bbox.getLeft() + bbox.getWidth()/2)/1E7d, bbox.getCenterLat(), offset.lon, offset.lat)/1000) +" km"); dialog.setPositiveButton(R.string.apply, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { osmts.setOffset(map.getZoomLevel(),offset.lon-offset.imageryLon, offset.lat-offset.imageryLat); map.invalidate(); } }); dialog.setNeutralButton(R.string.menu_tools_background_align_apply2all,new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { osmts.setOffset(offset.minZoom,offset.maxZoom,offset.lon-offset.imageryLon, offset.lat-offset.imageryLat); map.invalidate(); } }); if (index == (offsetList.size() - 1)) dialog.setNegativeButton(R.string.cancel, null); else dialog.setNegativeButton(R.string.next, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { AppCompatDialog d = createDisplayOffsetDialog(index+1); d.show(); } }); return dialog.create(); } @Override public void onDestroyActionMode(ActionMode mode) { if (cabBottomBar != null) { cabBottomBar.setVisibility(View.GONE); } main.showBottomBar(); main.setMode(main, oldMode); main.showLock(); } }