October 10, 2018

Импорт/экспорт данных приложения. Практика

Предыдущая часть: https://teletype.in/@snowvolf/BJeE3fK5X

Создадим приложение. Имя пакета приложения, в моём случае, ru.SnowVolf.backupfactory. Учтите это, при попытке воспроизвести мой код.

Откройте файл AndroidManifest.xml и добавьте внутри тега <manifest/>:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

(Разрешение на запись и чтение памяти соответственно)

Внутри тега <application/> добавьте обьявление новой активности:

<activity android:name=".BackupActivity"/>

Проигнорируйте ошибку IDE, мы исправим её позже.

Полный листинг AndroidManifest.xml приведён ниже:

Листинг 1. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="ru.SnowVolf.backupfactory">
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <application
    android:name=".App"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".BackupActivity"/>
    <activity android:name=".MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
  </application>

</manifest>

Создайте класс App.java и напишите следующий код:

Листинг 2. Синглтон Application.

package ru.SnowVolf.backupfactory;


import android.app.Application;

public class App extends Application {
  private static App instance = null;
  private static SharedPreferences preferences;

  /**
   * Called when the application is starting, before any activity, service,
   * or receiver objects (excluding content providers) have been created.
   * Implementations should be as quick as possible (for example using
   * lazy initialization of state) since the time spent in this function
   * directly impacts the performance of starting the first activity,
   * service, or receiver in a process.
   * If you override this method, be sure to call super.onCreate().
   */
  @Override
  public void onCreate() {
    super.onCreate();
    instance = this;
  }

  public static App get(){
    if (instance == null){
      instance = new App();
    }
    return instance;
  }
}

Вернитесь к манифесту. Одна ошибка должна исчезнуть.

Перейдите в каталог ресурсов вашего проекта, и откройте файл разметки главной активности (в моём случае - activity_main.xml) и сверстайте следующую разметку:

Листинг 3. Разметка activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  xmlns:android="http://schemas.android.com/apk/res/android" >

  <TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentStart="true"
    android:layout_alignParentTop="true"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="8dp"
    android:text="Settings test"
    android:textAppearance="@android:style/TextAppearance.Material.Body2"/>

  <EditText
    android:id="@+id/fieldName"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/textView"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:hint="Your name"
    android:maxLines="1" />

  <Button
    android:id="@+id/buttonGet"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignEnd="@+id/fieldName"
    android:layout_below="@+id/fieldName"
    android:textAllCaps="false"
    android:text="Get" />

  <Button
    android:id="@+id/buttonPut"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/fieldName"
    android:layout_toStartOf="@+id/buttonGet"
    android:textAllCaps="false"
    android:text="Set" />

  <TextView
    android:id="@+id/textView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignStart="@+id/fieldName"
    android:layout_below="@+id/buttonPut"
    android:text="Database Test"
    android:textAppearance="@android:style/TextAppearance.Material.Body2" />

  <TextView
    android:id="@+id/txtDB"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/textView2"
    android:layout_marginEnd="16dp"
    android:layout_marginStart="16dp"
    android:layout_marginTop="8dp" />

  <EditText
    android:id="@+id/fieldInsertCount"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignStart="@+id/txtDB"
    android:layout_below="@+id/txtDB"
    android:layout_toStartOf="@+id/btnInsert"
    android:maxEms="2"
    android:hint="Insert count"
    android:inputType="phone"
    android:maxLines="1" />

  <Button
    android:id="@+id/btnInsert"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignBottom="@+id/fieldInsertCount"
    android:layout_alignEnd="@+id/buttonGet"
    android:text="Insert"
    android:textAllCaps="false"/>

</RelativeLayout>

Создайте новый класс BackupFactory.java и поместите туда код из прошлой статьи. Я продублировал его здесь для удобства:

/*
 * Copyright (c) 2017 Deletescape.
 * Copyright (c) 2018 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;


/**
 * Created by Snow Volf on 05.08.2018, 12:12
 *
 * Класс экспорта/импорта БД и настроек
 *
 * Основано на: https://github.com/Deletescape/Lawnchair/app/src/main/java/ch/deletescape/lawnchair/DumbImportExportTask.java
 */

public class BackupFactory {
  // Экспорт БД
  public static void exportDb(Activity activity, String dbFiles) {
    ContextWrapper contextWrapper = new ContextWrapper(activity);
    String[] dbs = dbFiles.split(",");
    for (String db : dbs) {
      File expDb = contextWrapper.getDatabasePath(db);
      exportFile(expDb, activity);
    }
  }

  //Импорт БД
  public static void importDb(Activity activity, String dbFiles) {
    ContextWrapper contextWrapper = new ContextWrapper(activity);
    String[] dbs = dbFiles.split(",");
    for (String db : dbs) {
      File expDb = contextWrapper.getDatabasePath(db);
      importFile(expDb, activity);
    }
  }

  // Экспорт файла настроек
  public static void exportPrefs(Activity activity) {
    ApplicationInfo info = activity.getApplicationInfo();
    String dir = new ContextWrapper(activity).getCacheDir().getParent();
    File prefs = new File(dir, "shared_prefs/" + info.packageName + "_preferences.xml");
    exportFile(prefs, activity);
  }

  // Импорт файла настроек
  public static void importPrefs(Activity activity) {
    ApplicationInfo info = activity.getApplicationInfo();
    String dir = new ContextWrapper(activity).getCacheDir().getParent();
    File prefs = new File(dir, "shared_prefs/" + info.packageName + "_preferences.xml");

    importFile(prefs, activity);
  }

  // Экспорт файла
  private static void exportFile(File file, Activity activity) {
    // Если нет разрешения на запись
    if (!isExternalStorageWritable() || !canWriteStorage(activity)) {
      Toast.makeText(activity, "No write read/permission!", Toast.LENGTH_SHORT).show();
      return;
    }
    File backup = new File(getFolder(), file.getName());
    if (backup.exists()) {
      backup.delete();
    }
    if (copy(file, backup)) {
      Toast.makeText(activity, "Successful!", Toast.LENGTH_SHORT).show();
    } else {
      Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
  }

  // Импорт файла
  private static void importFile(File file, Activity activity) {
    // Если нет разрешения на запись
    if (!isExternalStorageReadable() || !canWriteStorage(activity)) {
      Toast.makeText(activity, "No write read/permission!", Toast.LENGTH_SHORT).show();
      return;
    }
    File backup = new File(getFolder(), file.getName());
    if (!backup.exists()) {
      Toast.makeText(activity, String.format("Backups not found in %s", getFolder().getAbsolutePath()), Toast.LENGTH_LONG).show();
      return;
    }
    if (file.exists()) {
      file.delete();
    }
    if (copy(backup, file)) {
      Toast.makeText(activity, "Successful!", Toast.LENGTH_SHORT).show();
    } else {
      Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
  }

  // Получение пути к папке бэкапов
  // [SDCARD]/Documents/backup/

  private static File getFolder() {
    File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "backup/");
    if (!folder.exists()) {
      folder.mkdirs();
    }
    return folder;
  }

  // Проверка на возможность записи
  private static boolean canWriteStorage(Activity activity) {
    return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
        activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            == PackageManager.PERMISSION_GRANTED;
  }

  // Копирование файлов тудым-сюдым
  private static boolean copy(File inFile, File outFile) {
    FileInputStream in;
    FileOutputStream out;
    try {
      in = new FileInputStream(inFile);
      out = new FileOutputStream(outFile);
      byte[] buffer = new byte[1024];
      int read;
      while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
      }
      in.close();

      // write the output file
      out.flush();
      out.close();
      return true;
    } catch (Exception e) {
      Log.e("BackupFactory", e.getMessage());
      e.printStackTrace();
    }
    return false;
  }

  /* Checks if external storage is available for read and write */
  private static boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state);
  }

  /* Checks if external storage is available to at least read */
  private static boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state) ||
 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
  }
}

Создадим класс BackupActivity.java и напишем следующий код:

Листинг 4. Активность восстановления.

package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

public class BackupActivity extends Activity {
  private Activity mActivity;
  private String namesOfDb = "test.db";
  private String[] options = {"Import databases", "Export databases", "Import preferences",
 "Export preferences", "Apply changes and restart"};


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
		mActivity = this;
    setContentView(createView());
  }

  private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );

    content.setLayoutParams(paramsMatch);
		
		return content;
	}
	
	@Override
  protected void onDestroy() {
    mActivity = null;
    namesOfDb = null;
    options = null;
    super.onDestroy();
  }
}

Разметка для этого экрана будет создана полностью из кода (для более удобного инжекта в другие программы при помощи smali). На данный момент мы создали только корневое представление View, т.е. пустой экран.

Заполним его списком с прикреплёнными действиями.

Допишем следующий код в метод createView():

		// Создаём список
		ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
		// Создаём адаптер для списка. В качестве разметки используем встроенный в framework ресурс.
		// В качестве заголовков для пунктов используем элементы массива options.
    ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options);
		// Присваиваем адаптер
    list.setAdapter(adapter);
		// Присваиваем реакцию на нажатия в зависимости от позиции в списке
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);

В итоге, ваш метод createView() должен выглядеть так:

Листинг 5. Метод createView().

private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );
    ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
    ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options);
    list.setAdapter(adapter);
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);

    return content;
  }

Особо наблюдательные могли заметить отсутствие метода restartApp(). Давайте напишем его.

Листинг 6. Метод restartApp().

private void restartApp(){
    Intent mStartActivity = new Intent(this, MainActivity.class);
    mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    int mIntentPendingId = 6;
    PendingIntent mPendingIntent = PendingIntent.getActivity(this, mIntentPendingId,
 mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager manager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    manager.set(AlarmManager.RTC, System.currentTimeMillis() + 300, mPendingIntent);
    Process.killProcess(Process.myPid());
    System.exit(0);
  }

Полный листинг активности приведён ниже:

Листинг 7. Класс BackupActivity.java

package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

public class BackupActivity extends Activity {
  private Activity mActivity;
  private String namesOfDb = "test.db";
  private String[] options = {"Import databases", "Export databases", "Import preferences",
 "Export preferences", "Apply changes then restart"};

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mActivity = this;
    setContentView(createView());
  }

  private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );
    ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
    ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options);
    list.setAdapter(adapter);
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);

    return content;
  }

  private void restartApp(){
    Intent mStartActivity = new Intent(this, MainActivity.class);
    mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    int mIntentPendingId = 6;
    PendingIntent mPendingIntent = PendingIntent.getActivity(this, mIntentPendingId,
 mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager manager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    manager.set(AlarmManager.RTC, System.currentTimeMillis() + 300, mPendingIntent);
    Process.killProcess(Process.myPid());
    System.exit(0);
  }
	
	@Override
  protected void onDestroy() {
    mActivity = null;
    namesOfDb = null;
    options = null;
    super.onDestroy();
  }
}

Всё. С этим закончили. Код можно свободно инжектить в любую программу.

Создадим пример

Вернитесь к MainActivity.java и напишите такой код:

Листинг 8. Основная активность

package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Locale;

public class MainActivity extends Activity {
  private TextView txtDb;
  private EditText mPrefValue, mNumOfItems;
  private Button btnGetPref, btnSetPref, btnAddItems;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txtDb = findViewById(R.id.txtDB);

    mPrefValue = findViewById(R.id.fieldName);
    mNumOfItems = findViewById(R.id.fieldInsertCount);

    btnAddItems = findViewById(R.id.btnInsert);
    btnGetPref = findViewById(R.id.buttonGet);
    btnSetPref = findViewById(R.id.buttonPut);

    final TestDB db = new TestDB(getApplicationContext());

    txtDb.setText(String.format(Locale.ENGLISH, "Number of items in abstract DB: %d", db.getAllItems().size()));

    btnAddItems.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        int count = Integer.parseInt(mNumOfItems.getText().toString());
        if ( count > 0 && count <= 99){

          for (int i = 0; i < count; i++) {
            db.addItem(new TestDbItem(System.currentTimeMillis()));
          }
        } else {
          Toast.makeText(MainActivity.this, "Very big number", Toast.LENGTH_SHORT).show();
        }
      }
    });
    btnSetPref.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        App.get().getPreferences().edit().putString("name", mPrefValue.getText().toString()).apply();
      }
    });

    btnGetPref.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        mPrefValue.setText(App.get().getPreferences().getString("name", "empty"));
      }
    });
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_settings){
      startActivity(new Intent(this, BackupActivity.class));
      return true;
    }
    return super.onOptionsItemSelected(item);
  }
}

Активность ссылается на 2 несущесуществующих класса. Создадим их.

Листинг 9. TestDbItem.java

/*
 * Copyright (c) 2017 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

/**
 * Created by Snow Volf on 04.06.2017, 22:44
 */

public class TestDbItem {
  private long id = -1L;

  public TestDbItem(long id) {
    this.id = id;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }
}

Последнее - класс для работы с базой данных SQLiteDatabase

Листинг 9. TestDB.java

/*
 * Copyright (c) 2017 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Snow Volf on 04.06.2017, 22:52
 */

public class TestDB extends SQLiteOpenHelper {

  public TestDB(Context context) {
    super(context, "test.db", null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
    db.execSQL("CREATE TABLE test_db (id INTEGER)");
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // Update this when db table will be changed
  }

  public void addItem(TestDbItem item) {
    if (item.getId() == -1) {
      item.setId(System.currentTimeMillis());
    }

    ContentValues values = new ContentValues();
    values.put("id", System.currentTimeMillis());

    SQLiteDatabase db = getWritableDatabase();
    db.insert("test_db", null, values);
    db.close();
  }

  public List<TestDbItem> getAllItems() {
    List<TestDbItem> list = new ArrayList<>();
    SQLiteDatabase db = getReadableDatabase();
    Cursor cursor = db.rawQuery("SELECT * FROM " + "test_db", null);

    if (cursor.moveToFirst()) {
      do {
        list.add(new TestDbItem((cursor.getLong(0))));
      } while (cursor.moveToNext());
    }
    cursor.close();
    db.close();
    return list;
  }

  public void deleteAll() {
    SQLiteDatabase db = getWritableDatabase();
    db.delete("test_db", "id" + ">=?", new String[]{"0"});
    db.close();
  }
}

Проверьте код на ошибки, их не должно быть. Скомпилируйте приложение.