PageRenderTime 99ms CodeModel.GetById 2ms app.highlight 86ms RepoModel.GetById 1ms app.codeStats 0ms

/v3.2/nimbits-android/src/com/nimbits/android/activity/StartActivity.java

http://nimbits-server.googlecode.com/
Java | 1224 lines | 892 code | 275 blank | 57 comment | 129 complexity | 055a6724faa1350aa70ed276ef77277a MD5 | raw file
   1package com.nimbits.android.activity;
   2
   3import android.app.AlertDialog;
   4import android.app.Dialog;
   5import android.app.ListActivity;
   6import android.app.ProgressDialog;
   7import android.content.ContentValues;
   8import android.content.Context;
   9import android.content.DialogInterface;
  10import android.content.Intent;
  11import android.database.Cursor;
  12import android.database.sqlite.SQLiteDatabase;
  13import android.os.Bundle;
  14import android.os.Handler;
  15import android.os.Message;
  16import android.util.Log;
  17import android.view.*;
  18import android.view.GestureDetector.OnGestureListener;
  19import android.view.View.OnClickListener;
  20import android.widget.*;
  21import com.nimbits.android.CustomDialog;
  22import com.nimbits.android.DisplayType;
  23import com.nimbits.android.ImageCursorAdapter;
  24import com.nimbits.android.R;
  25import com.nimbits.android.account.OwnerAccountFactory;
  26import com.nimbits.android.dao.LocalDatabaseDaoFactory;
  27import com.nimbits.android.database.DatabaseHelperFactory;
  28import com.nimbits.android.json.GsonFactory;
  29import com.nimbits.client.model.Const;
  30import com.nimbits.client.model.category.Category;
  31import com.nimbits.client.model.category.CategoryName;
  32import com.nimbits.client.model.common.CommonFactoryLocator;
  33import com.nimbits.client.model.diagram.Diagram;
  34import com.nimbits.client.model.point.Point;
  35import com.nimbits.client.model.point.PointModel;
  36import com.nimbits.client.model.point.PointName;
  37import com.nimbits.client.model.value.Value;
  38import org.apache.http.cookie.Cookie;
  39
  40import java.io.UnsupportedEncodingException;
  41import java.util.List;
  42
  43
  44public class StartActivity extends ListActivity implements OnGestureListener {
  45
  46    private static final int LOAD_DIALOG = 0;
  47    private static final int POINT_DIALOG = 1;
  48    private static final int CHANGE_SERVER_DIALOG = 2;
  49    private static final int CHOOSE_SERVER_DIALOG = 3;
  50    private static final int CHECK_SERVER_DIALOG = 4;
  51    private static final int NO_DATA_DIALOG = 5;
  52
  53    private PopulateDatabaseThread populateDatabaseThread;
  54    private LoadPointDataThread loadPointDataThread;
  55    private AuthenticateThread authenticateThread;
  56
  57    private ProgressDialog populateDatabaseDialog;
  58    private ProgressDialog pointDialog;
  59    private ProgressDialog authenticateDialog;
  60
  61    private final Handler timerHandler = new Handler();
  62    private GestureDetector gestureScanner;
  63    private String baseURL;
  64    private Cookie authCookie;
  65
  66    private Cursor listCursor;
  67
  68    private static final int SWIPE_MIN_DISTANCE = 120;
  69    private static final int SWIPE_MAX_OFF_PATH = 250;
  70    private static final int SWIPE_THRESHOLD_VELOCITY = 200;
  71    private static String selectedServer = "";
  72    private String currentCategory = null;
  73
  74    private String buildPointDescription(final Point p, final Value v) {
  75        final StringBuilder b = new StringBuilder();
  76        if (v != null) {
  77            b.append(v.getValue());
  78
  79            if (p.getUnit() != null) {
  80                b.append(p.getUnit());
  81            }
  82            if (v.getNote() != null) {
  83                b.append(" ").append(v.getNote());
  84            }
  85            if (v.getValue() > p.getHighAlarm() && p.isHighAlarmOn()) {
  86                b.append(" " + Const.MESSAGE_HIGH_ALERT_ON);
  87
  88            }
  89            if (v.getValue() < p.getLowAlarm() && p.isLowAlarmOn()) {
  90                b.append(" " + Const.MESSAGE_LOW_ALERT_ON);
  91
  92            }
  93        } else {
  94            b.append(Const.MESSAGE_NO_DATA);
  95        }
  96        return b.toString();
  97
  98    }
  99
 100    private void updatePointValues() {
 101        Cursor c;
 102        String cat;
 103        if (currentCategory == null) {
 104            cat = Const.CONST_HIDDEN_CATEGORY;
 105        } else {
 106            cat = currentCategory;
 107        }
 108        SQLiteDatabase db1;
 109        db1 = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(true);
 110        c = db1.query(Const.ANDROID_TABLE_LEVEL_TWO_DISPLAY, new String[]{"_id", Const.ANDROID_COL_CATEGORY, Const.ANDROID_COL_DESCRIPTION, Const.ANDROID_COL_DISPLAY_TYPE, Const.ANDROID_COL_JSON}, Const.ANDROID_COL_CATEGORY + "='" + cat + "'", null, null, null, null);
 111        c.moveToFirst();
 112        while (!c.isAfterLast()) {
 113            PointName pointName = (PointName) CommonFactoryLocator.getInstance().createPointName(c.getString(c.getColumnIndex(Const.ANDROID_COL_CATEGORY)));
 114            String json = c.getString(c.getColumnIndex(Const.ANDROID_COL_JSON));
 115            Point p = GsonFactory.getInstance().fromJson(json, Point.class);
 116
 117            Value v = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).getCurrentRecordedValue(pointName);
 118            if (v != null) {
 119                ContentValues u = new ContentValues();
 120                u.put(Const.ANDROID_COL_DESCRIPTION, buildPointDescription(p, v));
 121                if (v.getValue() > p.getHighAlarm() && p.isHighAlarmOn()) {
 122                    u.put(Const.ANDROID_COL_DISPLAY_TYPE, 3);
 123                } else if (v.getValue() < p.getLowAlarm() && p.isLowAlarmOn()) {
 124                    u.put(Const.ANDROID_COL_DISPLAY_TYPE, 4);
 125                } else {
 126                    u.put(Const.ANDROID_COL_DISPLAY_TYPE, 2);
 127                }
 128                db1.update(Const.ANDROID_TABLE_LEVEL_TWO_DISPLAY, u, Const.ANDROID_COL_ID + "=?", new String[]{Long.toString(c.getLong(c.getColumnIndex("_id")))});
 129                db1.update(Const.ANDROID_TABLE_LEVEL_ONE_DISPLAY, u, Const.ANDROID_COL_NAME + "=?", new String[]{pointName.getValue()});
 130            }
 131            c.moveToNext();
 132        }
 133        c.close();
 134        db1.close();
 135        if (currentCategory == null) {
 136            loadView();
 137        } else {
 138            loadLevelTwoView(currentCategory);
 139        }
 140    }
 141
 142    @Override
 143    public void onCreate(Bundle savedInstanceState) {
 144        gestureScanner = new GestureDetector(this);
 145
 146        super.onCreate(savedInstanceState);
 147        if (DatabaseHelperFactory.getInstance(StartActivity.this).checkDatabase()) {
 148            setContentView(R.layout.catagorylayout);
 149            showDialog(CHECK_SERVER_DIALOG);
 150        }
 151
 152    }
 153
 154    protected Dialog onCreateDialog(final int id) {
 155        switch (id) {
 156            case LOAD_DIALOG:
 157                return dialogLoadingMain();
 158            case POINT_DIALOG:
 159                return dialogLoadingPoints();
 160            case CHANGE_SERVER_DIALOG:
 161                return dialogAddServer();
 162            case CHOOSE_SERVER_DIALOG:
 163                return dialogChooseServer();
 164            case CHECK_SERVER_DIALOG:
 165                return dialogAuthenticatedResponse();
 166            case NO_DATA_DIALOG:
 167                return dialogNoPoints();
 168            default:
 169                return null;
 170        }
 171
 172
 173    }
 174
 175    private Dialog dialogNoPoints() {
 176        final Dialog dialog = new Dialog(StartActivity.this);
 177
 178        dialog.setContentView(R.layout.welcome_layout);
 179        dialog.setTitle("Welcome To Nimbits");
 180
 181        final TextView text1 = (TextView) dialog.findViewById(R.id.text);
 182        text1.setText("Nimbits is a free, social and open source data logging service built on cloud computing technology." +
 183                " By creating \"Data Points\" you can feed any values that change over time, such as a changing temperature or stock price, " +
 184                "into that point for storage in a global infrastructure. That point can then be visualized, charted, " +
 185                "and shared using many open source software interfaces.   You can configure points by logging into the Nimbits Portal: \n www.nimbits.com.\n " +
 186                "This Android interface to Nimbits allows you to create and view points, record values, and view charts. " +
 187                "To get started, click next to create your first data point!");
 188
 189        final Button d1 = (Button) dialog.findViewById(R.id.Button01);
 190        d1.setOnClickListener(new OnClickListener() {
 191
 192            public void onClick(View v) {
 193                dismissDialog(NO_DATA_DIALOG);
 194                removeDialog(NO_DATA_DIALOG);
 195                createPoint();
 196
 197
 198            }
 199
 200        });
 201
 202        return dialog;
 203    }
 204
 205    private Dialog dialogLoadingPoints() {
 206        pointDialog = new ProgressDialog(StartActivity.this);
 207        pointDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 208        pointDialog.setMessage("Loading Current Values...");
 209        loadPointDataThread = new LoadPointDataThread(pointHandler, currentCategory);
 210        loadPointDataThread.start();
 211        return pointDialog;
 212    }
 213
 214    private Dialog dialogLoadingMain() {
 215        populateDatabaseDialog = new ProgressDialog(StartActivity.this);
 216        populateDatabaseDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
 217        populateDatabaseDialog.setMessage("Loading...");
 218        populateDatabaseThread = new PopulateDatabaseThread(populateDatabaseHandler, StartActivity.this);
 219        populateDatabaseThread.start();
 220        return populateDatabaseDialog;
 221    }
 222
 223    private Dialog dialogChooseServer() {
 224
 225
 226        final List<String> servers = LocalDatabaseDaoFactory.getInstance().getServers(StartActivity.this);
 227
 228        final CharSequence[] items = servers.toArray(new CharSequence[servers.size()]);
 229
 230        AlertDialog.Builder builder = new AlertDialog.Builder(this);
 231        builder.setTitle("Change Nimbits Server");
 232
 233
 234        builder.setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
 235
 236
 237            public void onClick(DialogInterface dialog, int item) {
 238
 239
 240                selectedServer = (String) items[item];
 241
 242                //Toast.makeText(getApplicationContext(), items[item], Toast.LENGTH_SHORT).show();
 243                // selection =  (String) items[item];
 244
 245            }
 246        });
 247
 248        builder.setNeutralButton("New", new DialogInterface.OnClickListener() {
 249
 250            public void onClick(DialogInterface dialog, int which) {
 251                dismissDialog(CHOOSE_SERVER_DIALOG);
 252                removeDialog(CHOOSE_SERVER_DIALOG);
 253                showDialog(CHANGE_SERVER_DIALOG);
 254
 255            }
 256
 257        });
 258        builder.setNegativeButton("Delete", new DialogInterface.OnClickListener() {
 259
 260            public void onClick(DialogInterface dialog, int which) {
 261
 262                if (selectedServer != null) {
 263                    SQLiteDatabase db1;
 264                    db1 = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(true);
 265                    Log.v("delete", selectedServer);
 266                    db1.execSQL("delete from Servers where url='" + selectedServer + "'");
 267                    db1.close();
 268                    dismissDialog(CHOOSE_SERVER_DIALOG);
 269                    removeDialog(CHOOSE_SERVER_DIALOG);
 270                    showDialog(CHOOSE_SERVER_DIALOG);
 271
 272                }
 273
 274            }
 275
 276        });
 277
 278
 279        builder.setPositiveButton("Switch", new DialogInterface.OnClickListener() {
 280
 281            public void onClick(DialogInterface dialog, int which) {
 282                if (selectedServer != null) {
 283                    baseURL = selectedServer;
 284                    LocalDatabaseDaoFactory.getInstance().updateSetting(StartActivity.this, Const.PARAM_SERVER, baseURL);
 285                    dismissDialog(CHOOSE_SERVER_DIALOG);
 286                    removeDialog(CHOOSE_SERVER_DIALOG);
 287
 288                    refreshData();
 289
 290                }
 291
 292            }
 293        }
 294        );
 295
 296
 297        return builder.create();
 298
 299    }
 300
 301    private Dialog dialogAddServer() {
 302        final Dialog dialog1 = new Dialog(StartActivity.this);
 303
 304        dialog1.setContentView(R.layout.text_prompt);
 305        dialog1.setTitle("Change Server");
 306        TextView text = (TextView) dialog1.findViewById(R.id.text);
 307        text.setText("You can point your android device to another Nimbits Server URL (i.e yourserver.appspot.com)");
 308
 309        EditText urlText = (EditText) dialog1.findViewById(R.id.new_value);
 310        urlText.setText(baseURL);
 311        Button b = (Button) dialog1.findViewById(R.id.textPromptOKButton);
 312        Button d = (Button) dialog1.findViewById(R.id.textPromptDefaultButton);
 313        b.setOnClickListener(new OnClickListener() {
 314            public void onClick(View v) {
 315                EditText urlText = (EditText) dialog1.findViewById(R.id.new_value);
 316                // String authToken = OwnerAccountImpl.getToken(StartActivity.this);
 317                String u = urlText.getText().toString();
 318                LocalDatabaseDaoFactory.getInstance().addServer(StartActivity.this, u);
 319                dialog1.dismiss();
 320                removeDialog(CHANGE_SERVER_DIALOG);
 321                showDialog(CHOOSE_SERVER_DIALOG);
 322
 323//                try {
 324//                    if (Client.getN().isLoggedIn())
 325//                    {
 326//                        baseURL = u;
 327//
 328//                        refreshData();
 329//
 330//                    }
 331//                    else
 332//                    {
 333//                        Toast.makeText(StartActivity.this, "Could not connect to" + u, Toast.LENGTH_LONG).show();
 334//
 335//                    }
 336//                } catch (IOException e) {
 337//                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
 338//                }
 339//                //showDialog(CHOOSE_SERVER_DIALOG);
 340
 341
 342            }
 343        });
 344
 345        d.setOnClickListener(new OnClickListener() {
 346            public void onClick(View v) {
 347                EditText urlText = (EditText) dialog1.findViewById(R.id.new_value);
 348                urlText.setText(Const.PATH_NIMBITS_PUBLIC_SERVER);
 349
 350            }
 351        });
 352
 353        dialog1.setOnDismissListener(new DialogInterface.OnDismissListener() {
 354
 355            public void onDismiss(DialogInterface dialog) {
 356            }
 357
 358        });
 359
 360        return dialog1;
 361    }
 362
 363    private Dialog dialogAuthenticatedResponse() {
 364        Log.v("NimbitsV", "Authenticating");
 365        baseURL = LocalDatabaseDaoFactory.getInstance().getSetting(StartActivity.this, Const.PARAM_SERVER);
 366        Log.v("NimbitsV", "Logging into " + baseURL);
 367        authenticateDialog = new ProgressDialog(StartActivity.this);
 368        authenticateDialog = ProgressDialog.show(StartActivity.this, "", "Authenticating to Nimbits Server @ " + baseURL + " using account " + OwnerAccountFactory.getInstance().getEmail(StartActivity.this) + ".  Please wait...", true);
 369
 370        authenticateThread = new AuthenticateThread(authenticateThreadHandler, StartActivity.this);
 371        authenticateThread.start();
 372        return authenticateDialog;
 373    }
 374
 375    // Define the Handler that receives messages from the thread and update the progress
 376    private final Handler populateDatabaseHandler = new Handler() {
 377        public void handleMessage(Message msg) {
 378            final int total = msg.getData().getInt(Const.PARAM_TOTAL);
 379            final int pointCount = msg.getData().getInt(Const.PARAM_POINT_COUNT);
 380
 381            if (populateDatabaseDialog != null) {
 382                populateDatabaseDialog.setProgress(total);
 383                Log.v("handler", "" + total);
 384                if (total >= 100) {
 385                    populateDatabaseDialog.setProgress(0);
 386                    dismissDialog(LOAD_DIALOG);
 387                    removeDialog(LOAD_DIALOG);
 388                    populateDatabaseThread.setState(PopulateDatabaseThread.STATE_DONE);
 389                    populateDatabaseDialog = null;
 390                    if (pointCount == 0) {
 391                        showDialog(NO_DATA_DIALOG);
 392                    } else {
 393                        loadView();
 394                    }
 395
 396
 397                }
 398            }
 399        }
 400    };
 401
 402    // Define the Handler that receives messages from the thread and update the progress
 403    private final Handler authenticateThreadHandler = new Handler() {
 404        public void handleMessage(Message msg) {
 405            //	int total = msg.getData().getInt("total");
 406            final boolean isLoggedIn = msg.getData().getBoolean(Const.PARAM_IS_LOGGED_IN);
 407            Log.v("NimbitsV", "is logged in " + isLoggedIn);
 408            if (authenticateDialog != null) {
 409                dismissDialog(CHECK_SERVER_DIALOG);
 410                removeDialog(CHECK_SERVER_DIALOG);
 411                authenticateThread.setState(AuthenticateThread.STATE_DONE);
 412                authenticateDialog = null;
 413
 414                if (isLoggedIn) {
 415                    boolean reload = false;
 416                    final Bundle b = getIntent().getExtras();
 417                    String category = null;
 418                    if (b != null) {
 419                        reload = b.getBoolean(Const.PARAM_RELOAD);
 420                        category = b.getString(Const.ANDROID_COL_CATEGORY);
 421                    }
 422                    if (!reload) {
 423                        if (category != null) {
 424                            currentCategory = category;
 425
 426                            showDialog(POINT_DIALOG);
 427                        } else {
 428                            loadView();
 429                        }
 430                    } else {
 431                        showDialog(LOAD_DIALOG);
 432                    }
 433                } else {
 434                    Toast.makeText(StartActivity.this, "Nimbits uses google accounts to authenticate. Please add a google.com (gmail.com) account to this device.", Toast.LENGTH_LONG).show();
 435                }
 436            }
 437        }
 438    };
 439
 440    private final Handler pointHandler = new Handler() {
 441        public void handleMessage(Message msg) {
 442            int total = msg.getData().getInt(Const.PARAM_TOTAL);
 443
 444            if (pointDialog != null) {
 445                pointDialog.setProgress(total);
 446
 447                if (total >= 100) {
 448                    pointDialog.setProgress(0);
 449                    dismissDialog(POINT_DIALOG);
 450                    removeDialog(POINT_DIALOG);
 451                    loadPointDataThread.setState(LoadPointDataThread.STATE_DONE);
 452                    pointDialog = null;
 453                    loadLevelTwoView(currentCategory);
 454
 455
 456                }
 457            }
 458        }
 459    };
 460
 461    void loadView() {
 462
 463        currentCategory = null;
 464
 465
 466        if (listCursor != null) {
 467            listCursor.close();
 468        }
 469
 470
 471        ListAdapter adapter = LocalDatabaseDaoFactory.getInstance().mainListCursor(StartActivity.this);
 472        setContentView(R.layout.catagorylayout);
 473        setListAdapter(adapter);
 474
 475    }
 476
 477    private void loadLevelTwoView(final String category) {
 478        this.setTitle(category);
 479        currentCategory = category;
 480        if (listCursor != null) {
 481            listCursor.close();
 482        }
 483
 484        SQLiteDatabase db = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(false);
 485        listCursor = db.query(Const.ANDROID_TABLE_LEVEL_TWO_DISPLAY, new String[]{
 486                Const.ANDROID_COL_ID,
 487                Const.ANDROID_COL_CATEGORY,
 488                Const.ANDROID_COL_DESCRIPTION,
 489                Const.ANDROID_COL_DISPLAY_TYPE,
 490                Const.ANDROID_COL_NAME
 491        },
 492                Const.ANDROID_COL_CATEGORY + "='" + category + "'", null, null, null, Const.ANDROID_COL_DISPLAY_TYPE);
 493
 494
 495        ListAdapter adapter = new ImageCursorAdapter(
 496                this, // Context.
 497                R.layout.main_list,  // Specify the row template to use (here, two columns bound to the two retrieved cursor
 498                listCursor,                                              // Pass in the cursor to bind to.
 499                new String[]{Const.ANDROID_COL_NAME, Const.ANDROID_COL_DESCRIPTION},           // Array of cursor columns to bind to.
 500
 501                new int[]{R.id.text1, R.id.text2});  // Parallel array of which template objects to bind to those columns.
 502
 503        // Bind to our new adapter.
 504        setListAdapter(adapter);
 505
 506
 507    }
 508
 509    private void viewMap() {
 510        if (this.currentCategory == null) {
 511            Toast.makeText(StartActivity.this, "Please select a category to or point to view on the map", Toast.LENGTH_LONG).show();
 512
 513        } else {
 514            //SQLiteDatabase db1 = getDB(false);
 515            Cursor c;
 516            SQLiteDatabase db1;
 517            db1 = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(false);
 518            c = db1.query(Const.ANDROID_TABLE_LEVEL_ONE_DISPLAY, new String[]{Const.ANDROID_COL_ID, Const.ANDROID_COL_JSON}, Const.ANDROID_COL_NAME + "='" + currentCategory + "'", null, null, null, null);
 519
 520            c.moveToFirst();
 521            String json = c.getString(c.getColumnIndex(Const.ANDROID_COL_JSON));
 522            c.close();
 523
 524            Bundle b = new Bundle();
 525            Intent intent = new Intent();
 526            b.putString(Const.PARAM_TYPE, Const.ANDROID_COL_CATEGORY);
 527            b.putString(Const.PARAM_CATEGORY, currentCategory);
 528            b.putString(Const.PARAM_JSON, json);
 529            b.putString(Const.PARAM_BASE_URL, baseURL);
 530            intent.putExtras(b);
 531            intent.setClass(StartActivity.this, MapViewActivity.class);
 532            startActivity(intent);
 533            c.close();
 534            db1.close();
 535
 536            finish();
 537        }
 538    }
 539
 540    private void viewChart() {
 541
 542        String cat;
 543        Log.v("action", "view chart");
 544        Log.v("chart", currentCategory);
 545        if (this.currentCategory == null) {
 546            cat = Const.CONST_HIDDEN_CATEGORY;
 547            //Toast.makeText(StartActivity.this,"Please select a catagory to or point to view a chart", Toast.LENGTH_LONG).show();
 548
 549        } else {
 550            cat = currentCategory;
 551        }
 552        Log.v("chart", cat);
 553
 554        //SQLiteDatabase db1 = getDB(false);
 555        Cursor c;
 556        SQLiteDatabase db1;
 557        db1 = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(false);
 558        c = db1.query(Const.ANDROID_TABLE_LEVEL_ONE_DISPLAY, new String[]{Const.ANDROID_COL_ID, Const.ANDROID_COL_JSON}, Const.ANDROID_COL_NAME + "='" + cat + "'", null, null, null, null);
 559
 560        c.moveToFirst();
 561        String json = c.getString(c.getColumnIndex(Const.ANDROID_COL_JSON));
 562        Log.v("chart", json);
 563        c.close();
 564        db1.close();
 565
 566        Bundle b = new Bundle();
 567        Intent intent = new Intent();
 568        b.putString(Const.PARAM_TYPE, Const.PARAM_CATEGORY);
 569        b.putString(Const.PARAM_CATEGORY, cat);
 570        b.putString(Const.PARAM_JSON, json);
 571        b.putString(Const.PARAM_BASE_URL, baseURL);
 572
 573        intent.putExtras(b);
 574        intent.setClass(StartActivity.this, ChartActivity.class);
 575        startActivity(intent);
 576
 577        finish();
 578
 579    }
 580
 581    @Override
 582    public void finish() {
 583        super.finish();
 584        if (timerHandler != null) {
 585            timerHandler.removeCallbacks(mUpdateTimerTask);
 586        }
 587    }
 588
 589    private void createCategory() {
 590
 591        //setContentView(0);
 592        try {
 593            CustomDialog myDialog = new CustomDialog(this, "New Category Name:",
 594                    new OnNewCategoryListener());
 595            myDialog.show();
 596            //   al.add(myDialog.getEntry());
 597
 598        } catch (Exception e) {
 599
 600            Log.e("Create category", e.getMessage());
 601        }
 602
 603
 604        //dialog.show();
 605
 606    }
 607
 608    private void createPoint() {
 609        try {
 610            CustomDialog myDialog = new CustomDialog(this, "New Point Name:",
 611                    new OnCreatePointListener());
 612            myDialog.show();
 613        } catch (Exception e) {
 614
 615            Log.e(Const.TEXT_NEW_CATEGORY, e.getMessage());
 616        }
 617
 618
 619    }
 620
 621    private void refreshData() {
 622
 623        LocalDatabaseDaoFactory.getInstance().deleteAll(StartActivity.this);
 624        showDialog(LOAD_DIALOG);
 625    }
 626
 627    private void changeServer() {
 628
 629
 630        showDialog(CHOOSE_SERVER_DIALOG);
 631    }
 632
 633    //Event Overrides
 634
 635    @Override
 636    public boolean onOptionsItemSelected(MenuItem item) {
 637
 638        switch (item.getItemId()) {
 639
 640            case R.id.New_Point_Catagory:
 641                createCategory();
 642                return true;
 643            case R.id.New_Data_Point:
 644                createPoint();
 645                return true;
 646            case R.id.main_menu:
 647                loadView();
 648                return true;
 649            case R.id.refresh:
 650                refreshData();
 651                return true;
 652            case R.id.view_map:
 653                viewMap();
 654                return true;
 655            case R.id.view_chart:
 656                viewChart();
 657                return true;
 658            case R.id.exit:
 659                this.finish();
 660                return true;
 661            case R.id.Servers:
 662                changeServer();
 663                return true;
 664
 665            default:
 666                return super.onOptionsItemSelected(item);
 667        }
 668    }
 669
 670    @Override
 671    public boolean onCreateOptionsMenu(Menu menu) {
 672        MenuInflater inflater = getMenuInflater();
 673        inflater.inflate(R.menu.mainmenu, menu);
 674        return true;
 675    }
 676
 677    @Override
 678    protected void onListItemClick(final ListView l, final View v, final int position, final long id) {
 679        super.onListItemClick(l, v, position, id);
 680        //	Context context = getApplicationContext();
 681
 682        final ImageView icon = (ImageView) v.findViewById(R.id.icon1);
 683        final TextView d = (TextView) v.findViewById(R.id.text1);
 684
 685
 686        if (listCursor != null) {
 687            listCursor.close();
 688        }
 689
 690        if (icon.getTag().toString().equals(Const.PARAM_POINT)) {
 691            final String json = LocalDatabaseDaoFactory.getInstance().getSelectedChildTableJsonByName(StartActivity.this, (String) d.getText());
 692            final Bundle b = new Bundle();
 693            final Intent intent = new Intent();
 694            b.putString(Const.PARAM_CATEGORY, this.currentCategory);
 695            b.putString(Const.PARAM_POINT, (String) d.getText());
 696            b.putString(Const.PARAM_JSON, json);
 697            b.putString(Const.PARAM_BASE_URL, baseURL);
 698            intent.putExtras(b);
 699            intent.setClass(StartActivity.this, PointActivity.class);
 700            finish();
 701            startActivity(intent);
 702        } else if (icon.getTag().toString().equals(Const.PARAM_DIAGRAM)) {
 703            final String json = LocalDatabaseDaoFactory.getInstance().getSelectedChildTableJsonByName(StartActivity.this, (String) d.getText());
 704            final Bundle b = new Bundle();
 705            final Intent intent = new Intent();
 706            // final String jsonCookie = GsonFactory.getInstance().toJson(authCookie);
 707            b.putString(Const.PARAM_CATEGORY, this.currentCategory);
 708            b.putString(Const.PARAM_DIAGRAM, (String) d.getText());
 709            b.putString(Const.PARAM_JSON, json);
 710            b.putString(Const.PARAM_BASE_URL, baseURL);
 711
 712            b.putString(Const.PARAM_COOKIE, authCookie.getName() + "=" + authCookie.getValue() + "; domain=" + authCookie.getDomain());
 713            intent.putExtras(b);
 714            intent.setClass(StartActivity.this, DiagramActivity.class);
 715            finish();
 716            startActivity(intent);
 717        } else if (icon.getTag().toString().equals(Const.PARAM_CATEGORY)) {
 718            currentCategory = (String) d.getText();
 719            showDialog(POINT_DIALOG);
 720
 721        }
 722
 723
 724    }
 725
 726    @Override
 727    public boolean onTouchEvent(MotionEvent event) {
 728
 729        return gestureScanner.onTouchEvent(event);
 730
 731    }
 732
 733    @Override
 734    public boolean onDown(MotionEvent e) {
 735
 736        return false;
 737    }
 738
 739    @Override
 740    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
 741
 742        Log.v("action", "fling");
 743
 744        try {
 745            if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
 746                return false;
 747            // right to left swipe
 748            if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
 749
 750                Log.v("action", "right");
 751                viewChart();
 752            } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
 753
 754
 755                Log.v("action", "left");
 756                if (currentCategory != null) {
 757                    currentCategory = null;
 758                    loadView();
 759
 760                }
 761            }
 762        } catch (Exception e) {
 763            // nothing
 764        }
 765
 766        return false;
 767    }
 768
 769    @Override
 770    public void onLongPress(MotionEvent e) {
 771
 772
 773    }
 774
 775    @Override
 776    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
 777
 778        return false;
 779    }
 780
 781    @Override
 782    public void onShowPress(MotionEvent e) {
 783
 784
 785    }
 786
 787    @Override
 788    public boolean onSingleTapUp(MotionEvent e) {
 789
 790        return false;
 791    }
 792
 793    private class PopulateDatabaseThread extends Thread {
 794        final Handler mHandler;
 795        final static int STATE_DONE = 0;
 796        final static int STATE_RUNNING = 1;
 797        @SuppressWarnings("unused")
 798        int mState;
 799        @SuppressWarnings("unused")
 800        int total;
 801        final Context context;
 802
 803        PopulateDatabaseThread(Handler h, Context c) {
 804            mHandler = h;
 805            context = c;
 806        }
 807
 808        private void update(int c, boolean loggedIn, int count) {
 809            Message msg = mHandler.obtainMessage();
 810            Bundle b = new Bundle();
 811            b.putInt(Const.PARAM_TOTAL, c);
 812            b.putInt(Const.PARAM_POINT_COUNT, count);
 813            b.putBoolean(Const.PARAM_LOGGED_IN, loggedIn);
 814            msg.setData(b);
 815            mHandler.sendMessage(msg);
 816        }
 817
 818        public void run() {
 819            mState = STATE_RUNNING;
 820            total = 0;
 821            boolean loggedIn;
 822            update(7, false, 0);
 823            loggedIn = true;
 824            update(10, loggedIn, 0);
 825
 826            update(20, loggedIn, 0);
 827            int pointCount = 0;
 828            int diagramCount = 0;
 829            if (DatabaseHelperFactory.getInstance(StartActivity.this).isDatabaseEmpty()) {
 830
 831                pointCount = populateEmptyDatabase(loggedIn, pointCount, diagramCount);
 832            } else {
 833                pointCount++;
 834                diagramCount++;
 835            }
 836            //db1.close();
 837            total = 100;
 838            update(100, loggedIn, pointCount);
 839            mState = STATE_DONE;
 840
 841        }
 842
 843        private int populateEmptyDatabase(boolean loggedIn, int totalPointCount, int totalDiagramCount) {
 844            final List<Category> categories = OwnerAccountFactory.getInstance().getNimbitsClient(context, baseURL).getCategories(true, true);
 845            update(25, loggedIn, totalPointCount);
 846            Log.v(Const.N, "Populating empty db from: " + baseURL);
 847            int i = 30;
 848            if (categories != null) {
 849                update(i, loggedIn, totalPointCount);
 850                for (Category category : categories) {
 851
 852                    update(i++, loggedIn, totalPointCount);
 853                    ContentValues mainTableValues = new ContentValues();
 854                    mainTableValues.put(Const.ANDROID_COL_NAME, category.getName().getValue());
 855                    mainTableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, DisplayType.Category.getCode());
 856                    mainTableValues.put(Const.ANDROID_COL_JSON, category.getJsonPointCollection());
 857
 858                    for (final Point p : category.getPoints()) {
 859                        totalPointCount++;
 860                        addPointToChildrenTable(category, p);
 861
 862                    }
 863
 864                    for (final Diagram d : category.getDiagrams()) {
 865                        totalDiagramCount++;
 866                        addDiagramToChildrenTable(category, d);
 867
 868                    }
 869
 870                    mainTableValues.put(Const.ANDROID_COL_DESCRIPTION, category.getPoints().size() + " points " +
 871                            category.getDiagrams().size() + " diagrams");
 872                    LocalDatabaseDaoFactory.getInstance().insertMain(StartActivity.this, mainTableValues);
 873
 874
 875                    if (category.getName() != null && category.getName().getValue().equals(Const.CONST_HIDDEN_CATEGORY)) {
 876
 877                        for (Point p : category.getPoints()) {
 878                            totalPointCount++;
 879                            addTopLevelPointToDatabase(p);
 880                        }
 881
 882                        for (Diagram d : category.getDiagrams()) {
 883                            totalDiagramCount++;
 884                            addTopLevelDiagramToDatabase(d);
 885                        }
 886
 887
 888                    }
 889
 890
 891                }
 892
 893            }
 894            return totalPointCount;
 895        }
 896
 897        private void addTopLevelPointToDatabase(final Point p) {
 898            final ContentValues pointTableValues = new ContentValues();
 899            final ContentValues mainTableValues = new ContentValues();
 900            final Value v = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).getCurrentRecordedValue(p.getName());
 901            mainTableValues.put(Const.ANDROID_COL_NAME, p.getName().getValue());
 902            int displayType;
 903            if (v != null) {
 904                if (v.getValue() > p.getHighAlarm() && p.isHighAlarmOn()) {
 905                    displayType = DisplayType.HighAlarm.getCode();
 906                } else if (v.getValue() < p.getLowAlarm() && p.isLowAlarmOn()) {
 907                    displayType = DisplayType.LowAlarm.getCode();
 908                } else {
 909                    displayType = DisplayType.Point.getCode();
 910                }
 911            } else {
 912                displayType = DisplayType.Point.getCode();
 913            }
 914
 915            mainTableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, displayType);
 916            mainTableValues.put(Const.ANDROID_COL_DESCRIPTION, buildPointDescription(p, v));
 917            LocalDatabaseDaoFactory.getInstance().insertMain(StartActivity.this, mainTableValues);
 918
 919
 920            pointTableValues.put(Const.ANDROID_COL_CATEGORY, p.getName().getValue());
 921            pointTableValues.put(Const.ANDROID_COL_DESCRIPTION, p.getDescription());
 922            pointTableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, displayType);
 923            pointTableValues.put(Const.ANDROID_COL_JSON, GsonFactory.getInstance().toJson(p));
 924            LocalDatabaseDaoFactory.getInstance().insertPoints(StartActivity.this, pointTableValues);
 925
 926        }
 927
 928        private void addTopLevelDiagramToDatabase(final Diagram diagram) {
 929            final ContentValues pointTableValues = new ContentValues();
 930            final ContentValues mainTableValues = new ContentValues();
 931            mainTableValues.put(Const.ANDROID_COL_NAME, diagram.getName().getValue());
 932            mainTableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, DisplayType.Diagram.getCode());
 933            mainTableValues.put(Const.ANDROID_COL_DESCRIPTION, "");//TODO diagram description
 934            LocalDatabaseDaoFactory.getInstance().insertMain(StartActivity.this, mainTableValues);
 935
 936
 937            pointTableValues.put(Const.ANDROID_COL_CATEGORY, diagram.getName().getValue());
 938            pointTableValues.put(Const.ANDROID_COL_DESCRIPTION, "");//TODO
 939            pointTableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, DisplayType.Diagram.getCode());
 940            pointTableValues.put(Const.ANDROID_COL_JSON, GsonFactory.getInstance().toJson(diagram));
 941            LocalDatabaseDaoFactory.getInstance().insertPoints(StartActivity.this, pointTableValues);
 942
 943        }
 944
 945        private void addPointToChildrenTable(final Category category, final Point p) {
 946            final ContentValues tableValues = new ContentValues();
 947            tableValues.put(Const.ANDROID_COL_CATEGORY, category.getName().getValue());
 948            tableValues.put(Const.ANDROID_COL_NAME, p.getName().getValue());
 949            tableValues.put(Const.ANDROID_COL_DESCRIPTION, "");
 950            tableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, DisplayType.Point.getCode());
 951            tableValues.put(Const.ANDROID_COL_JSON, GsonFactory.getInstance().toJson(p));
 952            LocalDatabaseDaoFactory.getInstance().insertPoints(StartActivity.this, tableValues);
 953        }
 954
 955        private void addDiagramToChildrenTable(final Category category, final Diagram d) {
 956            final ContentValues tableValues = new ContentValues();
 957            tableValues.put(Const.ANDROID_COL_CATEGORY, category.getName().getValue());
 958            tableValues.put(Const.ANDROID_COL_NAME, d.getName().getValue());
 959            tableValues.put(Const.ANDROID_COL_DESCRIPTION, "");
 960            tableValues.put(Const.ANDROID_COL_DISPLAY_TYPE, DisplayType.Diagram.getCode());
 961            tableValues.put(Const.ANDROID_COL_JSON, GsonFactory.getInstance().toJson(d));
 962            LocalDatabaseDaoFactory.getInstance().insertPoints(StartActivity.this, tableValues);
 963        }
 964
 965        /* sets the current state for the thread,
 966  * used to stop the thread */
 967        public void setState(int state) {
 968            mState = state;
 969        }
 970    }
 971
 972    private class LoadPointDataThread extends Thread {
 973        final Handler mHandler;
 974        final static int STATE_DONE = 0;
 975        final static int STATE_RUNNING = 1;
 976        @SuppressWarnings("unused")
 977        int mState;
 978        @SuppressWarnings("unused")
 979        int total;
 980        final String selectedCategory;
 981
 982        LoadPointDataThread(final Handler h, final String categoryName) {
 983
 984            selectedCategory = categoryName;
 985            mHandler = h;
 986        }
 987
 988        /* sets the current state for the thread,
 989  * used to stop the thread */
 990        public void setState(int state) {
 991            mState = state;
 992        }
 993
 994        private void update(int c) {
 995            Message msg = mHandler.obtainMessage();
 996            Bundle b = new Bundle();
 997            b.putInt(Const.PARAM_TOTAL, c);
 998            msg.setData(b);
 999            mHandler.sendMessage(msg);
1000        }
1001
1002        public void run() {
1003            mState = STATE_RUNNING;
1004
1005            Value v;
1006            PointName pointName;
1007            String json;
1008
1009
1010            //SQLiteDatabase db1 = getDB(true);
1011
1012            final SQLiteDatabase db = DatabaseHelperFactory.getInstance(StartActivity.this).getDB(true);
1013            final Cursor c = db.query(Const.ANDROID_TABLE_LEVEL_TWO_DISPLAY, new String[]{
1014                    Const.ANDROID_COL_ID,
1015                    Const.ANDROID_COL_NAME,
1016                    Const.ANDROID_COL_DESCRIPTION,
1017                    Const.ANDROID_COL_DISPLAY_TYPE,
1018                    Const.ANDROID_COL_JSON},
1019                    Const.ANDROID_COL_CATEGORY + "='" + selectedCategory + "'",
1020                    null, null, null,
1021                    Const.ANDROID_COL_DISPLAY_TYPE);
1022
1023
1024            int count = c.getCount();
1025
1026            update(0);
1027            if (count > 0) {
1028                int d = 100 / count;
1029                int progress = 0;
1030
1031                //update(count);
1032
1033                c.moveToFirst();
1034                while (!c.isAfterLast()) {
1035
1036                    pointName = (PointName) CommonFactoryLocator.getInstance().createPointName(c.getString(c.getColumnIndex(Const.ANDROID_COL_NAME)));
1037                    json = c.getString(c.getColumnIndex(Const.ANDROID_COL_JSON));
1038                    Point p = GsonFactory.getInstance().fromJson(json, PointModel.class);
1039                    v = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).getCurrentRecordedValue(pointName);
1040                    ContentValues u = new ContentValues();
1041
1042                    if (v != null) {
1043
1044                        u.put(Const.ANDROID_COL_DESCRIPTION, buildPointDescription(p, v));
1045                        if (v.getValue() > p.getHighAlarm() && p.isHighAlarmOn()) {
1046                            u.put(Const.ANDROID_COL_DISPLAY_TYPE, 3);
1047                        } else if (v.getValue() < p.getLowAlarm() && p.isLowAlarmOn()) {
1048                            u.put(Const.ANDROID_COL_DISPLAY_TYPE, 4);
1049                        } else {
1050                            u.put(Const.ANDROID_COL_DISPLAY_TYPE, 2);
1051                        }
1052
1053                    } else {
1054                        u.put(Const.ANDROID_COL_DESCRIPTION, Const.MESSAGE_NO_DATA);
1055                    }
1056//                    DatabaseHelperFactory.getInstance(StartActivity.this).updatePointName(u, pointName);
1057                    LocalDatabaseDaoFactory.getInstance().updatePointValuesByName(StartActivity.this, u, pointName);
1058                    progress += d;
1059                    update(progress);
1060
1061                    c.moveToNext();
1062                }
1063
1064                c.close();
1065                db.close();
1066                update(100);
1067            } else {
1068                update(100);
1069            }
1070            update(100);
1071
1072
1073        }
1074
1075    }
1076
1077    private class AuthenticateThread extends Thread {
1078        final Handler m;
1079        final static int STATE_DONE = 0;
1080        int mState;
1081        final Context currentContext;
1082
1083        AuthenticateThread(Handler h, Context c) {
1084            m = h;
1085            currentContext = c;
1086        }
1087
1088        /* sets the current state for the thread,
1089  * used to stop the thread */
1090        public void setState(int state) {
1091            mState = state;
1092        }
1093
1094        private void update(boolean isLoggedIn) {
1095            Message msg = m.obtainMessage();
1096            Bundle b = new Bundle();
1097            b.putBoolean(Const.PARAM_IS_LOGGED_IN, isLoggedIn);
1098            msg.setData(b);
1099            m.sendMessage(msg);
1100        }
1101
1102        public void run() {
1103
1104            try {
1105                baseURL = LocalDatabaseDaoFactory.getInstance().getSetting(StartActivity.this, Const.PARAM_SERVER);
1106
1107                // String authToken = OwnerAccountImpl.getToken(currentContext);
1108                //googleAuth.connectClean(baseURL,authToken);
1109                boolean isLoggedIn = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).isLoggedIn();
1110                authCookie = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).getAuthCookie();
1111                update(isLoggedIn);
1112            } catch (Exception e) {
1113                Log.e(Const.N, e.getMessage());
1114                update(false);
1115            }
1116
1117
1118        }
1119
1120    }
1121
1122    private final Runnable mUpdateTimerTask = new Runnable() {
1123
1124        public void run() {
1125            Log.v("category timer", "tick");
1126            //			updatePointValues();
1127            //			if (mHandler != null)
1128            //			{
1129            //				mHandler.removeCallbacks(mUpdateTimerTask);
1130            //				mHandler.postDelayed(mUpdateTimerTask, refreshRate);
1131            //			}
1132        }
1133    };
1134
1135    @Override
1136    public boolean onKeyDown(int keyCode, KeyEvent event) {
1137        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
1138            if (currentCategory != null) {
1139                currentCategory = null;
1140                loadView();
1141                return true;
1142            }
1143
1144        }
1145
1146        return super.onKeyDown(keyCode, event);
1147    }
1148
1149    private class OnNewCategoryListener implements CustomDialog.ReadyListener {
1150        public void ready(String categoryNameResponse) throws UnsupportedEncodingException {
1151            if (categoryNameResponse != null && categoryNameResponse.trim().length() > 0) {
1152                CategoryName categoryName = CommonFactoryLocator.getInstance().createCategoryName(categoryNameResponse);
1153
1154                OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).addCategory(categoryName);
1155                ContentValues values = new ContentValues();
1156                values.put(Const.ANDROID_COL_CATEGORY, categoryName.getValue());
1157                values.put(Const.ANDROID_COL_DISPLAY_TYPE, 1);
1158                values.put(Const.ANDROID_COL_DESCRIPTION, 0 + " points");
1159                LocalDatabaseDaoFactory.getInstance().insertMain(StartActivity.this, values);
1160
1161                //db1.insert(DatabaseHelperImpl.ANDROID_TABLE_LEVEL_ONE_DISPLAY, null,values);
1162                //db1.close();
1163                Toast.makeText(StartActivity.this, "Added Category " + categoryName + ". Click on the category to add Data Points to it", Toast.LENGTH_LONG).show();
1164                loadView();
1165
1166            }
1167
1168
1169        }
1170    }
1171
1172    private class OnCreatePointListener implements CustomDialog.ReadyListener {
1173        public void ready(String pointNameText) {
1174
1175
1176            //SQLiteDatabase db1 = getDB(true);
1177            if (!pointNameText.trim().equals("")) {
1178                PointName pointName = CommonFactoryLocator.getInstance().createPointName(pointNameText);
1179                CategoryName categoryName = CommonFactoryLocator.getInstance().createCategoryName(currentCategory);
1180
1181
1182                Point point = OwnerAccountFactory.getInstance().getNimbitsClient(StartActivity.this, baseURL).addPoint(categoryName, pointName);
1183                ContentValues pValues = new ContentValues();
1184                ContentValues cValues = new ContentValues();
1185                if (currentCategory != null) {
1186                    pValues.put(Const.ANDROID_COL_CATEGORY, currentCategory);
1187                } else {
1188                    pValues.put(Const.ANDROID_COL_CATEGORY, Const.CONST_HIDDEN_CATEGORY);
1189                    cValues.put(Const.ANDROID_COL_CATEGORY, pointName.getValue());
1190                    cValues.put(Const.ANDROID_COL_DISPLAY_TYPE, 2);
1191
1192                    cValues.put(Const.ANDROID_COL_DESCRIPTION, "");
1193
1194                    LocalDatabaseDaoFactory.getInstance().insertMain(StartActivity.this, cValues);
1195
1196
1197                }
1198                pValues.put(Const.ANDROID_COL_CATEGORY, pointName.getValue());
1199                pValues.put(Const.ANDROID_COL_DESCRIPTION, "");
1200                pValues.put(Const.ANDROID_COL_DISPLAY_TYPE, 2);
1201                pValues.put(Const.ANDROID_COL_JSON, GsonFactory.getInstance().toJson(point));
1202                LocalDatabaseDaoFactory.getInstance().insertPoints(StartActivity.this, pValues);
1203
1204
1205                //db1.close();
1206                if (currentCategory != null) {
1207                    loadLevelTwoView(currentCategory);
1208                } else {
1209                    loadView();
1210
1211                }
1212
1213
1214                Toast.makeText(StartActivity.this, "Added Point " + pointName.getValue() + ". Go to " + baseURL + " to configure advanced properties", Toast.LENGTH_LONG).show();
1215
1216
1217            }
1218
1219
1220        }
1221    }
1222
1223
1224}