Compare commits

...

4 Commits
wm ... main

Author SHA1 Message Date
hejian b553191e9a v1.0
2 weeks ago
sasa 1cbfff9508 v1.0
2 weeks ago
sasa 4181a5daa7 v1.0
3 weeks ago
hejian b7293111f0 v1.0
3 weeks ago

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

@ -0,0 +1,65 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "net.micode.notes"
compileSdk = 34
defaultConfig {
applicationId = "net.micode.notes"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
packaging {
resources.excludes.add("META-INF/DEPENDENCIES");
resources.excludes.add("META-INF/NOTICE");
resources.excludes.add("META-INF/LICENSE");
resources.excludes.add("META-INF/LICENSE.txt");
resources.excludes.add("META-INF/NOTICE.txt");
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
// implementation(fileTree(mapOf(
// "dir" to "G:\\AndroidProject\\Notesmaster2\\httpcomponents-client-4.5.14-bin\\lib",
// "include" to listOf("*.aar", "*.jar"),
// "exclude" to listOf("")
// )))
// 部分需要重新修改
// implementation(fileTree(mapOf(
// "dir" to "D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib",
// "include" to listOf("*.aar", "*.jar"),
// "exclude" to listOf("")
// )))
//修改为如下代码:
implementation(files("G:\\AndroidProject\\Notesmaster2\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-osgi-4.5.14.jar"))
implementation(files("G:\\AndroidProject\\Notesmaster2\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-win-4.5.14.jar"))
implementation(files("G:\\AndroidProject\\Notesmaster2\\httpcomponents-client-4.5.14-bin\\lib\\httpcore-4.4.16.jar"))
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

@ -0,0 +1,26 @@
package net.micode.notes;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("net.micode.notes", appContext.getPackageName());
}
}

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notesmaster"
tools:targetApi="31">
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
<!-- <activity-->
<!-- android:name=".MainActivity"-->
<!-- android:exported="true">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
<!-- </activity>-->
</application>
</manifest>

@ -0,0 +1,24 @@
package net.micode.notes;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

@ -26,17 +26,25 @@ import android.util.Log;
import java.util.HashMap;
public class Contact {
// 用于处理联系人信息
// 实现了从联系人数据库中获取指定电话号码对应的联系人姓名的功能
// sContactCache用于缓存电话号码和对应的联系人姓名
// TAG用于日志输出的标识
private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact";
//SQL查询条件 WHERE 后面的语句),用于从联系人数据库中筛选出与给定电话号码匹配的联系人。
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
//功能简介用于从Android设备的联系人数据库中获取与给定电话号码对应的联系人姓名。
//参数:Context对象:用于访问系统服务和应用资源 phoneNumber:需要查询的联系人电话号码
public static String getContact(Context context, String phoneNumber) {
// 没映射表就建表,有就查缓存中有没有这个联系人
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
@ -44,7 +52,9 @@ public class Contact {
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
//缓存没有,就查询数据库
//构造一个SQL查询条件CALLER_ID_SELECTION中的"+"被替换为电话号码的最小匹配值
//然后执行查询语句
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
Cursor cursor = context.getContentResolver().query(
@ -53,6 +63,13 @@ public class Contact {
selection,
new String[] { phoneNumber },
null);
//判断查询结果:
//查询结果不为空,且能够移动到第一条记录:
// 那么就尝试从Cursor中获取联系人姓名并将其存入缓存sContactCache。然后返回联系人姓名。
// 异常情况如果在获取字符串时发生数组越界异常则记录一个错误日志并返回null。
// 最后都要确保关闭Cursor对象以避免内存泄漏。
//如果查询结果为空或者没有记录可以移动到(即没有找到匹配的联系人):
// 则记录一条调试日志并返回null
if (cursor != null && cursor.moveToFirst()) {
try {

@ -18,8 +18,10 @@ package net.micode.notes.data;
import android.net.Uri;
public class Notes {
// 这里是定义了基本的信息,即认证信息和日志输出时的标志,方便我们了解日志信息是由谁发出的。
public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
// 这边定义了note表中类型行的3种取值
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
@ -30,11 +32,16 @@ public class Notes {
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
// ID_ROOT_FOLDER默认文件夹
// ID_TEMPARAY_FOLDER不属于文件夹的笔记
// ID_CALL_RECORD_FOLDER用于存储通话记录以便返回
// ID_TRASH_FOLER垃圾回收站
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
// 个人理解为就是定义一些布局的ID这部分就是用于设置UI界面的一些布局或小组件的id。
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
@ -46,6 +53,7 @@ public class Notes {
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
// 这里定义了两种数据类型:文本便签和通话记录
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
@ -54,12 +62,16 @@ public class Notes {
/**
* Uri to query all notes and folders
*/
// Android开发中常见的用于定义内容提供者Content ProviderURI内容提供者是一种Android组件它允许应用程序共享和存储数据。这里定义了一个URI来查询数据
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
//这个接口定义了一系列静态的、最终的字符串常量,这些常量代表数据库表中的列名。
//
//里面定义的属性有ID、父级ID、创建日期、修改日期、提醒日期、文件标签摘要、小部件ID、小部件类型、背景颜色ID、附件、文件中的标签数量、 文件标签类型、最后一个同步ID、本地修改标签、移动前的ID、谷歌任务ID、代码版本信息
public interface NoteColumns {
/**
@ -179,30 +191,35 @@ public class Notes {
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
//MIME类型是一种标准用于标识文档、文件或字节流的性质和格式。在数据库中这个字段可以用来识别不同类型的数据例如文本、图片、音频或视频等。
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
// 归属的Note的ID
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
//创建日期
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
//最近修改日期
/**
* Data's content
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
//数据内容
/**
@ -210,6 +227,10 @@ public class Notes {
* integer data type
* <P> Type: INTEGER </P>
*/
// 以下5个是通用数据列它们的具体意义取决于MIME类型由MIME_TYPE字段指定
// 不同的MIME类型可能需要存储不同类型的数据这5个字段提供了灵活性允许根据MIME类型来存储相应的数据。
// 读后面的代码感觉这部分是在表示内容的不同状态?
public static final String DATA1 = "data1";
/**
@ -241,39 +262,43 @@ public class Notes {
public static final String DATA5 = "data5";
}
//以下是文本便签的定义
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
//模式这个被存在DATA1列中
public static final String MODE = DATA1;
//所处检查列表模式?
public static final int MODE_CHECK_LIST = 1;
// 定义了MIME类型用于标识文本标签的目录
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 定义了MIME类型用于标识文本标签的单个项
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
//文本标签内容提供者Content Provider的URI用于访问文本标签数据
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// 通话记录的定义
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
//一个字符串常量,表示通话记录的日期
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
//意味着在数据库表中这个电话号码信息将被存储在DATA3列中
public static final String PHONE_NUMBER = DATA3;
// 同样定义了MIME类型是用于标识通话记录的目录。
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 同样定义了MIME类型是用于标识通话记录的单个项。
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
//定义了通话记录内容提供者的URI用于访问通话记录数据。
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -28,20 +28,24 @@ import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库帮助类,用于管理名为 note.db 的 SQLite 数据库。
// 它继承自 SQLiteOpenHelper 类,这是 Android提供的一个方便的工具类用于管理数据库的创建和版本更新.
// 数据库的基本信息;数据库名称和版本信息(在创建实例对象时会用到)
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 4;
//内部接口个人理解为两个表名一个note一个data
public interface TABLE {
public static final String NOTE = "note";
public static final String DATA = "data";
}
//一个标签,方便日志输出时识别出信息来自哪里
private static final String TAG = "NotesDatabaseHelper";
//静态所有变量,提供一个全局访问点来获取数据库辅助类的唯一实例,使得在应用的任何地方都可以方便地使用它
private static NotesDatabaseHelper mInstance;
/* 以下都是一些SQL语句辅助我们来对数据库进行操作 */
//创建note表的语句这里的NoteColumns就是我们刚刚在Notes中定义的一个接口里面定义了一系列静态的数据库表中的列名
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
@ -62,7 +66,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
//同上创建data表的语句这里的DataColumns就是我们刚刚在Notes中定义的一个接口里面定义了一系列静态的数据库表中的列名
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
@ -77,14 +81,34 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
// 功能简介:
// 创建一个以note的ID为索引
// 解读:
// 用于在TABLE.DATA表上创建一个名为note_id_index的索引。
// 这个索引是基于DataColumns.NOTE_ID列的。IF NOT EXISTS确保了如果索引已经存在那么就不会尝试重新创建它避免了可能的错误。
// 索引通常用于提高查询性能,特别是在对某个字段进行频繁查询时。
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/* 以下是一些对便签增删改定义的触发器 */
/*
* NOTEDATA
* NOTE
* */
/**
* Increase folder's note count when move note to the folder
*/
// 功能简介:
// 添加触发器:增加文件夹的便签个数记录(因为我们会移动便签进入文件夹,这时候文件夹的计数要进行更新)
// 解读:
// 定义了一个SQL触发器increase_folder_count_on_update。
// 触发器是一种特殊的存储过程它会在指定表上的指定事件如INSERT、UPDATE、DELETE发生时自动执行。
// 这个触发器会在TABLE.NOTE表的NoteColumns.PARENT_ID字段更新后执行。
// 触发器的逻辑是当某个笔记的PARENT_ID即父文件夹ID被更新时它会找到对应的文件夹通过新的PARENT_ID并将该文件夹的NOTES_COUNT即笔记数增加1。
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
@ -97,6 +121,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Decrease folder's note count when move note from folder
*/
// 功能简介:(触发器和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了)
// 添加触发器:减少文件夹的便签个数记录(因为我们会移动便签移出文件夹,这时候文件夹的计数要进行更新)
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
@ -110,6 +137,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Increase folder's note count when insert new note to the folder
*/
// 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了)
// 添加触发器:当我们在文件夹插入便签时,增加文件夹的便签个数记录
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
@ -122,6 +151,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Decrease folder's note count when delete note from the folder
*/
// 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了)
// 添加触发器:当我们在文件夹删除便签时,减少文件夹的便签个数记录
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -135,6 +166,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
// 功能简介:
// 添加触发器当向DATA表中插入类型为NOTE便签的数据时更新note表对应的笔记内容。
// 解读:
// 在DATA表上进行INSERT操作后如果新插入的数据的MIME_TYPE为NOTE则触发此操作。
// 它会更新NOTE表将与新插入数据相关联的标签的SNIPPET摘要字段设置为新插入数据的CONTENT字段的值
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
@ -148,6 +185,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
// 功能简介:
// 添加触发器当DATA表中类型为NOTE便签的数据更改时更新note表对应的笔记内容。
// 解读:
// 在DATA表上进行UPDATE操作后如果更新前的数据的MIME_TYPE为NOTE则触发此操作。
// 它会更新NOTE表将与更新后的数据相关联的笔记的SNIPPET字段设置为新数据的CONTENT字段的值
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
@ -161,6 +204,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
// 功能简介:
// 添加触发器当DATA表中类型为NOTE便签的数据删除时更新note表对应的笔记内容置空
// 解读:
// 在DATA表上进行DELETE操作后如果删除的数据的MIME_TYPE为NOTE则触发此操作。
// 它会更新NOTE表将与删除的数据相关联的笔记的SNIPPET字段设置为空字符串。
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
@ -174,6 +223,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Delete datas belong to note which has been deleted
*/
// 功能简介:
// 添加触发器当从NOTE表中删除笔记时删除与该笔记相关联的数据就是删除data表中为该note的数据
// 解读:
// 在NOTE表上进行DELETE操作后此触发器被激活。
// 它会从DATA表中删除所有与已删除的笔记由old.ID表示相关联的数据行通过比较DATA表中的NOTE_ID字段与已删除笔记的ID来实现
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -185,6 +240,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Delete notes belong to folder which has been deleted
*/
// 功能简介:
// 添加触发器当从NOTE表中删除一个文件夹时删除该文件夹下的所有笔记。
// 解读:
// 在NOTE表上进行DELETE操作后如果删除的是一个文件夹由old.ID表示
// 触发器会删除所有以该文件夹为父级PARENT_ID的笔记通过比较NOTE表中的PARENT_ID字段与已删除文件夹的ID来实现
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
@ -196,6 +257,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Move notes belong to folder which has been moved to trash folder
*/
// 功能简介:
// 添加触发器:当某个文件夹被移动到回收站时,移动该文件夹下的所有笔记到回收站
// 解读:
// 在NOTE表上进行UPDATE操作后如果某个文件夹的新PARENT_ID字段值等于回收站的IDNotes.ID_TRASH_FOLER
// 触发器会更新所有以该文件夹为父级PARENT_ID的笔记将它们也移动到回收站。
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
@ -205,17 +272,20 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
// 构造器
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建note标签
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
// 重新创建或更新与笔记表相关的触发器。
// 首先使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前不存在同名的触发器。
// 然后使用db.execSQL()方法执行预定义的SQL语句这些语句用于创建新的触发器。
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
@ -234,6 +304,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/* 以下部分是操作SQLite数据库部分 */
// 功能简介:
// 创建通话记录文件夹、默认文件夹、临时文件夹和回收站,并插入相关数据
// 具体解读:
// ContentValues是一个用于存储键值对的类常用于SQLite数据库的插入操作
// values.put方法可以向ContentValues对象中添加数据。
// NoteColumns.ID是存储文件夹ID的列名Notes.ID_CALL_RECORD_FOLDER是通话记录文件夹的ID。
// NoteColumns.TYPE是存储文件夹类型的列名Notes.TYPE_SYSTEM表示这是一个系统文件夹。
// 使用db.insert方法将values中的数据插入到TABLE.NOTE即标签表中。
// 每次插入新数据前都使用values.clear()方法清除ContentValues对象中的旧数据确保不会重复插入旧数据。
// 然后分别创建默认文件夹、临时文件夹和回收站,并以同样的方法插入数据。
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
@ -248,6 +329,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* root folder which is default folder
*/
// 创建默认文件夹:重复上述步骤,但这次是为根文件夹插入数据。
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
@ -256,6 +338,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* temporary folder which is used for moving note
*/
// 创建“临时”文件夹:同样地,为临时文件夹插入数据。
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
@ -264,11 +348,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* create trash folder
*/
// 创建“回收站”文件夹:最后,为回收站文件夹插入数据。
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
//功能简介:
//创建data数据
//解读:
//这个方法用于创建数据表,以及与之相关的触发器。
//创建数据表使用db.execSQL方法执行预定义的SQL语句CREATE_DATA_TABLE_SQL用于创建数据表。
//重新创建数据表触发器调用reCreateDataTableTriggers方法用于删除并重新创建与数据表相关的触发器。
//创建索引使用db.execSQL方法执行CREATE_DATA_NOTE_ID_INDEX_SQL语句为数据表创建索引。
//记录日志使用Log.d方法记录一条调试级别的日志表示数据表已经创建。
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
@ -276,6 +370,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}
//和上面的note表的reCreate...同理
//重新创建或更新与笔记表相关的触发器。
//首先使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前不存在同名的触发器。
//然后使用db.execSQL()方法执行预定义的SQL语句这些语句用于创建新的触发器。
private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
@ -286,6 +385,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
//解读:
//synchronized关键字确保在多线程环境下只有一个线程能够进入这个方法防止了同时创建多个实例的情况
//getInstance(Context context)方法使用了单例模式来确保整个应用程序中只有一个NotesDatabaseHelper实例。
//它首先检查mInstance类的静态成员变量没有在代码片段中显示是否为null。
//如果是null则创建一个新的NotesDatabaseHelper实例并将其赋值给mInstance。最后返回mInstance。
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
@ -293,13 +397,19 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
}
return mInstance;
}
//功能简介:
//当数据库首次创建时onCreate方法会被调用。
//这里重写onCreate方法它调用了上述createNoteTable(db)和createDataTable(db)两个方法
//这样首次创建数据库时就多出了两张表。
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
//功能简介:
//当数据库需要升级时即数据库的版本号改变onUpgrade方法会被调用。
//该方法会根据当前的oldVersion和新的newVersion来执行相应的升级操作
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
@ -332,6 +442,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
+ "fails");
}
}
//功能简介:
// 将数据库从版本1升级到版本2。
//解读:
// 首先它删除了已经存在的NOTE和DATA表如果存在的话。DROP TABLE IF EXISTS语句确保了即使这些表不存在也不会抛出错误。
// 然后它调用了createNoteTable(db)和createDataTable(db)方法来重新创建这两个表。这意味着在升级到版本2时这两个表的内容会被完全清除并重新创建新的空表。
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
@ -339,6 +454,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createNoteTable(db);
createDataTable(db);
}
//功能简介:
// 将数据库从版本2或可能是跳过版本2的某个状态升级到版本3。
//解读:
// 首先,删除了三个不再使用的触发器(如果存在的话)。触发器是数据库中的一种对象,可以在插入、更新或删除记录时自动执行某些操作。
// 然后使用ALTER TABLE语句修改表结构向NOTE表中添加了一个名为GTASK_ID的新列并设置默认值为空字符串。
// 最后向NOTE表中插入了一条新的系统文件夹记录表示一个名为“trash folder”的系统文件夹。这可能是用于存储已删除笔记的回收站功能。
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
@ -354,7 +475,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
//功能简介:
// 这个方法负责将数据库从版本3升级到版本4。
//解读:
// 它向NOTE表中添加了一个名为VERSION的新列并设置了默认值为0。这个新列用于记录标签版本信息。
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");

@ -36,12 +36,23 @@ import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// Android 应用程序中的一部分内容提供者ContentProvider
// 内容提供者是 Android 四大组件之一,它允许应用程序之间共享数据。
//概述:
//NotesProvider的主要功能是作为一个内容提供者为其他应用程序或组件提供对“Notes”数据的访问。
//它允许其他应用程序查询、插入、更新或删除标签数据。
//通过URI匹配NotesProvider能够区分对哪种数据类型的请求例如单独的标签、标签的数据、文件夹操作等并执行相应的操作。
//用于匹配不同URI的UriMatcher对象通常用于解析传入的URI并确定应该执行哪种操作。
private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper;
//NotesDatabaseHelper实类用来操作SQLite数据库负责创建、更新和查询数据库。
private NotesDatabaseHelper mHelper;
//标签,输出日志时用来表示是该类发出的消息
private static final String TAG = "NotesProvider";
//6个URI的匹配码用于区分不同的URI类型
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
@ -49,14 +60,23 @@ public class NotesProvider extends ContentProvider {
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
//进一步定义了URI匹配规则和搜索查询的投影
//功能概述:
//初始化了一个UriMatcher对象mMatcher并添加了一系列的URI匹配规则。
//解读:
static {
//创建了一个UriMatcher实例并设置默认匹配码为NO_MATCH表示如果没有任何URI匹配则返回这个码。
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加规则当URI的authority为Notes.AUTHORITY路径为note时返回匹配码URI_NOTE。
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
//添加规则当URI的authority为Notes.AUTHORITY路径为note/后跟一个数字(#代表数字返回匹配码URI_NOTE_ITEM。
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
//和上面两句同理但用于匹配数据相关的URI
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
//用于匹配搜索相关的URI
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
//这两行用于匹配搜索建议相关的URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
@ -65,6 +85,17 @@ public class NotesProvider extends ContentProvider {
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
//功能概述:
//一个 SQL 查询的投影部分,用于定义查询返回的结果集中应该包含哪些列。
//解读:(每行对应)
//返回笔记的 ID。
//笔记的 ID 也被重命名为 SUGGEST_COLUMN_INTENT_EXTRA_DATA这通常用于 Android 的搜索建议中,作为传递给相关 Intent 的额外数据。
//对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n替换为空字符串然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_1
//对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n替换为空字符串然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_2
//返回一个用于搜索建议图标的资源 ID并命名为 SUGGEST_COLUMN_ICON_1。
//返回一个固定的 Intent 动作 ACTION_VIEW并命名为 SUGGEST_COLUMN_INTENT_ACTION。
//返回一个内容类型,并命名为 SUGGEST_COLUMN_INTENT_DATA。
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
@ -72,26 +103,48 @@ public class NotesProvider extends ContentProvider {
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
//功能概述:
//完整的 SQL 查询语句,用于从 TABLE.NOTE 表中检索信息
//解读:
// 使用上面定义的投影来选择数据。
// 并指定从哪个表中选择数据。
//WHERE子句包含三个条件
// ①搜索 SNIPPET 列中包含特定模式的行(? 是一个占位符,实际查询时会用具体的值替换)。
// ②父ID不为回收站的ID排除那些父 ID 为回收站的行。
// ③只选择类型为note标签的行。
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
//重写onCreate方法
//getContext() 方法被调用以获取当前组件的上下文Context以便 NotesDatabaseHelper 能够访问应用程序的资源和其他功能
//mHelper用于存储从 NotesDatabaseHelper.getInstance 方法返回的实例。这样,该实例就可以在整个组件的其他方法中被访问和使用。
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
//功能:查询数据
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
//初始化变量:
//Cursor对象 c用来存储查询结果
//使用 NotesDatabaseHelper 的实例 mHelper来获取一个可读的数据库实例
//定义一个字符串id用来存储从URI中解析出的ID
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
//根据匹配不同的URI来进行不同的查询
switch (mMatcher.match(uri)) {
// URI_NOTE查询整个 NOTE 表。
// URI_NOTE_ITEM查询 NOTE 表中的特定项。ID 从 URI 的路径段中获取,并添加到查询条件中。
// URI_DATA查询整个 DATA 表。
// URI_DATA_ITEM查询 DATA 表中的特定项。ID 的获取和处理方式与 URI_NOTE_ITEM 相同。
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
@ -110,6 +163,12 @@ public class NotesProvider extends ContentProvider {
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
//URI_SEARCH 和 URI_SEARCH_SUGGEST处理搜索查询。
// 代码首先检查是否提供了不应与搜索查询一起使用的参数(如 sortOrder, selection, selectionArgs, 或 projection
// 如果提供了这些参数,则抛出一个 IllegalArgumentException。
// 根据 URI 类型,从 URI 的路径段或查询参数中获取搜索字符串 searchString。
// 如果 searchString 为空或无效,则返回 null表示没有搜索结果。
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
if (sortOrder != null || projection != null) {
@ -129,7 +188,8 @@ public class NotesProvider extends ContentProvider {
if (TextUtils.isEmpty(searchString)) {
return null;
}
//字符串格式化:格式化后的字符串就会是 "%s%"即包含s是任何文本
//然后执行原始SQL查询
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
@ -138,19 +198,29 @@ public class NotesProvider extends ContentProvider {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
//未知URI处理
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
//如果查询结果不为空(即 Cursor 对象 c 不是 null则为其设置一个通知 URI。
//这意味着当与这个 URI 关联的数据发生变化时,任何注册了监听这个 URI 的 ContentObserver 都会被通知。
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
//功能:插入数据
//参数Uri 用来标识要插入数据的表ContentValues对象包含要插入的键值对
@Override
public Uri insert(Uri uri, ContentValues values) {
//获取数据库
//三个长整型变量分别用来存储数据项ID、便签ID 和插入行的ID
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
//对于 URI_NOTE将values插入到 TABLE.NOTE 表中,并返回插入行的 ID。
//对于 URI_DATA首先检查values是否包含 DataColumns.NOTE_ID如果包含则获取其值。如果不包含记录一条日志信息。然后将 values 插入到 TABLE.DATA 表中,并返回插入行的 ID。
//如果 uri 不是已知的 URI 类型,则抛出一个 IllegalArgumentException。
switch (mMatcher.match(uri)) {
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
@ -166,6 +236,9 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 功能:通知变化
// 如果noteId 或 dataId 大于 0即成功插入了数据则使用 ContentResolver 的 notifyChange 方法通知监听这些 URI 的观察者,告知数据已经改变。
// ContentUris.withAppendedId 方法用于在基本 URI 后面追加一个 ID形成完整的 URI。
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
@ -177,17 +250,27 @@ public class NotesProvider extends ContentProvider {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
//返回包含新插入数据项ID 的 Uri。允许调用者知道新插入的数据项的位置
return ContentUris.withAppendedId(uri, insertedId);
}
//功能:删除数据项
//参数uri标识要删除数据的表或数据项。 selection一个可选的 WHERE 子句,用于指定删除条件。 selectionArgs一个可选的字符串数组用于替换 selection 中的占位符
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//count记录被删除的行数。
//id用于存储从 URI 中解析出的数据项 ID。
//db可写的数据库对象用于执行删除操作。
//deleteData一个布尔值用于标记是否删除了 DATA 表中的数据。
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
switch (mMatcher.match(uri)) {
//URI_NOTE: 修改 selection 语句:确保只删除 ID 大于 0 的笔记。然后执行删除操作并返回被删除的行数。
//URI_NOTE_ITEM: 从 URI 中解析出 ID。检查 ID 是否小于等于 0如果是则不执行删除操作否则执行删除操作并返回被删除的行数
//URI_DATA 执行删除操作并返回被删除的行数。设置 deleteData 为 true表示删除了 DATA 表中的数据。
//URI_DATA_ITEM 先从 URI 中解析出 ID然后执行删除操作并返回被删除的行数并设置 deleteData 为 true表示删除了 DATA 表中的数据。
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
@ -218,6 +301,9 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
//如果 count 大于 0说明有数据被删除。
//如果 deleteData 为 true则通知监听 Notes.CONTENT_NOTE_URI 的观察者,数据已改变。
//通知监听传入 uri 的观察者数据已改变。
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -226,14 +312,25 @@ public class NotesProvider extends ContentProvider {
}
return count;
}
//功能:更新数据库的数据
//参数uri标识要更新数据的表或数据项。 values一个包含新值的键值对集合。
// selection一个可选的 WHERE 子句,用于指定更新条件。 selectionArgs一个可选的字符串数组用于替换 selection 中的占位符。
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
//count记录被更新的行数。
//id用于存储从 URI 中解析出的数据项 ID。
//db可写的 SQLite 数据库对象,用于执行更新操作。
//updateData用于标记是否更新了 data 表中的数据。
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
//URI_NOTE调用 increaseNoteVersion 方法用于增加便签版本然后在note表执行更新操作并返回被更新的行数。
//URI_NOTE_ITEM从 URI 中解析出 ID并调用 increaseNoteVersion 方法,传入解析出的 ID最后在note表执行更新操作并返回被更新的行数。
//URI_DATA在data表执行更新操作并返回被更新的行数。设置 updateData 为 true表示更新了 DATA 表中的数据。
//URI_DATA_ITEM从 URI 中解析出 ID。执行更新操作并返回被更新的行数。置 updateData 为 true表示更新了 DATA 表中的数据。
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
@ -257,7 +354,9 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
//如果 count 大于 0说明有数据被更新。
//如果 updateData 为 true则通知监听 Notes.CONTENT_NOTE_URI 的观察者数据已改变。
//通知监听传入 uri 的观察者数据已改变。
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -266,11 +365,11 @@ public class NotesProvider extends ContentProvider {
}
return count;
}
//解析传入的条件语句:一个 SQL WHERE 子句的一部分
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
//更新note表的version列将其值增加 1。
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");

@ -26,29 +26,45 @@ import org.json.JSONObject;
public class MetaData extends Task {
/*
* TAG
* getSimpleName ()
*/
private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;
/*
*
* JSONObjectput ()TasksetNotes ()setName ()
*
*/
public void setMeta(String gid, JSONObject metaInfo) {
//对函数块进行注释
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
// 将这对键值放入metaInfo这个jsonobject对象中
} catch (JSONException e) {
//输出错误信息
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
//功能描述获取相关联的Gid
public String getRelatedGid() {
return mRelatedGid;
}
//功能描述:判断当前数据是否为空,若为空则返回真即值得保存
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/*
* 使json
* TasksetContentByRemoteJSON ()
*
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
@ -58,22 +74,27 @@ public class MetaData extends Task {
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
//输出警告信息
mRelatedGid = null;
}
}
}
//功能描述使用本地json数据对象设置元数据内容一般不会用到若用到则抛出异常
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
//传递非法参数异常
}
//功能描述从元数据内容中获取本地json对象一般不会用到若用到则抛出异常
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
//传递非法参数异常
}
//功能描述:获取同步动作状态,一般不会用到,若用到,则抛出异常
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");

@ -19,34 +19,34 @@ package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
//应该是同步操作的基础数据类型,定义了相关指示同步操作的常量
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
// 本地和云端都无可更新内容(即本地和云端内容一致)
public static final int SYNC_ACTION_ADD_REMOTE = 1;
// 需要在远程云端增加内容
public static final int SYNC_ACTION_ADD_LOCAL = 2;
// 需要在本地增加内容
public static final int SYNC_ACTION_DEL_REMOTE = 3;
// 需要在远程云端删除内容
public static final int SYNC_ACTION_DEL_LOCAL = 4;
// 需要在本地删除内容
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
// 需要将本地内容更新到远程云端
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
// 需要将远程云端内容更新到本地
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
// 同步出现冲突
public static final int SYNC_ACTION_ERROR = 8;
// 同步出现错误
private String mGid;
private String mName;
private long mLastModified;
//记录最后一次修改时间
private boolean mDeleted;
//表征是否被删除
public Node() {
mGid = null;
mName = "";

@ -14,6 +14,8 @@
* limitations under the License.
*/
// Description用于支持小米便签最底层的数据库相关操作和sqlnote的关系上是子集关系即data是note的子集节点
// SqlData其实就是也就是所谓数据中的数据
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
@ -35,16 +37,23 @@ import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {
/*
* TAG
* getSimpleName ()
*/
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
// 为mDataId置初始值-99999
// 来自Notes类中定义的DataColumn中的一些常量
// 集合了interface DataColumns中所有SF常量
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
//以下五个变量作为sql表中5列的编号
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
@ -56,7 +65,7 @@ public class SqlData {
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver;
//判断是否直接用Content生成是为true否则为false
private boolean mIsCreate;
private long mDataId;
@ -70,6 +79,12 @@ public class SqlData {
private String mDataContentData3;
private ContentValues mDiffDataValues;
/*
*
* mContentResolverContentProvider
* mIsCreate
*
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
@ -81,6 +96,12 @@ public class SqlData {
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/*
*
* mContentResolverContentProvider
* mIsCreate
*
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
@ -88,7 +109,10 @@ public class SqlData {
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
/*
*
*
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
@ -96,8 +120,12 @@ public class SqlData {
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/*
*
*
*/
public void setContent(JSONObject js) throws JSONException {
//如果传入的JSONObject对象中有DataColumns.ID这一项则设置否则设为INVALID_ID
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
@ -130,11 +158,14 @@ public class SqlData {
mDataContentData3 = dataContentData3;
}
//功能描述:获取共享的数据内容,并提供异常抛出与处理机制
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
//创建JSONObject对象。并将相关数据放入其中并返回。
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
@ -143,7 +174,7 @@ public class SqlData {
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
//功能描述commit函数用于把当前造作所做的修改保存到数据库
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
@ -182,7 +213,7 @@ public class SqlData {
mDiffDataValues.clear();
mIsCreate = false;
}
//功能描述获取当前id
public long getId() {
return mDataId;
}

@ -13,7 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Description便sqldatanotedata
* SqlDataSqlNote
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
@ -39,10 +42,14 @@ import java.util.ArrayList;
public class SqlNote {
/*
* TAG
* getSimpleName ()
*/
private static final String TAG = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999;
// 集合了interface NoteColumns中所有SF常量17个
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
@ -51,7 +58,7 @@ public class SqlNote {
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
//以下设置17个列的编号
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
@ -85,7 +92,7 @@ public class SqlNote {
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
//一下定义了17个内部的变量其中12个可以由content中获得5个需要初始化为0或者new
private Context mContext;
private ContentResolver mContentResolver;
@ -121,7 +128,11 @@ public class SqlNote {
private ContentValues mDiffNoteValues;
private ArrayList<SqlData> mDataList;
/*
*
* mIsCreate
*/
//构造函数只有context对所有的变量进行初始化
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -129,9 +140,9 @@ public class SqlNote {
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();
mCreatedDate = System.currentTimeMillis();//调用系统函数获得创建时间
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();
mModifiedDate = System.currentTimeMillis();//最后一次修改时间初始化为创建时间
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
@ -142,7 +153,11 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
/*
*
* mIsCreate
*/
//构造函数有context和一个数据库的cursor多数变量通过cursor指向的一条记录直接进行初始化
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -153,7 +168,10 @@ public class SqlNote {
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/*
*
* mIsCreate
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -165,17 +183,18 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
}
//功能描述通过id从光标处加载数据
private void loadFromCursor(long id) {
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
}, null);//通过id获得对应的ContentResolver中的cursor
if (c != null) {
c.moveToNext();
loadFromCursor(c);
loadFromCursor(c);//然后加载数据进行初始化,这样函数
//SqlNote(Context context, long id)与SqlNote(Context context, long id)的实现方式基本相同
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
@ -184,8 +203,9 @@ public class SqlNote {
c.close();
}
}
//功能描述:通过游标从光标处加载数据
private void loadFromCursor(Cursor c) {
//直接从一条记录中的获得以下变量的初始值
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
@ -199,7 +219,7 @@ public class SqlNote {
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
//功能描述通过content机制获取共享数据并加载到数据库当前游标处
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
@ -225,7 +245,7 @@ public class SqlNote {
c.close();
}
}
//功能描述设置通过content机制用于共享的数据信息
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
@ -358,7 +378,7 @@ public class SqlNote {
}
return true;
}
//获取content机制提供的数据并加载到note中
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
@ -406,40 +426,40 @@ public class SqlNote {
}
return null;
}
//功能描述给当前id设置父id
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
//功能描述给当前id设置Gtaskid
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
//功能描述给当前id设置同步id
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
//初始化本地修改,即撤销所有当前修改
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
//获得当前id
public long getId() {
return mId;
}
//获得当前id的父id
public long getParentId() {
return mParentId;
}
//获取小片段即用于显示的部分便签内容
public String getSnippet() {
return mSnippet;
}
//判断是否为便签类型
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
//commit函数用于把当前造作所做的修改保存到数据库
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
@ -458,7 +478,7 @@ public class SqlNote {
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
for (SqlData sqlData : mDataList) {//直接使用sqldata中的实现
sqlData.commit(mId, false, -1);
}
}
@ -470,7 +490,7 @@ public class SqlNote {
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
if (!validateVersion) {//构造字符串
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)

@ -35,22 +35,22 @@ import org.json.JSONObject;
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
private boolean mCompleted;
private boolean mCompleted;//是否完成
private String mNotes;
private JSONObject mMetaInfo;
private JSONObject mMetaInfo;//将在实例中存储数据的类型
private Task mPriorSibling;
private Task mPriorSibling;//对应的优先兄弟Task的指针待完善
private TaskList mParent;
private TaskList mParent;//所在的任务列表的指针
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mPriorSibling = null;//TaskList中当前Task前面的Task的指针
mParent = null;//当前Task所在的TaskList
mMetaInfo = null;
}

@ -31,18 +31,18 @@ import java.util.ArrayList;
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();
private static final String TAG = TaskList.class.getSimpleName();//tag标记
private int mIndex;
private int mIndex;//当前TaskList的指针
private ArrayList<Task> mChildren;
private ArrayList<Task> mChildren;//类中主要的保存数据的单元用来实现一个以Task为元素的ArrayList
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
//生成并返回一个包含了一定数据的JSONObject实体
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
@ -58,7 +58,7 @@ public class TaskList extends Node {
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
JSONObject entity = new JSONObject();
JSONObject entity = new JSONObject();//entity实体
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
@ -73,7 +73,7 @@ public class TaskList extends Node {
return js;
}
//生成并返回一个包含了一定数据的JSONObject实体
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
@ -215,11 +215,15 @@ public class TaskList extends Node {
return SYNC_ACTION_ERROR;
}
//获得TaskList的大小即mChildren的大小
public int getChildTaskCount() {
return mChildren.size();
}
/*
* @param task
* @return
*
*/
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
@ -229,11 +233,18 @@ public class TaskList extends Node {
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
//注意每一次ArrayList的变化都要紧跟相关Task中PriorSibling的更改
//,接下来几个函数都有相关操作
}
}
return ret;
}
/*
* @param task
* @param index
* @return
*
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@ -259,7 +270,11 @@ public class TaskList extends Node {
return true;
}
/*
* @param task
* @return
* TaskListTask
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
@ -280,7 +295,12 @@ public class TaskList extends Node {
}
return ret;
}
/*
* @param task
* @param index
* @return
* TaskListTaskindex
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
@ -297,8 +317,13 @@ public class TaskList extends Node {
if (pos == index)
return true;
return (removeChildTask(task) && addChildTask(task, index));
//利用已实现好的功能完成当下功能;
}
/*
* @param gid
* @return
* gidTask
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
@ -308,11 +333,11 @@ public class TaskList extends Node {
}
return null;
}
//返回指定Task的index
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
//返回指定index的Task
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
@ -320,7 +345,7 @@ public class TaskList extends Node {
}
return mChildren.get(index);
}
//返回指定gid的Task
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))

@ -64,21 +64,22 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
}
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}

@ -15,7 +15,10 @@
*/
package net.micode.notes.gtask.remote;
/*
* GTASKGTASK
* 使accountManager JSONObject HttpParams authToken Gid
*/
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
@ -100,7 +103,10 @@ public class GTaskClient {
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
}/*
* 使 getInstance()
* mInstance
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
@ -108,7 +114,11 @@ public class GTaskClient {
}
return mInstance;
}
/*Activity
*
* 使URL使URL
* truefalse
*/
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
@ -117,41 +127,44 @@ public class GTaskClient {
mLoggedin = false;
}
// need to re-login after account switch
// need to re-login after account switch 重新登录操作
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
.getSyncAccountName(activity))) {
mLoggedin = false;
}
//如果没超过时间,则不需要重新登录
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false);
mLastLoginTime = System.currentTimeMillis();//更新最后登录时间,改为系统当前的时间
String authToken = loginGoogleAccount(activity, false);//判断是否登录到谷歌账户
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// login with custom domain if necessary
// login with custom domain if necessary
//尝试使用用户自己的域名登录
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
mGetUrl = url.toString() + "ig";//设置用户对应的getUrl
mPostUrl = url.toString() + "r/ig";//设置用户对应的postUrl
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// try to login with google official url
//如果用户账户无法登录则使用谷歌官方的URI进行登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
@ -165,9 +178,9 @@ public class GTaskClient {
}
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
String authToken; //令牌,是登录操作保证安全性的一个方法
AccountManager accountManager = AccountManager.get(activity);//AccountManager这个类给用户提供了集中注册账号的接口
Account[] accounts = accountManager.getAccountsByType("com.google");//获取全部以com.google结尾的account
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
@ -176,6 +189,7 @@ public class GTaskClient {
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
//遍历获得的accounts信息寻找已经记录过的账户信息
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
@ -190,11 +204,13 @@ public class GTaskClient {
}
// get the token now
//获取选中账号的令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
//如果是invalidateToken那么需要调用invalidateAuthToken(String, String)方法废除这个无效token
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
@ -206,11 +222,12 @@ public class GTaskClient {
return authToken;
}
//尝试登陆Gtask这只是一个预先判断令牌是否是有效以及是否能登上GTask的方法,而不是具体实现登陆的方法
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// maybe the auth token is out of authTokedate, now let's invalidate the
// token and try again
//删除过一个无效的authToken申请一个新的后再次尝试登陆
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
@ -225,25 +242,28 @@ public class GTaskClient {
return true;
}
//实现登录GTask的具体操作
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
int timeoutSocket = 15000;//socket是一种通信连接实现数据的交换的端口
HttpParams httpParameters = new BasicHttpParams();//实例化一个新的HTTP参数类
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);//设置连接超时时间
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);//设置设置端口超时时间
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
BasicCookieStore localBasicCookieStore = new BasicCookieStore();//设置本地cookie
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
String loginUrl = mGetUrl + "?auth=" + authToken;//设置登录的url
HttpGet httpGet = new HttpGet(loginUrl);//通过登录的uri实例化网页上资源的查找
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
//获取CookieStore里存放的cookie,看如果存有“GTL(不知道什么意思)”则说明有验证成功的有效的cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
@ -256,6 +276,7 @@ public class GTaskClient {
}
// get the client version
//获取client的内容具体操作是在返回的Content中截取从_setup(开始到)}</script>中间的字符串内容也就是gtask_url的内容
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
@ -283,14 +304,20 @@ public class GTaskClient {
private int getActionId() {
return mActionId++;
}
/*
* 使HttpPost
* httpPost
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
/*URL
* 使getContentEncoding()
*
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
@ -322,21 +349,27 @@ public class GTaskClient {
input.close();
}
}
/*JSON
* jsonjs
* UrlEncodedFormEntity entityhttpPost.setEntity(entity)jshttpPost
* 使getResponseContent
* json
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
//实例化一个httpPost的对象用来向服务器传输数据在这里就是发送请求而请求的内容在js里
HttpPost httpPost = createHttpPost();
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// execute the post
//执行这个请求
// execute the post
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
@ -359,7 +392,12 @@ public class GTaskClient {
throw new ActionFailureException("error occurs when posting request");
}
}
/*
* .gtask.data.TaskTask
* jsonTask,jsPost
* postRequest
* 使task.setGidtasknew_ID
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
@ -385,7 +423,9 @@ public class GTaskClient {
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
/*
* createTasktasklistgid
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
@ -411,7 +451,11 @@ public class GTaskClient {
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
/*
*
* 使JSONObject使jsPost.putPutUpdateArrayClientVersion
* 使postRequestjspost,
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
@ -432,7 +476,10 @@ public class GTaskClient {
}
}
}
/*
*
* commitUpdate()
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
@ -446,7 +493,12 @@ public class GTaskClient {
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
/*
* task,tasktask
* getGidtaskgid
* JSONObject.put(String name, Object value)task
* postRequest
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
@ -463,6 +515,7 @@ public class GTaskClient {
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
//设置优先级ID只有当移动是发生在文件中
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
@ -472,6 +525,7 @@ public class GTaskClient {
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
//最后将ACTION_LIST加入到jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
@ -483,7 +537,11 @@ public class GTaskClient {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}/*
*
* JSON
* 使postRequest
*/
}
public void deleteNode(Node node) throws NetworkFailureException {
@ -494,7 +552,7 @@ public class GTaskClient {
// action_list
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
actionList.put(node.getUpdateAction(getActionId()));//这里会获取到删除操作的ID加入到actionLiast中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
@ -508,7 +566,11 @@ public class GTaskClient {
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
/*
*
* GetURI使getResponseContent
* "_setup(")}</script>GTASK_JSON_LISTS
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@ -521,6 +583,7 @@ public class GTaskClient {
response = mHttpClient.execute(httpGet);
// get the task list
//筛选工作把筛选出的字符串放入jsString
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
@ -531,6 +594,7 @@ public class GTaskClient {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
//获取GTASK_JSON_LISTS
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
@ -546,7 +610,9 @@ public class GTaskClient {
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
/*
* TASKListgid,
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
@ -578,6 +644,7 @@ public class GTaskClient {
public Account getSyncAccount() {
return mAccount;
}
//重置更新的内容
public void resetUpdateArray() {
mUpdateArray = null;

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save