пятница, 27 апреля 2012 г.

Спасибо

В сегодняшнем внеплановом сообщении, спешу поделиться неожиданной радостью :) Получено принципиальное согласие Алексея Шамшина на размещение 10 замечательных головоломок, разработанных им, в моей игре. Вряд ли что-либо смогло бы мне поднять настроение сегодня лучше чем эта новость :)

Спасибо



четверг, 26 апреля 2012 г.

10. Подсказка

В предыдущей статье мы реализовали меню приложения. Сегодня мы продолжим реализацию задач, имеющих статус High.

10. Подсказка


Приступая к решению головоломки, хотелось бы видеть конечную позицию, при достижении которой, головоломка считается решенной. Эта позиция может содержать не все, а только некоторые ключевые Tag-и, описывая их расположение на игровом поле. Возможны различные решения для отображения конечной позиции. Мы используем класс Toast для формирования splash-экрана, показывающего финальную позицию (при этом, для отображения позиции, используем уже разработанный нами класс MainView).

Начнем с создания xml-ресурса, help.xml, используемого для построения отображаемого нами окна. Добавляем в res/layout Android xml-файл. Его содержимое тривиально:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/help_page"
    android:padding="10dp"
    android:background="#DAAA"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >
</LinearLayout>


Внесем необходимые изменения в PuzzleActivity (при этом нам придется дополнительно реализовать логику загрузки головоломок из БД). Поскольку изменений довольно много приведу весь класс целиком:

package com.whiterabbit.tags;

...
public class PuzzleActivity extends Activity {

    private static final int BASE_MENU         = 100;
    private static final int BASE_LOCALE_MENU  = 200;
    private static final int BASE_NEW_MENU     = 300;

    private static final int MENU_PROFILE_ITEM = 5;
    private static final int MENU_NEW_ITEM     = 6;
   
    private MainView view = null;
    private Menu mainMenu = null;
    private Map<Long, String> stringCache = new HashMap<Long, String>();
    private int currentLocale = PuzzleDb.Locales.LOCALE_EN;
    private int currentPuzzle;
    private long currentPosition;

    private static final String[] STRINGS_PROJECTION =
            new String[] {
                PuzzleDb.StringValues._ID,
                PuzzleDb.StringValues.COLUMN_NAME_VALUE
        };
   
    private static final String[] LOCALE_PROJECTION =
            new String[] {
                PuzzleDb.Locales._ID,
                PuzzleDb.StringValues.COLUMN_NAME_VALUE
        };
   
    private static final String[] MENU_PROJECTION =
            new String[] {
                PuzzleDb.Puzzles._ID,
                PuzzleDb.StringValues.COLUMN_NAME_VALUE
        };
   
    private static final String[] PROFILE_PROJECTION =
            new String[] {
                PuzzleDb.Profiles._ID,
                PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID,
                PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID,
                PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID
        };
   
    private static final String[] ENDPOS_PROJECTION =
            new String[] {
                PuzzleDb.EndPositions._ID,
                PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID
        };
   
    private static final String[] PARAMS_PROJECTION =
            new String[] {
                PuzzleDb.Params._ID,
                PuzzleDb.Params.COLUMN_NAME_PARAM_TYPE_ID,
                PuzzleDb.Params.COLUMN_NAME_VALUE
        };
   
    private static final String[] PUZZLE_PROJECTION =
            new String[] {
                PuzzleDb.Items._ID,
                PuzzleDb.Tags.COLUMN_NAME_IX,
                PuzzleDb.Items.COLUMN_NAME_TAG_ID,
                PuzzleDb.Items.COLUMN_NAME_IMG_ID,
                PuzzleDb.Items.COLUMN_NAME_ITEM_X,
                PuzzleDb.Items.COLUMN_NAME_ITEM_Y,
                PuzzleDb.Items.COLUMN_NAME_X,
                PuzzleDb.Items.COLUMN_NAME_Y
        };

    public String getLocalizedString(long id) {
        String r = stringCache.get(id);
        if (r != null) {
            return r;
        }
        Uri uri = ContentUris.withAppendedId(PuzzleDb.Strings.CONTENT_URI, id);
        Cursor cursor = managedQuery(
                uri,
                STRINGS_PROJECTION,
                PuzzleDb.StringValues.COLUMN_NAME_LOCALE_ID + " = ?",     
                new String [] {Integer.toString(currentLocale)},
                null
            );
        if (cursor.moveToFirst()) {
            int stringsValueColumn = cursor.getColumnIndex(PuzzleDb.StringValues.COLUMN_NAME_VALUE);
            r = cursor.getString(stringsValueColumn);
        }
        stringCache.put(id, r);
        return r;
    }
   
    public void getLocales(Menu menu) {
        Uri uri = PuzzleDb.Locales.CONTENT_URI;
        Cursor cursor = managedQuery(
                uri,
                LOCALE_PROJECTION,
                "b." + PuzzleDb.StringValues.COLUMN_NAME_LOCALE_ID + " = ?",     
                new String [] {Integer.toString(currentLocale)},
                null
            );
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
            int idColumn = cursor.getColumnIndex(PuzzleDb.Puzzles._ID);
            int nameColumn = cursor.getColumnIndex(PuzzleDb.StringValues.COLUMN_NAME_VALUE);
            int id = cursor.getInt(idColumn);
            String name = cursor.getString(nameColumn);
            menu.add(0, id + BASE_LOCALE_MENU, id, name);
        }
    }
   
    public void getMenu(Menu menu) {
        Uri uri = PuzzleDb.Puzzles.CONTENT_URI;
        Cursor cursor = managedQuery(
                uri,
                MENU_PROJECTION,
                PuzzleDb.StringValues.COLUMN_NAME_LOCALE_ID + " = ?",     
                new String [] {Integer.toString(currentLocale)},
                null
            );
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
            int idColumn = cursor.getColumnIndex(PuzzleDb.Puzzles._ID);
            int nameColumn = cursor.getColumnIndex(PuzzleDb.StringValues.COLUMN_NAME_VALUE);
            int id = cursor.getInt(idColumn);
            String name = cursor.getString(nameColumn);
            menu.add(0, id + BASE_NEW_MENU, 1000 - id, name);
        }
    }
   
    private int saveProfile() {
        Uri uri = PuzzleDb.Profiles.CONTENT_URI;
        ContentValues values = new ContentValues();
        values.put(PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID, Integer.toString(currentLocale));
        return getContentResolver().update(uri, values, null, null);
    }
   
    private void loadProfile() {
        Uri uri = PuzzleDb.Profiles.CONTENT_URI;
        Cursor cursor = managedQuery(
                uri,
                PROFILE_PROJECTION,
                null,     
                null,
                null
            );
        if (cursor.moveToFirst()) {
            int puzzleIdColumn = cursor.getColumnIndex(PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID);
            int localeIdColumn = cursor.getColumnIndex(PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID);
            int positionIdColumn = cursor.getColumnIndex(PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID);
            currentPuzzle = cursor.getInt(puzzleIdColumn);
            currentPosition = cursor.getInt(positionIdColumn);
            currentLocale = cursor.getInt(localeIdColumn);
        }       
    }
   
    public long getEndPositionId(int puzzleId) {
        Uri uri = ContentUris.withAppendedId(PuzzleDb.EndPositions.CONTENT_URI, puzzleId);
        Cursor cursor = managedQuery(
                uri,
                ENDPOS_PROJECTION,
                null,     
                null,
                null
            );
        if (cursor.moveToFirst()) {
            int positionIdColumn = cursor.getColumnIndex(PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID);
            return cursor.getLong(positionIdColumn);
        }
        return -1;
    }
   
    public void getEndPositions(Model callback, int id) {
        Uri uri = ContentUris.withAppendedId(PuzzleDb.EndPositions.CONTENT_URI, id);
        Cursor cursor = managedQuery(
                uri,
                ENDPOS_PROJECTION,
                null,     
                null,
                null
            );
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
            int positionIdColumn = cursor.getColumnIndex(PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID);
            long positionId = cursor.getLong(positionIdColumn);
            getPosition(callback, positionId);
        }       
    }
   
    public void getParams(Model callback, int id) {
        Uri uri = ContentUris.withAppendedId(PuzzleDb.Params.CONTENT_URI, id);
        Cursor cursor = managedQuery(
                uri,
                PARAMS_PROJECTION,
                null,     
                null,
                null
            );
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
            int paramIdColumn = cursor.getColumnIndex(PuzzleDb.Params.COLUMN_NAME_PARAM_TYPE_ID);
            int paramValueColumn = cursor.getColumnIndex(PuzzleDb.Params.COLUMN_NAME_VALUE);
            int paramId = cursor.getInt(paramIdColumn);
            int paramValue = cursor.getInt(paramValueColumn);
            callback.setParam(paramId, paramValue);
        }
    }
   
    public void getPosition(Model callback, long id) {
        callback.clearTags();
        Uri uri = ContentUris.withAppendedId(PuzzleDb.Positions.CONTENT_URI, id);
        Cursor cursor = managedQuery(
                uri,
                PUZZLE_PROJECTION,
                "b." + PuzzleDb.Tags.COLUMN_NAME_PUZZLE_ID + " = ?",     
                new String[] {Long.toString(currentPuzzle)},
                null
            );
        for (cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {
            int tagIdColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_TAG_ID);
            int tagIxColumn = cursor.getColumnIndex(PuzzleDb.Tags.COLUMN_NAME_IX);
            int xColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_X);
            int yColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_Y);
            int imgColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_IMG_ID);
            int itemXColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_ITEM_X);
            int itemYColumn = cursor.getColumnIndex(PuzzleDb.Items.COLUMN_NAME_ITEM_Y);
            int tagId = cursor.getInt(tagIdColumn);
            int tagIx = cursor.getInt(tagIxColumn);
            int x = cursor.getInt(xColumn);
            int y = cursor.getInt(yColumn);
            int itemX = cursor.getInt(itemXColumn);
            int itemY = cursor.getInt(itemYColumn);
            long img = cursor.getLong(imgColumn);
            callback.addItem(tagId, x, y, (itemX == 0)&&(itemY == 0), tagIx, img);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        mainMenu = menu;
        super.onCreateOptionsMenu(menu);
        SubMenu newMenu = menu.addSubMenu(BASE_MENU, BASE_MENU + MENU_NEW_ITEM, Menu.NONE, getLocalizedString(MENU_NEW_ITEM));
        getMenu(newMenu);
        newMenu = menu.addSubMenu(BASE_MENU, BASE_MENU + MENU_PROFILE_ITEM, Menu.NONE, getLocalizedString(MENU_PROFILE_ITEM));
        getLocales(newMenu);
        return true;
    }

    public void reloadPuzzle() {
        saveProfile();
        loadProfile();
        view.loadPuzzle(currentPuzzle, currentPosition);
    }
   
    private void help() {
        long endPosId = getEndPositionId(currentPuzzle);
        if (endPosId > 0) {
            LayoutInflater inflater = getLayoutInflater();
            LinearLayout layout = (LinearLayout)inflater.inflate(R.layout.help,
                                           (ViewGroup) findViewById(R.id.help_page));
            MainView vw = new MainView(this, this);
            int sizeX = view.getPhysX() / 2;
            int sizeY = view.getPhysY() / 2;
            vw.setMaxSizes(sizeX, sizeY);
            vw.loadPuzzle(currentPuzzle, endPosId);
            layout.addView(vw);
            Toast toast = new Toast(getApplicationContext());
            toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
            toast.setDuration(Toast.LENGTH_LONG);
            toast.setView(layout);
            toast.show();
        }
    }
   
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();
        if (itemId >= BASE_NEW_MENU) {
            reloadPuzzle();
            help();
        } else if (itemId >= BASE_LOCALE_MENU) {
            currentLocale = itemId - BASE_LOCALE_MENU;
            stringCache.clear();
            view.invalidate();
            if (mainMenu != null) {
                mainMenu.clear();
                onCreateOptionsMenu(mainMenu);
            }
            saveProfile();
        }
        return true;
    }
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        LinearLayout.LayoutParams containerParams =
                new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT, 0.0F);
        LinearLayout root = new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(Color.LTGRAY);
        root.setLayoutParams(containerParams);
        view = new MainView(this, this);
        loadProfile();
        view.loadPuzzle(currentPuzzle, currentPosition);
        root.addView(view);
        setContentView(root);
   }
}


В PuzzleActivity стоит обратить внимание на метод help, реализующий вывод справки. Добавим новые методы (осуществляющие загрузку головоломки) в Model:

package com.whiterabbit.tags;

...
public class Model {

    ...
    private int marginSize = 0;
    private Map<Long, Tag> tags = new HashMap<Long, Tag>();
    private Map<Long, Tag> currTagSet = null;
    private Set<Map<Long, Tag>> endpositions = new HashSet<Map<Long, Tag>>();
    private PuzzleActivity callback;
   
    public Model(PuzzleActivity callback) {
        this.callback = callback;
    }

    public void loadPuzzle(int puzzleId, long positionId) {
        callback.getParams(this, puzzleId);
        callback.getPosition(this, positionId);
        currTagSet = new HashMap<Long, Tag>();
        callback.getEndPositions(this, puzzleId);
        if (!currTagSet.isEmpty()) {
            endpositions.add(currTagSet);
        }
        currTagSet = null;
        setSizes(physX, physY);
    }
   
    public void setParam(int paramId, int value) {
        switch (paramId) {
            case PuzzleDb.ParamTypes.SizeX:
                sizeX = value;
                break;
            case PuzzleDb.ParamTypes.SizeY:
                sizeY = value;
                break;
        }
    }
   
    public void clearTags() {
        if (currTagSet == null) {
            tags.clear();
            endpositions.clear();
        } else {
            if (!currTagSet.isEmpty()) {
                endpositions.add(currTagSet);
                currTagSet = new HashMap<Long, Tag>();
            }
        }
    }

    public void addItem(int tagId, int x, int y, boolean isMain, int tagIx, long type) {
        Map<Long, Tag> tagSet = currTagSet;
        if (tagSet == null) {
            tagSet = tags;
        }
        Long id = new Long(tagId);
        Tag t = tagSet.get(id);
        if (t == null) {
            t = new Tag(tagId);
            tagSet.put(id, t);
        }
        t.addItem(x, y);
    }
   
    public int getPhysX() {
        return sizeX * tagSize;
    }
   
    public int getPhysY() {
        return sizeY * tagSize;
    }
   
    public void setMarginSize() {
        marginSize = 3;
    }
   
    public void setSizes(int x, int y) {
        physX = x;
        physY = y;
        int szX = (physX - 2 * marginSize) / sizeX;
        int szY = (physY - 2 * marginSize) / sizeY;
        if (szX < szY) {
            tagSize = szX;
            marginY = (physY - tagSize * sizeY) / 2;
            marginX = marginSize;
        } else {
            tagSize = szY;
            marginY = marginSize;
            marginX = (physX - tagSize * sizeX) / 2;
        }
    }
    ...
}


Изменения в MainView тривиальны:

package com.whiterabbit.tags;
...
public class MainView extends View {

    ...
    public void loadPuzzle(int puzzleId, long positionId) {
        model.loadPuzzle(puzzleId, positionId);
        invalidate();
    }
   
    public int getPhysX() {
        return model.getPhysX();
    }
   
    public int getPhysY() {
        return model.getPhysY();
    }
   
    public void setMaxSizes(int x, int y) {
        maxX = x;
        maxY = y;
        model.setMarginSize();
    }
    ...
}


В PuzzleProvider добавим логику обработки новых запросов, традиционно исправим ошибки с неправильными идентификаторами (БД придется пересоздать, также как это делалось в предыдущей статье), а также добавим строковый ресурс и его локализацию для нового пункта меню (New):

package com.whiterabbit.tags;

...
public class PuzzleProvider extends ContentProvider {

   private static final String DATABASE_NAME = "puzzle.db";
   private static final int DATABASE_VERSION = 2;
  
   private DatabaseHelper mOpenHelper;
   private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
   private static final int STRING_URI_INDICATOR        = 1;
   private static final int LOCALE_URI_INDICATOR        = 2;
   private static final int PROFILE_URI_INDICATOR       = 3;
   private static final int PUZZLE_LIST_URI_INDICATOR   = 4;
   private static final int ENDPOS_ITEM_URI_INDICATOR   = 5;
   private static final int PARAMS_URI_INDICATOR        = 6;
   private static final int POSITION_ITEM_URI_INDICATOR = 7;

   static {
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Strings.PATH_STRING + "/#", STRING_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Locales.PATH_LOCALE, LOCALE_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Profiles.PATH_PROFILE, PROFILE_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Puzzles.PATH_PUZZLE, PUZZLE_LIST_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.EndPositions.PATH_ENDS  + "/#", ENDPOS_ITEM_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Params.PATH_PARAMS + "/#", PARAMS_URI_INDICATOR);
       uriMatcher.addURI(PuzzleDb.AUTHORITY, PuzzleDb.Positions.PATH_POSITION + "/#", POSITION_ITEM_URI_INDICATOR);
   }

   @Override
   public boolean onCreate() {
       mOpenHelper = new DatabaseHelper(getContext());
       return true;
   }

   @Override
   public String getType(Uri uri) {
       switch (uriMatcher.match(uri)) {
            case STRING_URI_INDICATOR:
                return PuzzleDb.Strings.CONTENT_ITEM_TYPE;
               case LOCALE_URI_INDICATOR:
                   return PuzzleDb.Locales.CONTENT_DIR_TYPE;
               case PROFILE_URI_INDICATOR:
                   return PuzzleDb.Profiles.CONTENT_DIR_TYPE;
               case PUZZLE_LIST_URI_INDICATOR:
                   return PuzzleDb.Puzzles.CONTENT_DIR_TYPE;
               case ENDPOS_ITEM_URI_INDICATOR:
                   return PuzzleDb.EndPositions.CONTENT_ITEM_TYPE;
            case PARAMS_URI_INDICATOR:
                return PuzzleDb.Params.CONTENT_ITEM_TYPE;
               case POSITION_ITEM_URI_INDICATOR:
                   return PuzzleDb.Positions.CONTENT_ITEM_TYPE;
       }
       return null;
   }

   @Override
   public Uri insert(Uri uri, ContentValues values) {
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       // TODO:
       return null;
   }

   @Override
   public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       switch (uriMatcher.match(uri)) {
          case PROFILE_URI_INDICATOR:
               return db.update(PuzzleDb.Profiles.TABLE_NAME,
                       values,
                       PuzzleDb.Profiles.COLUMN_NAME_IS_DEFAULT + " = 1",
                       null);
       }
       return 0;
   }
  
   @Override
   public int delete(Uri uri, String selection, String[] selectionArgs) {
       int r = 0;
       SQLiteDatabase db = mOpenHelper.getWritableDatabase();
       // TODO:
       return r;
   }

   @Override
   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
       SQLiteDatabase db = mOpenHelper.getReadableDatabase();
       switch (uriMatcher.match(uri)) {
          case STRING_URI_INDICATOR:
               return db.rawQuery("select " + PuzzleDb.StringValues._ID +
                                      ", " + PuzzleDb.StringValues.COLUMN_NAME_VALUE +
                                      " from " + PuzzleDb.StringValues.TABLE_NAME +
                                      " where " + PuzzleDb.StringValues.COLUMN_NAME_STRING_ID +
                                      " = " + uri.getPathSegments().get(1) +
                                      " and " + selection, selectionArgs);
           case LOCALE_URI_INDICATOR:
               return db.rawQuery("select a." + PuzzleDb.Locales._ID + " as " + PuzzleDb.Locales._ID + ", " +
                                   "b." + PuzzleDb.StringValues.COLUMN_NAME_VALUE + " as " + PuzzleDb.StringValues.COLUMN_NAME_VALUE + " " +
                                   "from " + PuzzleDb.Locales.TABLE_NAME + " a, " +
                                   PuzzleDb.StringValues.TABLE_NAME + " b " +
                                   "where b." + PuzzleDb.StringValues.COLUMN_NAME_STRING_ID + " = " +
                                   "a." + PuzzleDb.Locales.COLUMN_NAME_STRING_ID + " and " + selection, selectionArgs);
           case PUZZLE_LIST_URI_INDICATOR:
               return db.rawQuery("select " +
                                  "a." + PuzzleDb.Puzzles._ID + " as " + PuzzleDb.Puzzles._ID + ", " +
                                      "b." + PuzzleDb.StringValues.COLUMN_NAME_VALUE + " as " + PuzzleDb.StringValues.COLUMN_NAME_VALUE + " from " +
                                   PuzzleDb.Puzzles.TABLE_NAME + " a, " +
                                   PuzzleDb.StringValues.TABLE_NAME + " b where " +
                                  "b." + PuzzleDb.StringValues.COLUMN_NAME_STRING_ID + " = " +
                                  "a." + PuzzleDb.Puzzles.COLUMN_NAME_STRING_ID + " and " +
                                  "b." + selection, selectionArgs);
           case PROFILE_URI_INDICATOR:
               return db.rawQuery("select a." + PuzzleDb.Profiles._ID + " as " + PuzzleDb.Profiles._ID + ", " +
                                   "a." + PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID + " as " + PuzzleDb.Profiles.COLUMN_NAME_LOCALE_ID + ", " +
                                   "a." + PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID + " as " + PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID + ", " +
                                   "b." + PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID + " as " + PuzzleDb.Puzzles.COLUMN_NAME_START_POSITION_ID + " " +
                                   "from " + PuzzleDb.Profiles.TABLE_NAME + " a, " +
                                   PuzzleDb.Puzzles.TABLE_NAME + " b " +
                                   "where a." + PuzzleDb.Profiles.COLUMN_NAME_IS_DEFAULT + " = 1 " +
                                   "and b." + PuzzleDb.Puzzles._ID + " = a." + PuzzleDb.Profiles.COLUMN_NAME_PUZZLE_ID, selectionArgs);
           case ENDPOS_ITEM_URI_INDICATOR:
               return db.rawQuery("select " + PuzzleDb.EndPositions.COLUMN_NAME_POSITION_ID + " " +
                                  "from " + PuzzleDb.EndPositions.TABLE_NAME + " " +
                                  "where " + PuzzleDb.EndPositions.COLUMN_NAME_PUZZLE_ID + " = " + uri.getPathSegments().get(1), selectionArgs);
           case PARAMS_URI_INDICATOR:
               return db.rawQuery("select " +
                                   PuzzleDb.Params._ID + ", " +
                                   PuzzleDb.Params.COLUMN_NAME_PARAM_TYPE_ID + ", " +
                                   PuzzleDb.Params.COLUMN_NAME_VALUE + " from " +
                                   PuzzleDb.Params.TABLE_NAME + " where " +
                                   PuzzleDb.Params.COLUMN_NAME_PUZZLE_ID + " = " +
                                   uri.getPathSegments().get(1), selectionArgs);
           case POSITION_ITEM_URI_INDICATOR:
               return db.rawQuery("select " +
                                   "b." + PuzzleDb.Tags.COLUMN_NAME_IX + " as " + PuzzleDb.Tags.COLUMN_NAME_IX + ", " +
                                   "d." + PuzzleDb.Items._ID + " as " + PuzzleDb.Items._ID + ", " +
                                   "b." + PuzzleDb.Tags._ID + "  as " + PuzzleDb.Items.COLUMN_NAME_TAG_ID + ", " +
                                   "c." + PuzzleDb.TagPositions.COLUMN_NAME_X + " + d." + PuzzleDb.Items.COLUMN_NAME_X + " as " + PuzzleDb.Items.COLUMN_NAME_X + ", " +
                                   "c." + PuzzleDb.TagPositions.COLUMN_NAME_Y + " + d." + PuzzleDb.Items.COLUMN_NAME_Y + " as " + PuzzleDb.Items.COLUMN_NAME_Y + ", " +
                                   "d." + PuzzleDb.Items.COLUMN_NAME_IMG_ID + " as " + PuzzleDb.Items.COLUMN_NAME_IMG_ID + ", " +
                                   "d." + PuzzleDb.Items.COLUMN_NAME_X + " as " + PuzzleDb.Items.COLUMN_NAME_ITEM_X + ", " +
                                   "d." + PuzzleDb.Items.COLUMN_NAME_Y + " as " + PuzzleDb.Items.COLUMN_NAME_ITEM_Y + " " +
                                   "from   " + PuzzleDb.Tags.TABLE_NAME + " b, " + PuzzleDb.TagPositions.TABLE_NAME + " c, " + PuzzleDb.Items.TABLE_NAME + " d " +
                                   "where  c." + PuzzleDb.TagPositions.COLUMN_NAME_TAG_ID + " = b." + PuzzleDb.Tags._ID + " " +
                                   "and    d." + PuzzleDb.Items.COLUMN_NAME_TAG_ID + " = b." + PuzzleDb.Tags._ID + " " +
                                   "and " + selection + " " +
                                   "and    c." + PuzzleDb.TagPositions.COLUMN_NAME_POSITION_ID + " = " +
                                   uri.getPathSegments().get(1), selectionArgs);
       }
       return null;
   }

   static class DatabaseHelper extends SQLiteOpenHelper {

       ...
       @Override
       public void onCreate(SQLiteDatabase db) {
          
           ...
           patch_2(db);
       }

       private void patch_2(SQLiteDatabase db) {
          
           addString(db, "6"); // New
          
           addValue(db, "9,  1,  6, 'New'");
           addValue(db, "10, 2,  6, 'Заново'");
       }
      
       @Override
       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
           if (oldVersion < 2)  {patch_2(db);}
       }
          
   }
}


Обратите внимание, что добавление строкового ресурса мы оформили патчем, увеличив номер версии БД (поскольку БД пришлось пересоздавать, делать это, строго говоря, было совсем необязательно).

Запустив приложение, и выполнив пункт меню New, увидим вполне вменяемую подсказку, показывающую финальную позицию головоломки:






В следующей статье, мы создадим новую Activity, для отображения списка рекордов. Исходники по текущему состоянию проекта можно скачать здесь.