Compare commits

..

38 Commits
main ... master

Author SHA1 Message Date
m38nqrb4x f4b52e6c68 Merge pull request '解决按键遮挡和状态栏遮挡问题' (#11) from fangzheng_branch into master
3 hours ago
fangzheng 37c4b345c8 解决按键遮挡和状态栏遮挡问题
3 hours ago
m38nqrb4x e81df84a6d Merge pull request '将原先只有六个包的src文件夹修改为小米便签项目代码结构' (#10) from fangzheng_branch into master
3 hours ago
fangzheng e987d86405 原先的src文件夹只有小米便签的六个包,现在调整为小米便签项目的代码结构,包含java文件夹和res文件夹
3 hours ago
fangzheng c0a2e8fe11 更新
3 hours ago
fangzheng 9e73668904 推送完整代码
3 hours ago
fangzheng 642fba3a05 test
3 hours ago
fangzheng 061de96ae5 测试
3 hours ago
m38nqrb4x 979f348454 修改后的质量分析报告
1 week ago
pyz3hlwn2 bbc2256971 Delete 'doc/~$便签泛读、标注和维护报告文档.docx'
4 weeks ago
wyxfzgg 9ba2f81f45 小米便签泛读、标注和维护报告文档
4 weeks ago
wyxfzgg 7968a3e325 Merge branch 'master' of https://bdgit.educoder.net/pyz3hlwn2/git.text
4 weeks ago
wyxfzgg 50e92f9ec1 小米便签泛读、标注和维护报告文档
4 weeks ago
pyz3hlwn2 4ea16d5cfb Delete 'doc/小米便签泛读报告.docx'
4 weeks ago
pyz3hlwn2 8837108877 Delete 'doc/小米便签泛读、标注和维护报告文档.docx'
4 weeks ago
pyz3hlwn2 5c63e7c75b Merge pull request '对tool包、ui包、widget包中的每个类进行了注释。' (#7) from wangyixin_branch into master
4 weeks ago
wyxfzgg 4cfb6320c7 对tool包、ui包、widgt包中的每个类进行了注释。
4 weeks ago
m38nqrb4x 8335dc6041 Merge pull request '代码的修改' (#6) from fangzheng_branch into master
4 weeks ago
fangzheng cf23605247 Merge branch 'master' of https://bdgit.educoder.net/pyz3hlwn2/git.text into fangzheng_branch
4 weeks ago
fangzheng 76a9960fa6 对data包、gtask包、model包中的每个类进行了注释。
4 weeks ago
fangzheng a2a8896c21 对注释的代码类名及其代码行数同步到文档
4 weeks ago
fangzheng 6b251bfc19 对注释的代码类名及其代码行数同步到文档
4 weeks ago
pyz3hlwn2 1b263e5964 Merge pull request '小米便签泛读、标注和维护报告文档' (#3) from wangyixin_branch into master
1 month ago
wyxfzgg 0e5fba5e19 小米便签泛读、标注和维护报告文档
1 month ago
pyz3hlwn2 a1cb8f78b6 Merge pull request '小米便签泛读、标注和维护报告文档' (#2) from wangyixin_branch into master
1 month ago
wyxfzgg 8dc84310b6 小米便签泛读、标注和维护报告文档
1 month ago
pyz3hlwn2 782a8ca101 Merge pull request '泛读报告' (#1) from wangyixin_branch into master
2 months ago
wyxfzgg 8dd58ea131 泛读报告完整版
2 months ago
fangzheng 0a845ad23a 补充了类的作用以及类之间关系
2 months ago
fangzheng 6c3a2a6954 修改了标题格式
2 months ago
fangzheng aaa207f5b2 调整了标题格式
2 months ago
fangzheng 335557587b 1234
2 months ago
fangzheng e8670c045a Merge branch 'wangyixin_branch' of https://bdgit.educoder.net/pyz3hlwn2/git.text into fangzheng_branch
2 months ago
fangzheng cf821a0336 123
2 months ago
fangzheng 71881e0010 12
2 months ago
fangzheng 24420dcf76 1
2 months ago
wyxfzgg 5810729213 小米便签泛读报告
2 months ago
wyxfzgg 8bf9d432a2 1
2 months ago

@ -0,0 +1,69 @@
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;
/**
*
* <p>
*
* <ul>
* <li>Activity</li>
* <li>EdgeToEdge</li>
* <li>Insets</li>
* </ul>
* <p>
*
* <ul>
* <li></li>
* <li>Android 11+</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>{@link androidx.appcompat.app.AppCompatActivity}使 AppCompat </li>
* </ul>
*
* @author Micode Team
* @since 1.0.0
*/
public class MainActivity extends AppCompatActivity {
/**
* Activity
* <p>
*
* <ul>
* <li>onCreate</li>
* <li></li>
* <li>Activityactivity_main</li>
* <li>InsetsUI</li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link EdgeToEdge}</li>
* <li>{@link ViewCompat}Insets</li>
* <li>UI</li>
* </ul>
*
* @param savedInstanceState Activity
*/
@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;
});
}
}

@ -0,0 +1,135 @@
/*
* 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.
*/
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
/**
* <p></p>
* <p></p>
* <ul>
* <li></li>
* <li></li>
* <li>使AndroidAPI</li>
* </ul>
* <p></p>
* <ul>
* <li>便</li>
* <li></li>
* <li>便</li>
* </ul>
* <p></p>
* <ul>
* <li>使{@link Context}访Content Provider</li>
* <li>使{@link HashMap}</li>
* <li>便使</li>
* </ul>
*/
public class Contact {
/**
* <p></p>
* <p></p>
* <p>使HashMap</p>
*/
private static HashMap<String, String> sContactCache;
/**
* <p></p>
* <p>Contact</p>
*/
private static final String TAG = "Contact";
/**
* <p></p>
* <p>SQL使PHONE_NUMBERS_EQUAL</p>
* <p>'+'</p>
*/
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 = '+')";
/**
* <p></p>
* <p></p>
*
* @param context 访ContentResolver
* @param phoneNumber
*
* @return null
*
* <p></p>
* <ol>
* <li></li>
* <li></li>
* <li></li>
* <li>ContentResolverContent Provider</li>
* <li></li>
* <li>Cursor</li>
* <li></li>
* </ol>
*
* <p></p>
* <ul>
* <li>IndexOutOfBoundsExceptionnull</li>
* <li>null</li>
* </ul>
*/
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
cursor.close();
}
} else {
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -0,0 +1,578 @@
/*
* 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.
*/
package net.micode.notes.data;
import android.net.Uri;
/**
* 便URI
* <p>
*
* <ul>
* <li>便</li>
* <li>ID</li>
* <li>Intent</li>
* <li>Widget</li>
* <li>Content ProviderURI</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li></li>
* <li>Content Provider</li>
* <li></li>
* <li>便便便</li>
* <li>便</li>
* <li>Widget</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link NotesProvider}使Content Provider</li>
* <li>{@link NotesDatabaseHelper}使</li>
* <li>UI使Intent</li>
* <li>Widget使Widget</li>
* </ul>
*
*/
public class Notes {
/**
* Content ProviderAuthority便Content Provider
*/
public static final String AUTHORITY = "micode_notes";
/**
*
*/
public static final String TAG = "Notes";
/**
* 便便
*/
public static final int TYPE_NOTE = 0;
/**
* 便
*/
public static final int TYPE_FOLDER = 1;
/**
* 便使
*/
public static final int TYPE_SYSTEM = 2;
/**
* ID便
*/
public static final int ID_ROOT_FOLDER = 0;
/**
* ID便
*/
public static final int ID_TEMPARAY_FOLDER = -1;
/**
* ID便
*/
public static final int ID_CALL_RECORD_FOLDER = -2;
/**
* ID便
*/
public static final int ID_TRASH_FOLER = -3;
/**
* Intent便
*/
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
/**
* IntentID便
*/
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
/**
* IntentWidget IDWidget
*/
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
/**
* IntentWidgetWidget2x4x
*/
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
/**
* IntentID便ID
*/
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
/**
* Intent
*/
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
/**
* WidgetWidget
*/
public static final int TYPE_WIDGET_INVALIDE = -1;
/**
* Widget2xWidget2x2
*/
public static final int TYPE_WIDGET_2X = 0;
/**
* Widget4xWidget4x4
*/
public static final int TYPE_WIDGET_4X = 1;
/**
* 便MIME
* <p>
*
* <ul>
* <li>访</li>
* <li>便MIME</li>
* </ul>
*/
public static class DataConstants {
/**
* 便MIME
*/
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
/**
* 便MIME
*/
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* Content Provider URI便
* <p>
* URIContent Providernote便
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Content Provider URI便
* <p>
* URIContent Providerdata便
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
/**
* 便
* <p>
*
* <ul>
* <li>便</li>
* <li>Content Provider</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li>使便</li>
* <li>便</li>
* <li>ID</li>
* <li>便</li>
* <li>便Widget</li>
* <li>便</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link NotesProvider}使</li>
* <li>{@link NotesDatabaseHelper}使</li>
* <li>UI使</li>
* </ul>
*/
public interface NoteColumns {
/**
*
* <p>: INTEGER (long)</p>
* <p>: </p>
*/
public static final String ID = "_id";
/**
* 便ID
* <p>: INTEGER (long)</p>
* <p>: ID</p>
* <p>: 便</p>
*/
public static final String PARENT_ID = "parent_id";
/**
* 便
* <p>: INTEGER (long)</p>
* <p>: Unix</p>
* <p>: </p>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <p>: INTEGER (long)</p>
* <p>: Unix</p>
* <p>: </p>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <p>: INTEGER (long)</p>
* <p>: Unix0</p>
* <p>: 便</p>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* 便
* <p>: TEXT</p>
* <p>: </p>
* <p>: 便</p>
*/
public static final String SNIPPET = "snippet";
/**
* 便Widget ID
* <p>: INTEGER (long)</p>
* <p>: -1Widget</p>
* <p>: 便Widget</p>
*/
public static final String WIDGET_ID = "widget_id";
/**
* 便Widget
* <p>: INTEGER (long)</p>
* <p>: {@link Notes#TYPE_WIDGET_INVALIDE}{@link Notes#TYPE_WIDGET_2X}{@link Notes#TYPE_WIDGET_4X}</p>
* <p>: Widget</p>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* 便ID
* <p>: INTEGER (long)</p>
* <p>: ID</p>
* <p>: 便</p>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
*
* <p>: INTEGER</p>
* <p>: 01</p>
* <p>: 便</p>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* 便
* <p>: INTEGER (long)</p>
* <p>: </p>
* <p>: 便</p>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* 便
* <p>: INTEGER</p>
* <p>: {@link Notes#TYPE_NOTE}{@link Notes#TYPE_FOLDER}{@link Notes#TYPE_SYSTEM}</p>
* <p>: 便</p>
*/
public static final String TYPE = "type";
/**
* ID
* <p>: INTEGER (long)</p>
* <p>: </p>
* <p>: 便</p>
*/
public static final String SYNC_ID = "sync_id";
/**
*
* <p>: INTEGER</p>
* <p>: 01</p>
* <p>: </p>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <p>: INTEGER</p>
* <p>: ID</p>
* <p>: 便便</p>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* Google Task ID
* <p>: TEXT</p>
* <p>: Google Task使</p>
* <p>: 便Google Task</p>
*/
public static final String GTASK_ID = "gtask_id";
/**
*
* <p>: INTEGER (long)</p>
* <p>: </p>
* <p>: </p>
*/
public static final String VERSION = "version";
}
/**
* 便
* <p>
*
* <ul>
* <li>便</li>
* <li>便</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li>使便</li>
* <li>便便便</li>
* <li>便便</li>
* <li>ID</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link NotesProvider}使</li>
* <li>{@link NotesDatabaseHelper}使</li>
* <li>{@link TextNote}{@link CallNote}便</li>
* </ul>
*/
public interface DataColumns {
/**
*
* <p>: INTEGER (long)</p>
* <p>: </p>
*/
public static final String ID = "_id";
/**
* MIME
* <p>: TEXT</p>
* <p>: "text_note""call_note"</p>
* <p>: 便</p>
*/
public static final String MIME_TYPE = "mime_type";
/**
* 便ID
* <p>: INTEGER (long)</p>
* <p>: noteID</p>
* <p>: 便</p>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <p>: INTEGER (long)</p>
* <p>: Unix</p>
* <p>: </p>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <p>: INTEGER (long)</p>
* <p>: Unix</p>
* <p>: </p>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <p>: TEXT</p>
* <p>: 便</p>
* <p>: 便</p>
*/
public static final String CONTENT = "content";
/**
* 1{@link #MIME_TYPE}
* <p>: INTEGER</p>
* <p>: MIME</p>
* <p>: 便</p>
* <p>: 便/ checklist</p>
*/
public static final String DATA1 = "data1";
/**
* 2{@link #MIME_TYPE}
* <p>: INTEGER</p>
* <p>: MIME</p>
* <p>: 便</p>
*/
public static final String DATA2 = "data2";
/**
* 3{@link #MIME_TYPE}
* <p>: TEXT</p>
* <p>: MIME</p>
* <p>: 便</p>
* <p>: 便</p>
*/
public static final String DATA3 = "data3";
/**
* 4{@link #MIME_TYPE}
* <p>: TEXT</p>
* <p>: MIME</p>
* <p>: 便</p>
*/
public static final String DATA4 = "data4";
/**
* 5{@link #MIME_TYPE}
* <p>: TEXT</p>
* <p>: MIME</p>
* <p>: 便</p>
*/
public static final String DATA5 = "data5";
}
/**
* 便{@link DataColumns}
* <p>
*
* <ul>
* <li>便</li>
* <li>便MIME</li>
* <li>便Content Provider URI</li>
* <li>Checklist</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link DataColumns}便</li>
* <li>使DATA1便/Checklist</li>
* <li>Content Provider便访</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link DataColumns}</li>
* <li>{@link NotesProvider}使便</li>
* <li>UI使便</li>
* </ul>
*/
public static final class TextNote implements DataColumns {
/**
* 便DATA1
* <p>: INTEGER</p>
* <p>: 01Checklist</p>
* <p>: 便</p>
*/
public static final String MODE = DATA1;
/**
* Checklist便Checklist
*/
public static final int MODE_CHECK_LIST = 1;
/**
* 便MIMEContent Provider便使
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
/**
* 便MIMEContent Provider便使
*/
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");
}
/**
* 便{@link DataColumns}
* <p>
*
* <ul>
* <li>便</li>
* <li>便MIME</li>
* <li>便Content Provider URI</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>{@link DataColumns}便</li>
* <li>使DATA1</li>
* <li>使DATA3</li>
* <li>Content Provider便访</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>{@link DataColumns}</li>
* <li>{@link NotesProvider}使便</li>
* <li>使便</li>
* <li>UI使便</li>
* </ul>
*/
public static final class CallNote implements DataColumns {
/**
* DATA1
* <p>: INTEGER (long)</p>
* <p>: Unix</p>
* <p>: </p>
*/
public static final String CALL_DATE = DATA1;
/**
* DATA3
* <p>: TEXT</p>
* <p>: </p>
* <p>: 便</p>
*/
public static final String PHONE_NUMBER = DATA3;
/**
* 便MIMEContent Provider便使
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
/**
* 便MIMEContent Provider便使
*/
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
/**
* 便Content Provider URI便
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,546 @@
/*
* 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.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 便
* <p>
*
* <ul>
* <li> "note.db" ({@link #onCreate})</li>
* <li> ({@link #onUpgrade})</li>
* <li> {@link TABLE#NOTE}</li>
* <li> SQL Triggers </li>
* <li></li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li></li>
* <li>使SQL Triggers</li>
* <li></li>
* <li>便</li>
* <li>便</li>
* </ul>
* <p>
*
* <ul>
* <li> {@link SQLiteOpenHelper}Android</li>
* </ul>
* <p>
*
* <ul>
* <li> {@link NotesProvider} 使Content Provider</li>
* <li>使 {@link Notes} </li>
* <li>访</li>
* </ul>
* <p>
*
* <ul>
* <li>使SQL Triggers便</li>
* <li></li>
* <li>便</li>
* <li></li>
* </ul>
*
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
*
* <p>note.db</p>
*/
private static final String DB_NAME = "note.db";
/**
*
* <p>
* <strong></strong>
* addColumn Trigger +1
* {@link #onUpgrade}
* </p>
* <p>
*
* <ul>
* <li>1</li>
* <li>2</li>
* <li>3Google Task ID</li>
* <li>4</li>
* </ul>
* </p>
*/
private static final int DB_VERSION = 4;
/**
*
* <p>
*
* <ul>
* <li></li>
* <li>访便</li>
* <li>{@link NotesProvider}使</li>
* </ul>
*/
public interface TABLE {
/**
* 便
* <p>IDPARENT_IDALERTED_DATEBG_COLOR_IDCREATED_DATE</p>
*/
public static final String NOTE = "note";
/**
* 便
* <p>IDMIME_TYPENOTE_IDCONTENTDATA1-DATA5</p>
*/
public static final String DATA = "data";
}
/**
*
*/
private static final String TAG = "NotesDatabaseHelper";
/**
*
* <p>
*
* <ul>
* <li></li>
* <li>线</li>
* <li>{@link #getInstance(Context)}</li>
* </ul>
*/
private static NotesDatabaseHelper mInstance;
/**
* SQL
* IDID
*/
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID默认为0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期时间戳
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期,默认为当前时间
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期,默认为当前时间
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量(用于文件夹)
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 内容摘要
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(笔记/文件夹/系统文件夹等)
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父文件夹ID
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google Task ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号
")";
/**
* SQL
* MIME
*/
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型标识数据类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容
DataColumns.DATA1 + " INTEGER," + // 扩展字段1整数
DataColumns.DATA2 + " INTEGER," + // 扩展字段2整数
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段3文本
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段4文本
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 扩展字段5文本
")";
/**
* note_id
*/
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* SQL
* <p>
* <strong></strong><br>
* 1. {@link TABLE#NOTE} {@link NoteColumns#PARENT_ID} <br>
* 2. ID (new.parent_id)<br>
* 3. 'note_count' +1
* </p>
*/
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 +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* SQL
* 0
*/
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 +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
* SQL
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* SQL
* 0
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* SQL NOTE
* MIME_TYPEDataConstants.NOTE
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* SQL NOTE
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* SQL NOTE
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* SQL
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* SQL
* delete_data_on_delete
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* SQL
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
*
* @param context Android
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
*
* @param db SQLite
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
/**
*
*
* @param db SQLite
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除所有旧触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
*
*
* @param db SQLite
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
*
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
*
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
*
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
*
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
*
* @param db SQLite
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建索引提高查询性能
Log.d(TAG, "data table has been created");
}
/**
*
*
* @param db SQLite
*/
private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
/**
* 线
* @param context Android
* @return NotesDatabaseHelper
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
/**
*
*
* @param db SQLite
*/
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
/**
*
*
* @param db SQLite
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重建触发器
boolean skipV2 = false; // 是否跳过V2升级
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // V1到V2的升级包含了V2到V3的内容
oldVersion++;
}
// 从版本2升级到版本3如果未跳过
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true; // V3升级需要重建触发器
oldVersion++;
}
// 从版本3升级到版本4
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果需要,重建所有触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 版本号检查,确保升级完成
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
/**
* 2
* @param db SQLite
*/
private void upgradeToV2(SQLiteDatabase db) {
// 删除旧表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
// 创建新表
createNoteTable(db);
createDataTable(db);
}
/**
* 3Google Task ID
* @param db SQLite
*/
private void upgradeToV3(SQLiteDatabase db) {
// 删除不再使用的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// 添加Google Task ID字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// 添加回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 4
* @param db SQLite
*/
private void upgradeToV4(SQLiteDatabase db) {
// 添加版本号字段
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -0,0 +1,661 @@
/*
* 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.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
* 便Content Provider便CRUD
* <p>
*
* <ul>
* <li>便Content Provider访</li>
* <li>便便</li>
* <li>便</li>
* <li>便</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>Android Content Provider访</li>
* <li>使UriMatcherURI</li>
* <li></li>
* <li>访</li>
* <li>Android</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link android.content.ContentProvider}Content Provider</li>
* </ul>
* <p>
*
* <ul>
* <li>{@link NotesDatabaseHelper}</li>
* <li>使{@link Notes}URI访</li>
* <li>{@link SearchManager}Android</li>
* <li>UIWidget使访</li>
* </ul>
* <p>
* URI
* <ul>
* <li>content://micode_notes/note - 所有便签和文件夹</li>
* <li>content://micode_notes/note/# - 单个便签或文件夹</li>
* <li>content://micode_notes/data - 所有便签详细数据</li>
* <li>content://micode_notes/data/# - 单个便签详细数据</li>
* <li>content://micode_notes/search - 搜索便签</li>
* <li>content://micode_notes/search_suggest_query - 搜索建议</li>
* </ul>
*
*/
public class NotesProvider extends ContentProvider {
/**
* UriMatcherURI
* <p>
* URI
* <p>
* URI
*/
private static final UriMatcher mMatcher;
/**
* SQLiteDatabase
* <p>
*
* <p>
* onCreate
*/
private NotesDatabaseHelper mHelper;
/**
*
*/
private static final String TAG = "NotesProvider";
/**
* URI便
*/
private static final int URI_NOTE = 1;
/**
* URI便
*/
private static final int URI_NOTE_ITEM = 2;
/**
* URI便
*/
private static final int URI_DATA = 3;
/**
* URI便
*/
private static final int URI_DATA_ITEM = 4;
/**
* URI便
*/
private static final int URI_SEARCH = 5;
/**
* URI
*/
private static final int URI_SEARCH_SUGGEST = 6;
/**
* UriMatcherURI
* <p>
* URI
* <ul>
* <li>note - 便</li>
* <li>note/# - 便</li>
* <li>data - 便</li>
* <li>data/# - 便</li>
* <li>search - 便</li>
* <li>search_suggest_query - </li>
* <li>search_suggest_query/* - </li>
* </ul>
*/
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/**
* 便
* <p>
*
* <ul>
* <li>便ID</li>
* <li>便ID</li>
* <li>1便</li>
* <li>2便</li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>使SQLiteTRIMREPLACE便x'0A'</li>
* <li>使</li>
* </ul>
*/
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 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ 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
* <p>
*
* <ul>
* <li>便LIKE ?</li>
* <li>便PARENT_ID <> TRASH_FOLDER</li>
* <li>便便TYPE = TYPE_NOTE</li>
* </ul>
* <p>
* URI_SEARCHURI_SEARCH_SUGGEST
*/
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;
/**
* Content Provider
* <p>
*
* <ul>
* <li>NotesDatabaseHelper</li>
* <li></li>
* </ul>
* <p>
*
* <ul>
* <li>{@link NotesDatabaseHelper#getInstance(android.content.Context)}</li>
* </ul>
*
* @return true
* @see android.content.ContentProvider#onCreate()
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/**
* URICursor
* <p>
*
* <ul>
* <li>URI</li>
* <li></li>
* <li>Cursor</li>
* <li>CursorURI便</li>
* </ul>
* <p>
* URI
* <ul>
* <li>{@link #URI_NOTE} - 便</li>
* <li>{@link #URI_NOTE_ITEM} - 便</li>
* <li>{@link #URI_DATA} - 便</li>
* <li>{@link #URI_DATA_ITEM} - 便</li>
* <li>{@link #URI_SEARCH} - 便</li>
* <li>{@link #URI_SEARCH_SUGGEST} - </li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link NotesDatabaseHelper#getReadableDatabase()}</li>
* <li>使{@link SQLiteDatabase}</li>
* <li>使{@link SearchManager}</li>
* </ul>
* <p>
*
* <ul>
* <li>sortOrderprojection</li>
* <li>便</li>
* <li>便</li>
* </ul>
*
* @param uri URI
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return Cursor Cursornull
* @throws IllegalArgumentException URI
* @see android.content.ContentProvider#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String)
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
searchString = uri.getQueryParameter("pattern");
}
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
/**
* URIURI
* <p>
*
* <ul>
* <li>URI</li>
* <li></li>
* <li>ID</li>
* <li>URI</li>
* <li>IDURI</li>
* </ul>
* <p>
* URI
* <ul>
* <li>{@link #URI_NOTE} - 便</li>
* <li>{@link #URI_DATA} - 便</li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link NotesDatabaseHelper#getWritableDatabase()}</li>
* <li>使{@link SQLiteDatabase}</li>
* <li>{@link android.content.ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver)}</li>
* </ul>
* <p>
*
* <ul>
* <li>noteIddataId</li>
* <li>URI</li>
* </ul>
*
* @param uri URI
* @param values
* @return Uri IDURI
* @throws IllegalArgumentException URI
* @see android.content.ContentProvider#insert(android.net.Uri, android.content.ContentValues)
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId);
}
/**
* URI
* <p>
*
* <ul>
* <li>URI</li>
* <li></li>
* <li></li>
* <li>URI</li>
* </ul>
* <p>
* URI
* <ul>
* <li>{@link #URI_NOTE} - 便</li>
* <li>{@link #URI_NOTE_ITEM} - 便</li>
* <li>{@link #URI_DATA} - 便</li>
* <li>{@link #URI_DATA_ITEM} - 便</li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link NotesDatabaseHelper#getWritableDatabase()}</li>
* <li>使{@link SQLiteDatabase}</li>
* <li>{@link android.content.ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver)}</li>
* </ul>
* <p>
*
* <ul>
* <li>便ID0</li>
* <li>CONTENT_NOTE_URI</li>
* </ul>
*
* @param uri URI
* @param selection
* @param selectionArgs
* @return int
* @throws IllegalArgumentException URI
* @see android.content.ContentProvider#delete(android.net.Uri, java.lang.String, java.lang.String[])
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/**
* URI
* <p>
*
* <ul>
* <li>URI</li>
* <li></li>
* <li>便</li>
* <li></li>
* <li>URI</li>
* </ul>
* <p>
* URI
* <ul>
* <li>{@link #URI_NOTE} - 便</li>
* <li>{@link #URI_NOTE_ITEM} - 便</li>
* <li>{@link #URI_DATA} - 便</li>
* <li>{@link #URI_DATA_ITEM} - 便</li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link NotesDatabaseHelper#getWritableDatabase()}</li>
* <li>使{@link SQLiteDatabase}</li>
* <li>{@link #increaseNoteVersion(long, String, String[])}便</li>
* <li>{@link android.content.ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver)}</li>
* </ul>
* <p>
*
* <ul>
* <li>便</li>
* <li>CONTENT_NOTE_URI</li>
* </ul>
*
* @param uri URI
* @param values
* @param selection
* @param selectionArgs
* @return int
* @throws IllegalArgumentException URI
* @see android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[])
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/**
*
* <p>
*
* <ul>
* <li>selection" AND (selection)"</li>
* <li></li>
* </ul>
*
* @param selection
* @return String
*/
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
/**
* 便
* <p>
*
* <ul>
* <li>UPDATE1</li>
* <li>idselectionWHERE</li>
* <li>SQL</li>
* </ul>
* <p>
*
* <ul>
* <li>使{@link NotesDatabaseHelper#getWritableDatabase()}</li>
* <li>使{@link SQLiteDatabase#execSQL(String)}SQL</li>
* </ul>
* <p>
*
* <ul>
* <li>使SQLSQL</li>
* <li>使replaceFirst</li>
* <li>TODO: 使PreparedStatement</li>
* </ul>
*
* @param id 便ID-1便
* @param selection
* @param selectionArgs
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
sql.append(selectString);
}
mHelper.getWritableDatabase().execSQL(sql.toString());
}
/**
* URIMIME
* <p>
* null
* <p>
* TODO: MIME
*
* @param uri URI
* @return String URIMIMEnull
* @see android.content.ContentProvider#getType(android.net.Uri)
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}

@ -0,0 +1,180 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* <p>Google</p>
* <p></p>
* <ul>
* <li></li>
* <li>JSON</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Task}</li>
* <li>使JSON便</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Task}</li>
* <li>使{@link GTaskStringUtils}</li>
* <li>{@link GTaskManager}{@link GTaskSyncService}使</li>
* </ul>
*/
public class MetaData extends Task {
/**
* <p></p>
* <p>MetaData</p>
*/
private final static String TAG = MetaData.class.getSimpleName();
/**
* <p>GoogleID</p>
* <p>Google</p>
* <p>{@link #setContentByRemoteJSON(JSONObject)}JSON</p>
*/
private String mRelatedGid = null;
/**
* <p></p>
* <p></p>
* <ul>
* <li>GoogleIDJSON</li>
* <li>JSONnotes</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>JSONExceptionGoogleID</li>
* </ul>
*
* @param gid GoogleID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
/**
* <p>GoogleID</p>
* <p>Google</p>
*
* @return GoogleIDnull
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
* <p></p>
* <p>notesnull</p>
*
* @return truefalse
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* <p>JSON</p>
* <p></p>
* <ul>
* <li>setContentByRemoteJSON</li>
* <li>notesnullJSON</li>
* <li>GoogleID</li>
* </ul>
* <p></p>
* <ul>
* <li>JSONExceptionmRelatedGidnull</li>
* </ul>
*
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
/**
* <p>JSON</p>
* <p>IllegalAccessError</p>
* <p>MetaDataJSON</p>
*
* @param js JSON
* @throws IllegalAccessError
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
* <p>JSON</p>
* <p>IllegalAccessError</p>
* <p>MetaDataJSON</p>
*
* @return JSON
* @throws IllegalAccessError
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/**
* <p></p>
* <p>IllegalAccessError</p>
* <p>MetaData</p>
*
* @param c
* @return
* @throws IllegalAccessError
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -0,0 +1,247 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
/**
* <p>Google</p>
* <p>Google</p>
* <p></p>
* <ul>
* <li>GoogleIDGID</li>
* <li>//JSON</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Task}, {@link MetaData}</li>
* <li>{@link GTaskClient}Google</li>
* <li>{@link Cursor}</li>
* </ul>
*/
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;
/**
* GoogleID
*/
private String mGid;
/**
*
*/
private String mName;
/**
*
*/
private long mLastModified;
/**
*
*/
private boolean mDeleted;
/**
*
*/
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getCreateAction(int actionId);
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
*
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getUpdateAction(int actionId);
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
*
* @param js JSONGoogle
*/
public abstract void setContentByRemoteJSON(JSONObject js);
/**
* <p>JSON</p>
* <p>JSON</p>
*
* @param js JSON
*/
public abstract void setContentByLocalJSON(JSONObject js);
/**
* <p>JSON</p>
* <p>JSON</p>
*
* @return JSON
*/
public abstract JSONObject getLocalJSONFromContent();
/**
* <p></p>
* <p></p>
* <p>
* <ul>
* <li>{@link #SYNC_ACTION_NONE}</li>
* <li>{@link #SYNC_ACTION_ADD_REMOTE}</li>
* <li>{@link #SYNC_ACTION_UPDATE_LOCAL}</li>
* <li>{@link #SYNC_ACTION_UPDATE_CONFLICT}</li>
* <li></li>
* </ul>
* </p>
*
* @param c
* @return {@link #SYNC_ACTION_NONE}{@link #SYNC_ACTION_ERROR}
*/
public abstract int getSyncAction(Cursor c);
/**
* GoogleID
*
* @param gid GoogleID
*/
public void setGid(String gid) {
this.mGid = gid;
}
/**
*
*
* @param name
*/
public void setName(String name) {
this.mName = name;
}
/**
*
*
* @param lastModified
*/
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
/**
*
*
* @param deleted
*/
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
/**
* GoogleID
*
* @return GoogleID
*/
public String getGid() {
return this.mGid;
}
/**
*
*
* @return
*/
public String getName() {
return this.mName;
}
/**
*
*
* @return
*/
public long getLastModified() {
return this.mLastModified;
}
/**
*
*
* @return
*/
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,305 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* <p>SQL便</p>
* <p>GoogleContentProvider访</p>
* <p></p>
* <ul>
* <li>Cursor</li>
* <li>JSON</li>
* <li>ContentProvider</li>
* <li></li>
* </ul>
*/
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
/**
* ID
*/
private static final int INVALID_ID = -99999;
/**
*
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
/**
* ID
*/
public static final int DATA_ID_COLUMN = 0;
/**
* MIME
*/
public static final int DATA_MIME_TYPE_COLUMN = 1;
/**
*
*/
public static final int DATA_CONTENT_COLUMN = 2;
/**
* DATA1
*/
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
/**
* DATA3
*/
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
/**
* 访ContentProvider
*/
private ContentResolver mContentResolver;
/**
*
*/
private boolean mIsCreate;
/**
* ID
*/
private long mDataId;
/**
* MIME
*/
private String mDataMimeType;
/**
*
*/
private String mDataContent;
/**
* DATA1
*/
private long mDataContentData1;
/**
* DATA3
*/
private String mDataContentData3;
/**
*
*/
private ContentValues mDiffDataValues;
/**
* <p>SqlData</p>
*
* @param context
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/**
* <p>CursorSqlData</p>
*
* @param context
* @param c Cursor
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
/**
* <p>Cursor</p>
*
* @param c Cursor
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* <p>JSON</p>
* <p></p>
* <ul>
* <li>JSON</li>
* <li></li>
* <li></li>
* </ul>
*
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
/**
* <p>JSON</p>
* <p></p>
* <ul>
* <li></li>
* <li>JSON</li>
* <li>JSON</li>
* </ul>
*
* @return JSONnull
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
/**
* <p>ContentProvider</p>
* <p></p>
* <ul>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
*
* @param noteId 便ID
* @param validateVersion
* @param version
* @throws ActionFailureException
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
/**
* <p>ID</p>
*
* @return ID
*/
public long getId() {
return mDataId;
}
}

@ -0,0 +1,759 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* <p>便Google便</p>
* <p></p>
* <ul>
* <li>便</li>
* <li>便{@link SqlData}</li>
* <li>便JSON</li>
* <li>便</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>Google</li>
* <li>ContentProviderAPI</li>
* <li></li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>使{@link Context}访ContentResolver</li>
* <li>使{@link ContentProvider}</li>
* <li>{@link SqlData}便</li>
* <li>{@link GTaskManager}Google</li>
* </ul>
*/
public class SqlNote {
/**
* <p></p>
* <p>SqlNote便</p>
*/
private static final String TAG = SqlNote.class.getSimpleName();
/**
* <p>ID</p>
* <p>便ID</p>
* <p>-99999</p>
*/
private static final int INVALID_ID = -99999;
/**
* <p>便</p>
* <p>ContentProvider便</p>
* <p>IDID</p>
*/
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,
NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE,
NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID,
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>0</p>
*/
public static final int ID_COLUMN = 0;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>1</p>
*/
public static final int ALERTED_DATE_COLUMN = 1;
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>2</p>
*/
public static final int BG_COLOR_ID_COLUMN = 2;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>3</p>
*/
public static final int CREATED_DATE_COLUMN = 3;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>4</p>
*/
public static final int HAS_ATTACHMENT_COLUMN = 4;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>5</p>
*/
public static final int MODIFIED_DATE_COLUMN = 5;
/**
* <p>便</p>
* <p>PROJECTION_NOTE便</p>
* <p>6</p>
*/
public static final int NOTES_COUNT_COLUMN = 6;
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>7</p>
*/
public static final int PARENT_ID_COLUMN = 7;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>8</p>
*/
public static final int SNIPPET_COLUMN = 8;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>9</p>
*/
public static final int TYPE_COLUMN = 9;
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>10</p>
*/
public static final int WIDGET_ID_COLUMN = 10;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>11</p>
*/
public static final int WIDGET_TYPE_COLUMN = 11;
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>12</p>
*/
public static final int SYNC_ID_COLUMN = 12;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>13</p>
*/
public static final int LOCAL_MODIFIED_COLUMN = 13;
/**
* <p>ID</p>
* <p>PROJECTION_NOTEID</p>
* <p>14</p>
*/
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
/**
* <p>GoogleID</p>
* <p>PROJECTION_NOTEGoogleID</p>
* <p>15</p>
*/
public static final int GTASK_ID_COLUMN = 15;
/**
* <p></p>
* <p>PROJECTION_NOTE</p>
* <p>16</p>
*/
public static final int VERSION_COLUMN = 16;
/**
* <p></p>
* <p>访ContentResolver</p>
* <p>使ContentProvider</p>
*/
private Context mContext;
/**
* <p></p>
* <p>ContentProvider</p>
* <p>使CRUD</p>
*/
private ContentResolver mContentResolver;
/**
* <p>便</p>
* <p>便</p>
* <p>使</p>
*/
private boolean mIsCreate;
/**
* <p>便ID</p>
* <p>便</p>
* <p>INVALID_ID-99999</p>
*/
private long mId;
/**
* <p></p>
* <p>便</p>
* <p>0</p>
*/
private long mAlertDate;
/**
* <p>ID</p>
* <p>便ID</p>
* <p>ResourceParser.getDefaultBgId(context)</p>
*/
private int mBgColorId;
/**
* <p></p>
* <p>便</p>
* <p></p>
*/
private long mCreatedDate;
/**
* <p></p>
* <p>便</p>
* <p>01</p>
* <p>0</p>
*/
private int mHasAttachment;
/**
* <p></p>
* <p>便</p>
* <p></p>
*/
private long mModifiedDate;
/**
* <p>便ID</p>
* <p>便ID</p>
* <p>0</p>
*/
private long mParentId;
/**
* <p>便</p>
* <p>便</p>
* <p></p>
*/
private String mSnippet;
/**
* <p>便</p>
* <p>便便</p>
* <p>Notes.TYPE_NOTE</p>
*/
private int mType;
/**
* <p>ID</p>
* <p>ID</p>
* <p>AppWidgetManager.INVALID_APPWIDGET_ID</p>
*/
private int mWidgetId;
/**
* <p></p>
* <p></p>
* <p>Notes.TYPE_WIDGET_INVALIDE</p>
*/
private int mWidgetType;
/**
* <p>便ID</p>
* <p>便ID</p>
* <p>0</p>
*/
private long mOriginParent;
/**
* <p></p>
* <p>便</p>
* <p>0</p>
*/
private long mVersion;
/**
* <p>便</p>
* <p>便</p>
* <p>使setContentcommit</p>
*/
private ContentValues mDiffNoteValues;
/**
* <p>便</p>
* <p>便</p>
* <p>使SqlData便</p>
*/
private ArrayList<SqlData> mDataList;
/**
* <p>SqlNote</p>
* <p>便</p>
* <p></p>
* <ul>
* <li>mIsCreatetrue便</li>
* <li></li>
* <li></li>
* </ul>
*
* @param context 访ContentResolver
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true;
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
/**
* <p>CursorSqlNote</p>
* <p>便</p>
* <p></p>
* <ul>
* <li>mIsCreatefalse便</li>
* <li>Cursor便</li>
* <li>便便</li>
* <li></li>
* </ul>
*
* @param context 访ContentResolver
* @param c Cursor便
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/**
* <p>IDSqlNote</p>
* <p>便ID便</p>
* <p></p>
* <ul>
* <li>mIsCreatefalse便</li>
* <li>ID便</li>
* <li>便便</li>
* <li></li>
* </ul>
*
* @param context 访ContentResolver
* @param id 便ID便
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
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);
if (c != null) {
c.moveToNext();
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
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);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = createDate;
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = hasAttachment;
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate;
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = widgetId;
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
sqlData.setContent(data);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
}
return true;
}
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
public long getId() {
return mId;
}
public long getParentId() {
return mParentId;
}
public String getSnippet() {
return mSnippet;
}
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
}
}
}
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues.clear();
mIsCreate = false;
}
}

@ -0,0 +1,560 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* <p>Google{@link Node}</p>
* <p>Google</p>
* <p></p>
* <ul>
* <li>Google</li>
* <li>//JSON</li>
* <li></li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Node}Google</li>
* <li>{@link TaskList}</li>
* <li>{@link MetaData}</li>
* <li>{@link GTaskStringUtils}使GoogleJSON</li>
* </ul>
*/
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
/**
*
*/
private boolean mCompleted;
/**
*
*/
private String mNotes;
/**
* JSON
*/
private JSONObject mMetaInfo;
/**
*
*/
private Task mPriorSibling;
/**
*
*/
private TaskList mParent;
/**
*
*/
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
* <p>JSON
* <ul>
* <li>action_type: "create"</li>
* <li>action_id: ID</li>
* <li>index: </li>
* <li>entity_delta: ID</li>
* <li>parent_id: ID</li>
* <li>dest_parent_type: "group"</li>
* <li>list_id: ID</li>
* <li>prior_sibling_id: ID</li>
* </ul>
* </p>
*
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js;
}
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
* <p>JSON
* <ul>
* <li>action_type: "update"</li>
* <li>action_id: ID</li>
* <li>id: ID</li>
* <li>entity_delta: </li>
* </ul>
* </p>
*
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
}
return js;
}
/**
* <p>JSON</p>
* <p>GoogleJSON</p>
* <p>JSON
* <ul>
* <li>id: IDGID</li>
* <li>last_modified: </li>
* <li>name: </li>
* <li>notes: </li>
* <li>deleted: </li>
* <li>completed: </li>
* </ul>
* </p>
*
* @param js JSONGoogle
* @throws ActionFailureException JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
/**
* <p>JSON</p>
* <p>JSON</p>
* <p>JSON
* <ul>
* <li>meta_note: </li>
* <li>meta_data: MIMENOTE</li>
* </ul>
* </p>
*
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type");
return;
}
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
/**
* <p>JSON</p>
* <p>JSON</p>
* <p>JSON
* <ul>
* <li>meta_note: </li>
* <li>meta_data: MIMENOTE</li>
* </ul>
* </p>
* <p>
* <ol>
* <li>JSONWeb</li>
* <li></li>
* </ol>
* </p>
*
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/**
* <p></p>
* <p>MetaDataJSON</p>
* <p>
* <ol>
* <li>MetaDatanotes</li>
* <li>notesJSONJSONObject</li>
* <li>null</li>
* </ol>
* </p>
*
* @param metaData MetaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
/**
* <p></p>
* <p></p>
* <p>
* <ul>
* <li>{@link Node#SYNC_ACTION_NONE}: </li>
* <li>{@link Node#SYNC_ACTION_UPDATE_LOCAL}: </li>
* <li>{@link Node#SYNC_ACTION_UPDATE_REMOTE}: </li>
* <li>{@link Node#SYNC_ACTION_UPDATE_CONFLICT}: </li>
* <li>{@link Node#SYNC_ACTION_ERROR}: </li>
* </ul>
* </p>
* <p>
* <ol>
* <li>ID</li>
* <li></li>
* <li>GTASK ID</li>
* <li></li>
* </ol>
* </p>
*
* @param c
* @return NodeSYNC_ACTION_*
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
}
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL;
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
/**
* <p></p>
* <p></p>
* <p>
* <ul>
* <li>mMetaInfonull</li>
* <li>0</li>
* <li>0</li>
* </ul>
* </p>
*
* @return truefalse
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
/**
*
*
* @param completed truefalse
*/
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
/**
*
*
* @param notes
*/
public void setNotes(String notes) {
this.mNotes = notes;
}
/**
*
*
* @param priorSibling
*/
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
/**
*
*
* @param parent
*/
public void setParent(TaskList parent) {
this.mParent = parent;
}
/**
*
*
* @return truefalse
*/
public boolean getCompleted() {
return this.mCompleted;
}
/**
*
*
* @return
*/
public String getNotes() {
return this.mNotes;
}
/**
*
*
* @return null
*/
public Task getPriorSibling() {
return this.mPriorSibling;
}
/**
*
*
* @return null
*/
public TaskList getParent() {
return this.mParent;
}
}

@ -0,0 +1,495 @@
/*
* 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.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* GoogleNode
* <p>
* Google Tasks
*
* <ul>
* <li>/JSON</li>
* <li>/JSON</li>
* <li></li>
* <li></li>
* </ul>
* </p>
*/
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();
/**
* Google
*/
private int mIndex;
/**
*
*/
private ArrayList<Task> mChildren;
/**
*
* <p>
* 1
* </p>
*/
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
/**
* JSON
* <p>
* Google Tasks
* </p>
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
return js;
}
/**
* JSON
* <p>
* Google Tasks
* </p>
* @param actionId ID
* @return JSON
* @throws ActionFailureException JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
return js;
}
/**
* JSON
* <p>
* Google TasksJSON
* </p>
* @param js JSON
* @throws ActionFailureException JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
/**
* JSON
* <p>
* JSON
* </p>
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
Log.e(TAG, "invalid system folder");
} else {
Log.e(TAG, "error type");
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
/**
* JSON
* <p>
* JSON
* </p>
* @return JSONnull
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/**
*
* <p>
*
* </p>
* @param c
* @return
* <ul>
* <li>{@link Node#SYNC_ACTION_NONE} - </li>
* <li>{@link Node#SYNC_ACTION_UPDATE_LOCAL} - </li>
* <li>{@link Node#SYNC_ACTION_UPDATE_REMOTE} - </li>
* <li>{@link Node#SYNC_ACTION_ERROR} - </li>
* </ul>
*/
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
/**
*
* @return
*/
public int getChildTaskCount() {
return mChildren.size();
}
/**
*
* <p>
*
* </p>
* @param task
* @return true if the task was added successfully, false otherwise
*/
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
}
}
return ret;
}
/**
*
* <p>
*
* </p>
* @param task
* @param index
* @return true if the task was added successfully, false otherwise
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
// update the task list
Task preTask = null;
Task afterTask = null;
if (index != 0)
preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1);
task.setPriorSibling(preTask);
if (afterTask != null)
afterTask.setPriorSibling(task);
}
return true;
}
/**
*
* <p>
*
* </p>
* @param task
* @return true if the task was removed successfully, false otherwise
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task);
if (ret) {
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null);
// update the task list
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
}
}
return ret;
}
/**
*
* <p>
*
* </p>
* @param task
* @param index
* @return true if the task was moved successfully, false otherwise
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}
if (pos == index)
return true;
return (removeChildTask(task) && addChildTask(task, index));
}
/**
* GoogleID
* @param gid GoogleID
* @return null
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}
}
return null;
}
/**
*
* @param task
* @return -1
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
/**
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
}
return mChildren.get(index);
}
/**
* GoogleID
* <p>
* {@link #findChildTaskByGid(String)}
* </p>
* @param gid GoogleID
* @return null
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
}
return null;
}
/**
*
* @return
*/
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
/**
*
* @param index
*/
public void setIndex(int index) {
this.mIndex = index;
}
/**
*
* @return
*/
public int getIndex() {
return this.mIndex;
}
}

@ -0,0 +1,53 @@
/*
* 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.
*/
package net.micode.notes.gtask.exception;
/**
* <p>Google</p>
* <p>Google</p>
* <p>Google</p>
* <p>{@link RuntimeException}</p>
*/
public class ActionFailureException extends RuntimeException {
private static final long serialVersionUID = 4425249765923293627L;
/**
*
*/
public ActionFailureException() {
super();
}
/**
* 使
*
* @param paramString
*/
public ActionFailureException(String paramString) {
super(paramString);
}
/**
* 使
*
* @param paramString
* @param paramThrowable
*/
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}

@ -0,0 +1,53 @@
/*
* 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.
*/
package net.micode.notes.gtask.exception;
/**
* <p></p>
* <p>Google</p>
* <p></p>
* <p>{@link Exception}</p>
*/
public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L;
/**
*
*/
public NetworkFailureException() {
super();
}
/**
* 使
*
* @param paramString
*/
public NetworkFailureException(String paramString) {
super(paramString);
}
/**
* 使
*
* @param paramString
* @param paramThrowable
*/
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}

@ -0,0 +1,234 @@
/*
* 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.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* <p>Google</p>
* <p>使AsyncTaskGoogle线</p>
* <p></p>
* <ul>
* <li>线Google</li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link AsyncTask}</li>
* <li>使{@link GTaskManager}</li>
* <li>{@link GTaskSyncService}广</li>
* <li>{@link OnCompleteListener}</li>
* </ul>
*/
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
/**
* GoogleID
*/
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
/**
* <p></p>
* <p>Google</p>
*/
public interface OnCompleteListener {
/**
*
*/
void onComplete();
}
/**
* 访
*/
private Context mContext;
/**
*
*/
private NotificationManager mNotifiManager;
/**
* Google
*/
private GTaskManager mTaskManager;
/**
*
*/
private OnCompleteListener mOnCompleteListener;
/**
* Google
*
* @param context 访
* @param listener
*/
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
}
/**
*
*/
public void cancelSync() {
mTaskManager.cancelSync();
}
/**
*
*
* @param message
*/
public void publishProgess(String message) {
publishProgress(new String[] {
message
});
}
// 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);
//
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
/**
* <p></p>
* <p></p>
* <p>
* <ul>
* <li></li>
* <li></li>
* </ul>
* </p>
*
* @param tickerId ID
* @param content
*/
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
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);
}
/**
* <p>线</p>
* <p>GTaskManager</p>
*
* @param unused 使
* @return GTaskManagerSTATE_*
*/
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
/**
* <p></p>
* <p>广</p>
*
* @param progress
*/
@Override
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]);
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
/**
* <p></p>
* <p></p>
* <p>
* <ul>
* <li>STATE_SUCCESS</li>
* <li>STATE_NETWORK_ERROR</li>
* <li>STATE_INTERNAL_ERROR</li>
* <li>STATE_SYNC_CANCELLED</li>
* </ul>
* </p>
*
* @param result GTaskManagerSTATE_*
*/
@Override
protected void onPostExecute(Integer result) {
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,842 @@
/*
* 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.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* <p>GoogleAPI</p>
* <p>GoogleAPI</p>
* <p></p>
* <ul>
* <li>Google</li>
* <li>GoogleCRUD</li>
* <li>APICookie</li>
* <li></li>
* <li>GoogleAPIJSON</li>
* </ul>
* <p></p>
* <ul>
* <li>{@link GTaskManager}</li>
* <li>使{@link Task}{@link TaskList}Google</li>
* <li>{@link NetworkFailureException}{@link ActionFailureException}</li>
* <li>{@link GTaskStringUtils}API</li>
* </ul>
* <p></p>
* <ul>
* <li>GoogleAPI</li>
* <li></li>
* <li>API</li>
* <li>gzipdeflate</li>
* </ul>
*/
public class GTaskClient {
/**
*
*/
private static final String TAG = GTaskClient.class.getSimpleName();
/**
* GoogleURL
*/
private static final String GTASK_URL = "https://mail.google.com/tasks/";
/**
* GoogleGETURL
*/
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
/**
* GooglePOSTURL
*/
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
/**
* GTaskClient
*/
private static GTaskClient mInstance = null;
/**
* HTTPGoogleAPI
*/
private DefaultHttpClient mHttpClient;
/**
* 使GETURL
*/
private String mGetUrl;
/**
* 使POSTURL
*/
private String mPostUrl;
/**
* Google
*/
private long mClientVersion;
/**
*
*/
private boolean mLoggedin;
/**
*
*/
private long mLastLoginTime;
/**
* IDAPI
*/
private int mActionId;
/**
* Google
*/
private Account mAccount;
/**
* JSON
*/
private JSONArray mUpdateArray;
/**
* <p></p>
* <p></p>
* <p></p>
*/
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
/**
* <p>GTaskClient</p>
* <p>使线</p>
*
* @return GTaskClient
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
/**
* <p>GoogleGoogleAPI</p>
* <p></p>
* <ul>
* <li>Cookie</li>
* <li>Google</li>
* <li>使URL</li>
* <li>使URL</li>
* </ul>
*
* @param activity Activity
* @return truefalse
*/
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
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
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";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// try to login with google official url
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
/**
* <p>Google</p>
* <p></p>
* <ul>
* <li>Google</li>
* <li>Account</li>
* <li>使AccountManager</li>
* <li>使</li>
* </ul>
*
* @param activity ActivityAccountManager
* @param invalidateToken 使
* @return null
*/
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
if (account != null) {
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 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);
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
/**
* <p>GoogleAPI</p>
* <p></p>
* <ul>
* <li>使</li>
* <li>使</li>
* <li>使</li>
* </ul>
*
* @param activity Activity
* @param authToken Google
* @return truefalse
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
/**
* <p>GoogleAPI</p>
* <p></p>
* <ul>
* <li>HTTP</li>
* <li>Cookie</li>
* <li>HTTP GET</li>
* <li>Cookie</li>
* <li></li>
* </ul>
*
* @param authToken Google
* @return truefalse
*/
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// get the client version
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
/**
* <p>ID</p>
* <p>IDAPI</p>
*
* @return ID
*/
private int getActionId() {
return mActionId++;
}
/**
* <p>HTTP POST</p>
* <p>POSTURL</p>
*
* @return 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;
}
/**
* <p>HTTP</p>
* <p></p>
* <ul>
* <li></li>
* <li>gzipdeflate</li>
* <li></li>
* </ul>
*
* @param entity HTTP
* @return
* @throws IOException
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
while (true) {
String buff = br.readLine();
if (buff == null) {
return sb.toString();
}
sb = sb.append(buff);
}
} finally {
input.close();
}
}
/**
* <p>POSTGoogleAPI</p>
* <p></p>
* <ul>
* <li></li>
* <li>HTTP POST</li>
* <li>JSON</li>
* <li></li>
* <li>JSON</li>
* </ul>
*
* @param js JSON
* @return JSON
* @throws NetworkFailureException
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
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
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
/**
* <p>Google</p>
* <p></p>
* <ul>
* <li></li>
* <li>JSON</li>
* <li>POST</li>
* <li>GID</li>
* </ul>
*
* @param task
* @throws NetworkFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
/**
* <p>Google</p>
* <p></p>
* <ul>
* <li></li>
* <li>JSON</li>
* <li>POST</li>
* <li>GID</li>
* </ul>
*
* @param tasklist
* @throws NetworkFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
/**
* <p></p>
* <p>mUpdateArrayGoogleAPI</p>
*
* @throws NetworkFailureException
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject();
// action_list
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
/**
* <p></p>
* <p></p>
* <ul>
* <li>10</li>
* <li></li>
* </ul>
*
* @param node
* @throws NetworkFailureException
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
/**
* <p></p>
* <p></p>
* <ul>
* <li></li>
* <li>JSON</li>
* <li>ID</li>
* <li>POST</li>
* </ul>
*
* @param task
* @param preParent
* @param curParent
* @throws NetworkFailureException
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
/**
* <p></p>
* <p></p>
* <ul>
* <li></li>
* <li></li>
* <li>JSON</li>
* <li>POST</li>
* <li></li>
* </ul>
*
* @param node
* @throws NetworkFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
/**
* <p>Google</p>
* <p></p>
* <ul>
* <li></li>
* <li>GET</li>
* <li></li>
* </ul>
*
* @return JSON
* @throws NetworkFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the task list
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
public Account getSyncAccount() {
return mAccount;
}
public void resetUpdateArray() {
mUpdateArray = null;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,290 @@
/*
* 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.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
/**
* <p>Google</p>
* <p>AndroidGoogle</p>
* <p></p>
* <ul>
* <li></li>
* <li></li>
* <li>广</li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>使{@link GTaskASyncTask}</li>
* <li>{@link GTaskManager}</li>
* <li>广UI</li>
* </ul>
*/
public class GTaskSyncService extends Service {
/**
* Intent extra
*/
public final static String ACTION_STRING_NAME = "sync_action_type";
/**
*
*/
public final static int ACTION_START_SYNC = 0;
/**
*
*/
public final static int ACTION_CANCEL_SYNC = 1;
/**
*
*/
public final static int ACTION_INVALID = 2;
/**
* 广
*/
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
/**
* 广extra
*/
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
/**
* 广extra
*/
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
/**
*
*/
private static GTaskASyncTask mSyncTask = null;
/**
*
*/
private static String mSyncProgress = "";
/**
* Google
*
* <p></p>
* <ul>
* <li></li>
* <li></li>
* <li></li>
* <li>广</li>
* <li></li>
* </ul>
*/
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
mSyncTask = null;
sendBroadcast("");
stopSelf();
}
});
sendBroadcast("");
mSyncTask.execute();
}
}
/**
* Google
*
* <p></p>
* <ul>
* <li></li>
* <li></li>
* </ul>
*/
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
/**
*
*
* <p></p>
* <ul>
* <li>null</li>
* </ul>
*/
@Override
public void onCreate() {
mSyncTask = null;
}
/**
* Intent
*
* <p></p>
* <ul>
* <li>Intent</li>
* <li></li>
* <li>START_STICKY</li>
* </ul>
*
* @param intent Intent
* @param flags
* @param startId ID
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync();
break;
default:
break;
}
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId);
}
/**
*
*
* <p></p>
* <ul>
* <li></li>
* </ul>
*/
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
/**
*
*
* <p></p>
* <ul>
* <li>null</li>
* </ul>
*
* @param intent Intent
* @return null
*/
public IBinder onBind(Intent intent) {
return null;
}
/**
* 广
*
* <p></p>
* <ul>
* <li></li>
* <li>广Intent</li>
* <li>广UI</li>
* </ul>
*
* @param msg
*/
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
/**
* Google
*
* <p></p>
* <ul>
* <li>GTaskManagerActivity</li>
* <li>Intent</li>
* <li></li>
* </ul>
*
* @param activity Activity
*/
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
/**
* Google
*
* <p></p>
* <ul>
* <li>Intent</li>
* <li></li>
* </ul>
*
* @param context
*/
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
/**
*
*
* <p></p>
* <ul>
* <li>null</li>
* </ul>
*
* @return truefalse
*/
public static boolean isSyncing() {
return mSyncTask != null;
}
/**
*
*
* <p></p>
* <ul>
* <li></li>
* </ul>
*
* @return
*/
public static String getProgressString() {
return mSyncProgress;
}
}

@ -0,0 +1,542 @@
/*
* 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.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
/**
* <p></p>
* <p></p>
* <ul>
* <li>{@link #mNoteDiffValues}</li>
* <li>{@link NoteData}</li>
* <li>ContentResolver</li>
* </ul>
* <p></p>
* <ul>
* <li>{@link net.micode.notes.data.NotesProvider}CRUD</li>
* <li>使{@link android.content.ContentValues}</li>
* <li>{@link android.content.ContentProviderOperation}</li>
* </ul>
*/
public class Note {
/**
* <p></p>
* <p></p>
* <p></p>
*/
private ContentValues mNoteDiffValues;
/**
* <p></p>
* <p></p>
*/
private NoteData mNoteData;
/**
* <p></p>
* <p>Note</p>
*/
private static final String TAG = "Note";
/**
* <p>ID</p>
* <p>ID</p>
* <p>线使synchronized线</p>
*
* @param context ContentResolver
* @param folderId ID
* @return ID
* @throws IllegalStateException ID-1
*
* <p></p>
* <ul>
* <li>{@link Context#getContentResolver()}ContentResolver</li>
* <li>{@link android.content.ContentResolver#insert(Uri, ContentValues)}{@link Notes#CONTENT_NOTE_URI}</li>
* </ul>
*
* <p></p>
* <ul>
* <li>NumberFormatException0</li>
* <li>ID-1IllegalStateException</li>
* </ul>
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
/**
* <p>Note</p>
* <p></p>
* <ul>
* <li>{@link #mNoteDiffValues}ContentValues</li>
* <li>{@link #mNoteData}NoteData</li>
* </ul>
*/
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
/**
* <p></p>
* <p></p>
*
* @param key
* @param value
*
* <p></p>
* <ul>
* <li>{@link NoteColumns#LOCAL_MODIFIED}1</li>
* <li>{@link NoteColumns#MODIFIED_DATE}</li>
* </ul>
*/
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* <p></p>
* <p>{@link NoteData}</p>
*
* @param key
* @param value
*/
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
/**
* <p>ID</p>
* <p>{@link NoteData}ID</p>
*
* @param id ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
/**
* <p>ID</p>
* <p>{@link NoteData}ID</p>
*
* @return ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId;
}
/**
* <p>ID</p>
* <p>{@link NoteData}ID</p>
*
* @param id ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
/**
* <p></p>
* <p>{@link NoteData}</p>
*
* @param key
* @param value
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
/**
* <p></p>
* <p></p>
*
* @return truefalse
*
* <p></p>
* <ul>
* <li>{@link #mNoteDiffValues}0</li>
* <li>{@link NoteData#isLocalModified()}</li>
* </ul>
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
* <p>ContentResolver</p>
* <p></p>
*
* @param context ContentResolver
* @param noteId ID
* @return truefalse
* @throws IllegalArgumentException noteId<=0
*
* <p></p>
* <ol>
* <li>noteId</li>
* <li>true</li>
* <li>ContentResolver</li>
* <li>ContentResolver</li>
* <li></li>
* </ol>
*
* <p></p>
* <ul>
* <li>{@link #mNoteDiffValues}ContentResolver.update()</li>
* <li>{@link NoteData#pushIntoContentResolver(Context, long)}</li>
* </ul>
*
* <p></p>
* <ul>
* <li></li>
* <li>false</li>
* </ul>
*
* <p></p>
* <ul>
* <li>{@link Context#getContentResolver()}ContentResolver</li>
* <li>{@link android.content.ContentResolver#update(Uri, ContentValues, String, String[])}</li>
* <li>{@link NoteData#pushIntoContentResolver(Context, long)}</li>
* </ul>
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}
/**
* <p></p>
* <p></p>
* <p>{@link Note}</p>
* <ul>
* <li></li>
* <li></li>
* <li></li>
* <li>ContentResolver</li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Note}</li>
* <li>使{@link ContentValues}</li>
* <li>{@link ContentProviderOperation}</li>
* </ul>
*/
private class NoteData {
/**
* <p>ID</p>
* <p></p>
* <p>0</p>
*/
private long mTextDataId;
/**
* <p></p>
* <p></p>
*/
private ContentValues mTextDataValues;
/**
* <p>ID</p>
* <p></p>
* <p>0</p>
*/
private long mCallDataId;
/**
* <p></p>
* <p></p>
*/
private ContentValues mCallDataValues;
/**
* <p></p>
* <p>NoteData</p>
*/
private static final String TAG = "NoteData";
/**
* <p>NoteData</p>
* <p>ID</p>
* <ul>
* <li>{@link #mTextDataValues}ContentValues</li>
* <li>{@link #mCallDataValues}ContentValues</li>
* <li>{@link #mTextDataId}{@link #mCallDataId}0</li>
* </ul>
*/
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
/**
* <p></p>
* <p></p>
*
* @return truefalse
*
* <p></p>
* <ul>
* <li>{@link #mTextDataValues}0</li>
* <li>{@link #mCallDataValues}0</li>
* </ul>
*/
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* <p>ID</p>
* <p></p>
*
* @param id ID
* @throws IllegalArgumentException id<=0
*/
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
/**
* <p>ID</p>
* <p></p>
*
* @param id ID
* @throws IllegalArgumentException id<=0
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
/**
* <p></p>
* <p></p>
*
* @param key
* @param value
*
* <p></p>
* <ul>
* <li>{@link Note}{@link Note#mNoteDiffValues}{@link NoteColumns#LOCAL_MODIFIED}1</li>
* <li>{@link Note}{@link Note#mNoteDiffValues}{@link NoteColumns#MODIFIED_DATE}</li>
* </ul>
*/
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* <p></p>
* <p></p>
*
* @param key
* @param value
*
* <p></p>
* <ul>
* <li>{@link Note}{@link Note#mNoteDiffValues}{@link NoteColumns#LOCAL_MODIFIED}1</li>
* <li>{@link Note}{@link Note#mNoteDiffValues}{@link NoteColumns#MODIFIED_DATE}</li>
* </ul>
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* <p>ContentResolver</p>
* <p></p>
*
* @param context ContentResolver
* @param noteId ID
* @return Urinull
* @throws IllegalArgumentException noteId<=0
*
* <p></p>
* <ol>
* <li>noteId</li>
* <li></li>
* <ul>
* <li>{@link #mTextDataId}0</li>
* <li></li>
* </ul>
* <li></li>
* <ul>
* <li>{@link #mCallDataId}0</li>
* <li></li>
* </ul>
* <li>ContentProviderOperation</li>
* </ol>
*
* <p></p>
* <ul>
* <li>{@link Context#getContentResolver()}ContentResolver</li>
* <li>{@link android.content.ContentResolver#insert(Uri, ContentValues)}</li>
* <li>{@link android.content.ContentResolver#applyBatch(String, ArrayList)}</li>
* </ul>
*
* <p></p>
* <ul>
* <li>null</li>
* <li>RemoteExceptionOperationApplicationExceptionnull</li>
* </ul>
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
return null;
}
}
}

@ -0,0 +1,763 @@
/*
* 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.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
* <p></p>
* <p>Note便</p>
* <ul>
* <li></li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
* <p></p>
* <ul>
* <li>{@link Note}</li>
* <li>{@link Context}</li>
* <li>使{@link NotesProvider}</li>
* <li>{@link NoteSettingChangedListener}</li>
* </ul>
*/
public class WorkingNote {
/**
* <p></p>
* <p></p>
*/
private Note mNote;
/**
* <p></p>
* <p></p>
* <p>0</p>
*/
private long mNoteId;
/**
* <p></p>
* <p></p>
*/
private String mContent;
/**
* <p></p>
* <p></p>
*/
private int mMode;
/**
* <p></p>
* <p></p>
* <p>0</p>
*/
private long mAlertDate;
/**
* <p></p>
* <p></p>
*/
private long mModifiedDate;
/**
* <p>ID</p>
* <p>使</p>
*/
private int mBgColorId;
/**
* <p>ID</p>
* <p></p>
* <p>{@link AppWidgetManager#INVALID_APPWIDGET_ID}</p>
*/
private int mWidgetId;
/**
* <p></p>
* <p></p>
* <p>{@link Notes#TYPE_WIDGET_INVALIDE}</p>
*/
private int mWidgetType;
/**
* <p>ID</p>
* <p></p>
*/
private long mFolderId;
/**
* <p></p>
* <p>访</p>
*/
private Context mContext;
/**
* <p></p>
* <p>WorkingNote</p>
*/
private static final String TAG = "WorkingNote";
/**
* <p></p>
* <p></p>
*/
private boolean mIsDeleted;
/**
* <p></p>
* <p></p>
*/
private NoteSettingChangedListener mNoteSettingStatusListener;
/**
* <p></p>
* <p>Notes.CONTENT_DATA_URI</p>
*/
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
/**
* <p></p>
* <p>Notes.CONTENT_NOTE_URI</p>
*/
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
/**
* <p>DATA_PROJECTIONID</p>
*/
private static final int DATA_ID_COLUMN = 0;
/**
* <p>DATA_PROJECTIONCONTENT</p>
*/
private static final int DATA_CONTENT_COLUMN = 1;
/**
* <p>DATA_PROJECTIONMIME_TYPE</p>
*/
private static final int DATA_MIME_TYPE_COLUMN = 2;
/**
* <p>DATA_PROJECTIONDATA1</p>
*/
private static final int DATA_MODE_COLUMN = 3;
/**
* <p>NOTE_PROJECTIONPARENT_ID</p>
*/
private static final int NOTE_PARENT_ID_COLUMN = 0;
/**
* <p>NOTE_PROJECTIONALERTED_DATE</p>
*/
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
/**
* <p>NOTE_PROJECTIONBG_COLOR_ID</p>
*/
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
/**
* <p>NOTE_PROJECTIONWIDGET_ID</p>
*/
private static final int NOTE_WIDGET_ID_COLUMN = 3;
/**
* <p>NOTE_PROJECTIONWIDGET_TYPE</p>
*/
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
/**
* <p>NOTE_PROJECTIONMODIFIED_DATE</p>
*/
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
/**
* <p></p>
* <p></p>
*
* @param context 访
* @param folderId ID
*
* <p></p>
* <ul>
* <li>0</li>
* <li></li>
* <li>ID0</li>
* <li>false</li>
* <li>0</li>
* <li></li>
* </ul>
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note();
mNoteId = 0;
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
/**
* <p></p>
* <p></p>
*
* @param context 访
* @param noteId ID
* @param folderId ID
*
* <p></p>
* <ul>
* <li></li>
* <li>{@link #loadNote()}</li>
* <li>{@link #loadNoteData()}</li>
* </ul>
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
}
/**
* <p></p>
* <p></p>
*
* <p></p>
* <ol>
* <li>ContentResolver{@link Notes#CONTENT_NOTE_URI}</li>
* <li></li>
* <li>Cursor</li>
* <li></li>
* <li>{@link #loadNoteData()}</li>
* </ol>
*
* <p></p>
* <ul>
* <li>ID</li>
* <li>ID</li>
* <li>ID</li>
* <li></li>
* <li></li>
* <li></li>
* </ul>
*
* <p></p>
* <ul>
* <li></li>
* <li>IllegalArgumentException</li>
* </ul>
*/
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData();
}
/**
* <p></p>
* <p></p>
*
* <p></p>
* <ol>
* <li>ContentResolver{@link Notes#CONTENT_DATA_URI}</li>
* <li></li>
* <li></li>
* <ul>
* <li>ID</li>
* <li>ID</li>
* <li></li>
* </ul>
* <li>Cursor</li>
* <li></li>
* </ol>
*
* <p></p>
* <ul>
* <li></li>
* <li>IllegalArgumentException</li>
* </ul>
*/
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
cursor.close();
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
/**
* <p></p>
* <p></p>
*
* @param context 访
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
*
* @return
*
* <p></p>
* <ol>
* <li>WorkingNote</li>
* <li></li>
* <li>ID</li>
* <li></li>
* <li></li>
* </ol>
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
/**
* <p></p>
* <p>ID</p>
*
* @param context 访
* @param id ID
*
* @return
*
* <p></p>
* <ul>
* <li>WorkingNote</li>
* <li>{@link #loadNote()}</li>
* <li>loadNote(){@link #loadNoteData()}</li>
* </ul>
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
* <p></p>
* <p></p>
*
* @return
*
* <p></p>
* <ol>
* <li>{@link #isWorthSaving()}</li>
* <li></li>
* <ul>
* <li>{@link Note#getNewNoteId(Context, long)}ID</li>
* <li>false</li>
* </ul>
* <li>{@link Note#syncNote(Context, long)}</li>
* <li></li>
* <li></li>
* </ol>
*
* <p>线</p>
* <ul>
* <li>使synchronized线</li>
* </ul>
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
/**
* <p></p>
* <p>ID</p>
*
* @return truefalse
*
* <p></p>
* <ul>
* <li>ID0</li>
* <li>ID0</li>
* </ul>
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
* <p></p>
* <p></p>
*
* @return truefalse
*
* <p></p>
* <ul>
* <li> </li>
* <li> </li>
* <li> </li>
* <li> </li>
* </ul>
*
* <p></p>
* <ul>
* <li></li>
* <li></li>
* </ul>
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
/**
* <p></p>
* <p></p>
*
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
/**
* <p></p>
* <p></p>
*
* @param date
* @param set truefalse
*
* <p></p>
* <ol>
* <li></li>
* <ul>
* <li></li>
* <li>{@link Note#setNoteValue(String, String)}</li>
* </ul>
* <li></li>
* </ol>
*/
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
* <p></p>
* <p></p>
*
* @param mark truefalse
*
* <p></p>
* <ol>
* <li></li>
* <li></li>
* </ol>
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
* <p></p>
* <p></p>
*
* @param phoneNumber
* @param callDate
*
* <p></p>
* <ol>
* <li></li>
* <li></li>
* <li></li>
* </ol>
*/
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
/**
* <p></p>
* <p></p>
*
* @return truefalse
*
* <p></p>
* <ul>
* <li>0</li>
* <li>0</li>
* </ul>
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
* <p></p>
* @return
*/
public String getContent() {
return mContent;
}
/**
* <p></p>
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
* <p></p>
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* <p>ID</p>
* @return ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* <p>ID</p>
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* <p>ID</p>
* @return ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
* <p></p>
* @return 01
*/
public int getCheckListMode() {
return mMode;
}
/**
* <p>ID</p>
* @return
*/
public long getNoteId() {
return mNoteId;
}
/**
* <p>ID</p>
* @return ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* <p>ID</p>
* @return ID{@link AppWidgetManager#INVALID_APPWIDGET_ID}
*/
public int getWidgetId() {
return mWidgetId;
}
/**
* <p></p>
* @return {@link Notes#TYPE_WIDGET_INVALIDE}
*/
public int getWidgetType() {
return mWidgetType;
}
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -0,0 +1,436 @@
/*
* 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.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 便便SD便便
* BackupUtils
*/
public class BackupUtils {
private static final String TAG = "BackupUtils"; // 日志标签
// 单例实例
private static BackupUtils sInstance;
/**
*
* @param context 访Context访便SDAndroid
* @return BackupUtilsBackupUtils
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* /
*/
// SD卡未挂载无法读写
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在(恢复时使用)
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 数据格式损坏(可能被其他程序修改)
public static final int STATE_DATA_DESTROIED = 2;
// 运行时异常导致操作失败
public static final int STATE_SYSTEM_ERROR = 3;
// 备份/恢复成功
public static final int STATE_SUCCESS = 4;
// 文本导出的具体实现类
private TextExport mTextExport;
/**
*
* @param context
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
* SD
* @return truefalse
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());// 返回存储状态
}
/**
*
* @return STATE_SUCCESSSTATE_SD_CARD_UNMOUONTED
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
*
* @return notes_20240520.txt
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
*
* @return /sdcard/NotesBackup/
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
/**
*
*/
private static class TextExport {
// 查询便签时需要的字段投影(只查询需要的列,优化性能)
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, // 便签/文件夹ID
NoteColumns.MODIFIED_DATE,// 最后修改时间
NoteColumns.SNIPPET, // 文件夹名或便签摘要
NoteColumns.TYPE // 类型(文件夹/便签)
};
// 字段索引常量用于从Cursor中快速获取对应值
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
// 查询便签详情数据时的字段投影
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, // 内容(文本/位置)
DataColumns.MIME_TYPE, // 类型(普通便签/通话便签)
DataColumns.DATA1, // 通话便签的通话时间getFormat函数定义的
DataColumns.DATA2,
DataColumns.DATA3, // 通话便签的电话号码getFormat函数定义的
DataColumns.DATA4,
};
/*
* private String getFormat(int id) {
return TEXT_FORMAT[id];
}*/
// 数据字段索引常量
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2; // 通话时间在DATA1中getFormat函数定义的
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 电话号码在DATA3中getFormat函数定义的
// 文本格式化模板(从资源文件读取,如文件夹名、时间、内容的格式)
private final String [] TEXT_FORMAT;
// 格式化模板的索引常量
private static final int FORMAT_FOLDER_NAME = 0; // 文件夹名格式
private static final int FORMAT_NOTE_DATE = 1; // 便签时间格式
private static final int FORMAT_NOTE_CONTENT = 2; // 便签内容格式
private Context mContext; // 上下文
private String mFileName; // 导出的文件名
private String mFileDirectory; // 导出的文件目录
/**
*
* @param context
*/
public TextExport(Context context) {
// 从资源文件读取格式化模板如strings.xml中的format_for_exported_note数组
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
/**
*
* @param id FORMAT_FOLDER_NAME
* @return
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* 便
* @param folderId ID
* @param ps
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询该文件夹下的所有便签通过parent_id关联
Cursor notesCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 便签数据库的URI
NOTE_PROJECTION, // 要查询的字段
NoteColumns.PARENT_ID + "=?", // 查询条件parent_id=folderId
new String[] {folderId}, // 条件参数
null); // 排序方式(默认)
if (notesCursor != null) {
if (notesCursor.moveToFirst()) { // 遍历所有便签
do {
// 打印便签的最后修改时间(格式化显示)
ps.println(String.format(
getFormat(FORMAT_NOTE_DATE),
DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), // 时间格式(月日时分)
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)
)
));
// 导出该便签的详细内容
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close(); // 关闭游标,释放资源
}
}
/**
* 便
* 便便
* @param noteId 便ID
* @param ps
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询该便签的详细数据(如文本内容、通话记录信息)
Cursor dataCursor = mContext.getContentResolver().query(
Notes.CONTENT_DATA_URI, // 便签数据的URI
DATA_PROJECTION, // 要查询的字段
DataColumns.NOTE_ID + "=?", // 查询条件note_id=noteId
new String[] {noteId}, // 条件参数
null); // 排序方式
if (dataCursor != null) {
if (dataCursor.moveToFirst()) { // 遍历便签的所有数据
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { // 通话记录便签
// 提取电话号码、通话时间、位置信息
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 打印电话号码(非空时)
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber));
}
// 打印通话时间(格式化)
ps.println(String.format(
getFormat(FORMAT_NOTE_CONTENT),
DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
callDate
)
));
// 打印位置信息(非空时)
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location));
}
} else if (DataConstants.NOTE.equals(mimeType)) { // 普通文本便签
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) { // 打印文本内容
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close(); // 关闭游标,释放资源
}
// 便签之间添加分隔符(换行)
try {
ps.write(new byte[] {Character.LINE_SEPARATOR, Character.LINE_SEPARATOR});
} catch (IOException e) {
Log.e(TAG, "写入分隔符失败:" + e.toString());
}
}
/**
* SD
* @return STATE_SUCCESS
*/
public int exportToText() {
// 检查SD卡是否可用
if (!externalStorageAvailable()) {
Log.d(TAG, "SD卡未挂载无法导出");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取指向备份文件的输出流
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "创建输出流失败");
return STATE_SYSTEM_ERROR;
}
// 1. 导出所有文件夹及其包含的便签(排除回收站,包含通话记录文件夹)
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
// 查询条件:文件夹类型且不在回收站,或通话记录文件夹
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER,
null,
null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) { // 遍历所有文件夹
do {
// 获取文件夹名(通话记录文件夹使用固定名称)
String folderName = "";
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name); // 从资源获取“通话记录”
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); // 普通文件夹名
}
// 打印文件夹名(非空时)
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
// 导出该文件夹下的所有便签
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close(); // 关闭游标
}
// 2. 导出根目录下的便签无文件夹归属的便签parent_id=0
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0",
null,
null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) { // 遍历根目录便签
do {
// 打印修改时间
ps.println(String.format(
getFormat(FORMAT_NOTE_DATE),
DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)
)
));
// 导出便签内容
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close(); // 关闭游标
}
ps.close(); // 关闭输出流
return STATE_SUCCESS; // 导出成功
}
/**
* SDPrintStream
* @return PrintStreamnull
*/
private PrintStream getExportToTextPrintStream() {
// 在SD卡上生成备份文件路径和名称从资源获取包含当前日期
File file = generateFileMountedOnSDcard(
mContext,
R.string.file_path, // 目录路径(如/sdcard/NotesBackup/
R.string.file_name_txt_format // 文件名格式如notes_%s.txt%s为日期
);
if (file == null) {
Log.e(TAG, "创建备份文件失败");
return null;
}
// 记录文件名和目录
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
// 创建输出流
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* SD
* @param context
* @param filePathResId IDR.string.file_path
* @param fileNameFormatResId IDR.string.file_name_txt_format
* @return null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
// 拼接路径SD卡根目录 + 应用备份目录(如/sdcard/NotesBackup/
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
// 拼接文件名:目录 + 格式化名称含当前日期如notes_20240520.txt
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(
context.getString(R.string.format_date_ymd), // 日期格式(年月日)
System.currentTimeMillis()
)
));
File file = new File(sb.toString());
// 创建目录和文件
try {
if (!filedir.exists()) {
filedir.mkdir(); // 目录不存在则创建
}
if (!file.exists()) {
file.createNewFile(); // 文件不存在则创建
}
return file;
} catch (SecurityException e) {
e.printStackTrace(); // 权限不足
} catch (IOException e) {
e.printStackTrace(); // 创建文件失败
}
return null;
}
}

@ -0,0 +1,381 @@
/*
* 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.
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
/**
* 便
* ContentProviderUI
*/
public class DataUtils {
public static final String TAG = "DataUtils";
/**
* 便
* @param resolver
* @param ids 便/ID
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "待删除的ID集合为null");
return true;
}
if (ids.size() == 0) {
Log.d(TAG, "待删除的ID集合为空");
return true;
}
// 创建批量删除操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 禁止删除系统根目录
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "不能删除系统根目录文件夹");
continue;
}
// 构建删除操作根据ID删除便签
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {
// 执行批量删除
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "删除失败ID集合:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("远程异常: %s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("操作应用异常: %s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
* 便
* @param resolver
* @param id 便ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId); // 更新父目录ID为目标文件夹
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 记录原始文件夹ID
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记本地已修改
// 执行更新操作
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
* 便
* @param resolver
* @param ids 便ID
* @param folderId ID
* @return truefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "待移动的ID集合为null");
return true;
}
// 创建批量更新操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 构建更新操作修改父目录ID
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记本地修改
operationList.add(builder.build());
}
try {
// 执行批量更新
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "移动失败ID集合:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("远程异常: %s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("操作应用异常: %s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
*
* @param resolver
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 查询非系统的文件夹且不在垃圾站中的文件夹数量
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" }, // 统计数量
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "获取文件夹数量失败:" + e.toString());
} finally {
cursor.close();
}
}
}
return count;
}
/**
* 便
* @param resolver
* @param noteId 便ID
* @param type 便Notes.TYPE_NOTE
* @return truefalse
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
exist = cursor.getCount() > 0; // 游标有数据则存在
cursor.close();
}
return exist;
}
/**
* 便便
* @param resolver
* @param noteId 便ID
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
exist = cursor.getCount() > 0;
cursor.close();
}
return exist;
}
/**
* 便
* @param resolver
* @param dataId ID
* @return truefalse
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
exist = cursor.getCount() > 0;
cursor.close();
}
return exist;
}
/**
*
* @param resolver
* @param name
* @return truefalse
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?", // SNIPPET字段存储文件夹名称
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
exist = cursor.getCount() > 0;
cursor.close();
}
return exist;
}
/**
* 便
* @param resolver
* @param folderId ID
* @return widgetIdwidgetType
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, // 查询小部件ID和类型
NoteColumns.PARENT_ID + "=?", // 筛选指定文件夹下的便签
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
/**
* 便ID便
* @param resolver
* @param noteId 便ID
* @return
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER }, // 查询电话号码字段
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", // 筛选通话便签数据
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "获取电话号码失败 " + e.toString());
} finally {
cursor.close();
}
}
return "";
}
/**
* 便ID
* @param resolver
* @param phoneNumber
* @param callDate
* @return 便ID0
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID }, // 查询关联的便签ID
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)", // 按通话时间和电话号码筛选(支持号码格式匹配)
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "获取通话便签ID失败 " + e.toString());
}
}
cursor.close();
}
return 0;
}
/**
* 便ID
* @param resolver
* @param noteId 便ID
* @return 便
*/
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET }, // 查询摘要字段
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("未找到ID为 " + noteId + " 的便签");
}
/**
*
* @param snippet
* @return
*/
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim(); // 去除首尾空格
int index = snippet.indexOf('\n'); // 查找第一个换行符
if (index != -1) {
snippet = snippet.substring(0, index); // 截取首行内容
}
}
return snippet;
}
}

@ -0,0 +1,169 @@
/*
* 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.
*/
package net.micode.notes.tool;
/**
* GTask便GTaskGoogle Tasks使
* JSON
*/
public class GTaskStringUtils {
// ========================== GTask JSON动作相关键名 ==========================
/** JSON中动作ID的键名用于标识同步操作的唯一ID如创建/更新任务的动作ID */
public final static String GTASK_JSON_ACTION_ID = "action_id";
/** JSON中动作列表的键名用于包装多个同步动作如批量创建/更新任务) */
public final static String GTASK_JSON_ACTION_LIST = "action_list";
/** JSON中动作类型的键名用于标识当前动作是创建/更新/移动等类型 */
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
/** 动作类型创建操作用于标识在GTask中创建新任务或文件夹 */
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
/** 动作类型获取全部数据用于请求GTask中的所有任务和文件夹数据 */
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
/** 动作类型:移动操作,用于标识将任务/文件夹移动到新位置 */
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
/** 动作类型更新操作用于标识修改GTask中已有的任务或文件夹 */
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// ========================== GTask JSON实体属性相关键名 ==========================
/** JSON中创建者ID的键名标识任务/文件夹的创建者(小米便签中暂用"null" */
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
/** JSON中子实体的键名用于存储子任务信息 */
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
/** JSON中客户端版本的键名标识当前同步客户端的版本 */
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
/** JSON中任务完成状态的键名标识任务是否已完成true/false */
public final static String GTASK_JSON_COMPLETED = "completed";
/** JSON中当前列表ID的键名标识当前操作的任务列表ID */
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
/** JSON中默认列表ID的键名标识GTask中的默认任务列表ID */
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
/** JSON中删除状态的键名标识任务/文件夹是否已删除true/false */
public final static String GTASK_JSON_DELETED = "deleted";
/** JSON中目标列表的键名用于移动操作时指定目标列表 */
public final static String GTASK_JSON_DEST_LIST = "dest_list";
/** JSON中目标父节点的键名用于移动操作时指定目标父节点 */
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
/** JSON中目标父节点类型的键名标识目标父节点是任务TASK还是文件夹GROUP */
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
/** JSON中实体增量的键名用于存储任务/文件夹的修改内容(如名称、状态等) */
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
/** JSON中实体类型的键名标识当前实体是任务TASK还是文件夹GROUP */
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
/** JSON中获取已删除实体的键名用于请求GTask中已删除的任务/文件夹 */
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
/** JSON中实体ID的键名标识任务/文件夹在GTask中的唯一ID */
public final static String GTASK_JSON_ID = "id";
/** JSON中排序索引的键名标识任务/文件夹在列表中的位置(用于排序) */
public final static String GTASK_JSON_INDEX = "index";
/** JSON中最后修改时间的键名标识任务/文件夹的最近修改时间(用于同步冲突判断) */
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
/** JSON中最新同步点的键名标识上次同步的时间点用于增量同步 */
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
/** JSON中列表ID的键名标识当前任务所属的列表ID */
public final static String GTASK_JSON_LIST_ID = "list_id";
/** JSON中列表集合的键名用于存储多个任务列表数据 */
public final static String GTASK_JSON_LISTS = "lists";
/** JSON中名称的键名标识任务/文件夹的名称 */
public final static String GTASK_JSON_NAME = "name";
/** JSON中新ID的键名用于标识创建操作后GTask返回的新实体ID */
public final static String GTASK_JSON_NEW_ID = "new_id";
/** JSON中备注的键名标识任务的备注内容 */
public final static String GTASK_JSON_NOTES = "notes";
/** JSON中父节点ID的键名标识当前实体的父节点ID用于层级结构 */
public final static String GTASK_JSON_PARENT_ID = "parent_id";
/** JSON中前序兄弟节点ID的键名标识当前实体的前一个兄弟节点用于排序 */
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
/** JSON中结果集合的键名用于存储GTask返回的同步结果 */
public final static String GTASK_JSON_RESULTS = "results";
/** JSON中源列表的键名用于移动操作时指定源列表 */
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
/** JSON中任务集合的键名用于存储多个任务数据 */
public final static String GTASK_JSON_TASKS = "tasks";
/** JSON中类型的键名与ENTITY_TYPE配合使用 */
public final static String GTASK_JSON_TYPE = "type";
/** 实体类型文件夹分组标识当前实体是GTask中的文件夹 */
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
/** 实体类型任务标识当前实体是GTask中的任务 */
public final static String GTASK_JSON_TYPE_TASK = "TASK";
/** JSON中用户信息的键名标识当前同步的用户 */
public final static String GTASK_JSON_USER = "user";
// ========================== 小米便签专属文件夹标识 ==========================
/** 小米便签文件夹前缀用于在GTask中区分小米便签创建的文件夹避免与其他应用冲突 */
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
/** 小米便签默认文件夹名称,拼接前缀后为"[MIUI_Notes]Default" */
public final static String FOLDER_DEFAULT = "Default";
/** 小米便签通话记录文件夹名称,拼接前缀后为"[MIUI_Notes]Call_Note" */
public final static String FOLDER_CALL_NOTE = "Call_Note";
/** 小米便签元数据文件夹名称用于存储便签的额外信息如本地ID、附件信息等 */
public final static String FOLDER_META = "METADATA";
// ========================== 元数据相关键名 ==========================
/** 元数据中GTask ID的键名用于存储实体在GTask中的ID */
public final static String META_HEAD_GTASK_ID = "meta_gid";
/** 元数据中便签信息的键名用于存储便签的核心信息如ID、类型等 */
public final static String META_HEAD_NOTE = "meta_note";
/** 元数据中数据列表的键名,用于存储便签的内容数据(如文本、附件路径等) */
public final static String META_HEAD_DATA = "meta_data";
/** 元数据便签的名称,标识该便签为元数据(禁止用户修改或删除) */
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}

@ -0,0 +1,272 @@
/*
* 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.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* 便
*
*/
public class ResourceParser {
// 背景颜色常量定义(用于标识不同颜色主题)
public static final int YELLOW = 0; // 黄色背景
public static final int BLUE = 1; // 蓝色背景
public static final int WHITE = 2; // 白色背景
public static final int GREEN = 3; // 绿色背景
public static final int RED = 4; // 红色背景
public static final int BG_DEFAULT_COLOR = YELLOW; // 默认背景颜色(黄色)
// 字体大小常量定义
public static final int TEXT_SMALL = 0; // 小字体
public static final int TEXT_MEDIUM = 1; // 中字体(默认)
public static final int TEXT_LARGE = 2; // 大字体
public static final int TEXT_SUPER = 3; // 超大字体
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; // 默认字体大小
/**
* 便
*
*/
public static class NoteBgResources {
// 编辑页主体背景资源数组(与颜色常量顺序对应)
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow, // 黄色背景
R.drawable.edit_blue, // 蓝色背景
R.drawable.edit_white, // 白色背景
R.drawable.edit_green, // 绿色背景
R.drawable.edit_red // 红色背景
};
// 编辑页标题栏背景资源数组(与颜色常量顺序对应)
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow, // 黄色标题栏
R.drawable.edit_title_blue, // 蓝色标题栏
R.drawable.edit_title_white, // 白色标题栏
R.drawable.edit_title_green, // 绿色标题栏
R.drawable.edit_title_red // 红色标题栏
};
/**
* 便
* @param id YELLOWBLUE
* @return ID
*/
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
/**
* 便
* @param id YELLOWBLUE
* @return ID
*/
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* ID
*
* @param context
* @return
*/
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 随机选择背景颜色0到背景资源数量-1之间
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
// 返回默认背景色
return BG_DEFAULT_COLOR;
}
}
/**
* 便
*
*/
public static class NoteItemBgResources {
// 列表首项背景资源数组
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up, // 黄色首项
R.drawable.list_blue_up, // 蓝色首项
R.drawable.list_white_up, // 白色首项
R.drawable.list_green_up, // 绿色首项
R.drawable.list_red_up // 红色首项
};
// 列表中间项背景资源数组
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle, // 黄色中间项
R.drawable.list_blue_middle, // 蓝色中间项
R.drawable.list_white_middle, // 白色中间项
R.drawable.list_green_middle, // 绿色中间项
R.drawable.list_red_middle // 红色中间项
};
// 列表末项背景资源数组
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down, // 黄色末项
R.drawable.list_blue_down, // 蓝色末项
R.drawable.list_white_down, // 白色末项
R.drawable.list_green_down, // 绿色末项
R.drawable.list_red_down, // 红色末项
};
// 列表唯一项(仅一项时)背景资源数组
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single, // 黄色唯一项
R.drawable.list_blue_single, // 蓝色唯一项
R.drawable.list_white_single, // 白色唯一项
R.drawable.list_green_single, // 绿色唯一项
R.drawable.list_red_single // 红色唯一项
};
/**
*
* @param id
* @return ID
*/
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
/**
*
* @param id
* @return ID
*/
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
/**
*
* @param id
* @return ID
*/
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
/**
*
* @param id
* @return ID
*/
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
/**
*
* @return ID
*/
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
* Widget
* Widget
*/
public static class WidgetBgResources {
// 2x尺寸Widget背景资源数组
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow, // 2x黄色Widget
R.drawable.widget_2x_blue, // 2x蓝色Widget
R.drawable.widget_2x_white, // 2x白色Widget
R.drawable.widget_2x_green, // 2x绿色Widget
R.drawable.widget_2x_red, // 2x红色Widget
};
/**
* 2xWidget
* @param id
* @return 2x WidgetID
*/
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
// 4x尺寸Widget背景资源数组
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow, // 4x黄色Widget
R.drawable.widget_4x_blue, // 4x蓝色Widget
R.drawable.widget_4x_white, // 4x白色Widget
R.drawable.widget_4x_green, // 4x绿色Widget
R.drawable.widget_4x_red // 4x红色Widget
};
/**
* 4xWidget
* @param id
* @return 4x WidgetID
*/
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
*
*
*/
public static class TextAppearanceResources {
// 文字样式资源数组(与字体大小常量顺序对应)
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal, // 小字体样式
R.style.TextAppearanceMedium, // 中字体样式
R.style.TextAppearanceLarge, // 大字体样式
R.style.TextAppearanceSuper // 超大字体样式
};
/**
*
* @param id TEXT_SMALLTEXT_MEDIUM
* @return IDid
*/
public static int getTexAppearanceResource(int id) {
/**
* ID
* id
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
/**
*
* @return
*/
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -0,0 +1,203 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/**
* 便便
*
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
// 提醒的便签ID
private long mNoteId;
// 便签内容摘要(用于提醒对话框展示)
private String mSnippet;
// 摘要最大显示长度(超出部分用省略号处理)
private static final int SNIPPET_PREW_MAX_LEN = 60;
// 媒体播放器,用于播放闹钟声音
MediaPlayer mPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去除标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window win = getWindow();
// 允许在锁屏时显示窗口
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕处于关闭状态,添加额外窗口标志:保持屏幕常亮、点亮屏幕、允许锁屏时显示、布局适配
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取启动当前Activity的Intent由AlarmReceiver传递
Intent intent = getIntent();
try {
// 从Intent的Data中解析便签IDData格式为content://.../notes/{id}取路径的第2段
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 通过便签ID获取内容摘要用于提醒展示
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 处理摘要长度:超过最大长度时截断并添加省略提示
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
// 初始化媒体播放器
mPlayer = new MediaPlayer();
// 检查便签是否存在且可见(未被删除)
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
// 显示提醒对话框
showActionDialog();
// 播放闹钟声音
playAlarmSound();
} else {
// 便签不存在则直接结束活动
finish();
}
}
/**
*
* @return truefalse
*/
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
/**
*
*/
private void playAlarmSound() {
// 获取系统默认闹钟铃声的Uri
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取系统静音模式影响的音频流(判断闹钟是否被静音)
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 设置媒体播放器的音频流类型:若闹钟流被静音模式影响,则使用该流;否则默认使用闹钟流
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
// 设置铃声数据源并准备播放
mPlayer.setDataSource(this, url);
mPlayer.prepare();
// 设置为循环播放(直到用户操作停止)
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException | SecurityException | IllegalStateException | IOException e) {
e.printStackTrace();
}
}
/**
* 便
*/
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
// 设置对话框标题为应用名称(小米便签)
dialog.setTitle(R.string.app_name);
// 显示便签摘要内容
dialog.setMessage(mSnippet);
// 确定按钮:关闭提醒(默认行为)
dialog.setPositiveButton(R.string.notealert_ok, this);
// 若屏幕亮着,添加“进入”按钮(跳转到便签编辑页)
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框,并设置消失监听(用于停止铃声)
dialog.show().setOnDismissListener(this);
}
/**
*
* @param dialog
* @param which BUTTON_POSITIVE/BUTTON_NEGATIVE
*/
public void onClick(DialogInterface dialog, int which) {
switch (which) {
// 点击“进入”按钮:跳转到便签编辑页查看完整内容
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW); // 动作:查看便签
intent.putExtra(Intent.EXTRA_UID, mNoteId); // 传递便签ID
startActivity(intent);
break;
// 点击“确定”按钮:默认不做额外操作,对话框消失后会停止铃声并结束活动
default:
break;
}
}
/**
*
* @param dialog
*/
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
/**
*
*/
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop(); // 停止播放
mPlayer.release(); // 释放资源
mPlayer = null;
}
}
}

@ -0,0 +1,100 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 便
* 便
* 便
*/
public class AlarmInitReceiver extends BroadcastReceiver {
/**
* 便ID
*
*/
private static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 便签ID
NoteColumns.ALERTED_DATE // 提醒时间戳
};
// 字段索引常量用于快速访问Cursor中的数据
private static final int COLUMN_ID = 0; // 便签ID在投影中的索引
private static final int COLUMN_ALERTED_DATE = 1; // 提醒时间在投影中的索引
/**
* 广
* 便AlarmManager
* @param context
* @param intent 广
*/
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前系统时间,用于筛选未过期的提醒
long currentDate = System.currentTimeMillis();
// 查询数据库中所有未过期的便签提醒
// 条件:提醒时间 > 当前时间 且 便签类型为普通便签TYPE_NOTE
Cursor c = context.getContentResolver().query(
Notes.CONTENT_NOTE_URI, // 查询的内容URI指向便签表
PROJECTION, // 需要查询的字段
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) }, // 筛选条件参数(当前时间)
null // 排序方式(无需排序)
);
if (c != null) {
// 遍历查询结果,为每个未过期的提醒注册闹钟
if (c.moveToFirst()) {
do {
// 获取便签的提醒时间
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建触发提醒时的意图指向AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
// 为意图附加便签ID便于后续定位具体便签
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(COLUMN_ID)));
// 创建延迟执行的PendingIntent用于触发提醒
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, sender, 0);
// 获取AlarmManager服务用于设置系统闹钟
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 注册闹钟在指定时间alertDate唤醒设备并发送广播
// RTC_WAKEUP模式确保在设备休眠时也能触发
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext()); // 继续处理下一个便签
}
// 关闭Cursor释放资源
c.close();
}
}
}

@ -0,0 +1,46 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* 便广广
* 便
*/
public class AlarmReceiver extends BroadcastReceiver {
/**
* 广
* @param context Activity
* @param intent 便便ID
*/
@Override
public void onReceive(Context context, Intent intent) {
// 将意图的目标组件设置为AlarmAlertActivity便签提醒展示界面
// 确保广播中携带的便签信息如Data中的便签ID被传递给目标界面
intent.setClass(context, AlarmAlertActivity.class);
// 添加FLAG_ACTIVITY_NEW_TASK标志在BroadcastReceiver中启动Activity必须设置此标志
// 因为BroadcastReceiver本身没有任务栈需要为启动的Activity创建新任务栈
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动AlarmAlertActivity展示便签提醒对话框并播放铃声
context.startActivity(intent);
}
}

@ -0,0 +1,576 @@
/*
* 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.
*/
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
/**
*
* 便12/24
*/
public class DateTimePicker extends FrameLayout {
// 默认启用状态
private static final boolean DEFAULT_ENABLE_STATE = true;
// 时间计算常量定义
private static final int HOURS_IN_HALF_DAY = 12; // 半天的小时数
private static final int HOURS_IN_ALL_DAY = 24; // 一天的小时数
private static final int DAYS_IN_ALL_WEEK = 7; // 一周的天数
private static final int DATE_SPINNER_MIN_VAL = 0; // 日期选择器最小值
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; // 日期选择器最大值
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; // 24小时制小时最小值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; // 24小时制小时最大值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; // 12小时制小时最小值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; // 12小时制小时最大值
private static final int MINUT_SPINNER_MIN_VAL = 0; // 分钟选择器最小值
private static final int MINUT_SPINNER_MAX_VAL = 59; // 分钟选择器最大值
private static final int AMPM_SPINNER_MIN_VAL = 0; // 上下午选择器最小值
private static final int AMPM_SPINNER_MAX_VAL = 1; // 上下午选择器最大值
// 选择器组件
private final NumberPicker mDateSpinner; // 日期选择器(显示近一周日期)
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // 上下午选择器12小时制用
private Calendar mDate; // 日期时间计算工具
// 日期显示值数组(存储近一周的日期文本)
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 时间状态变量
private boolean mIsAm; // 是否为上午12小时制
private boolean mIs24HourView; // 是否为24小时制显示
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 组件是否启用
private boolean mInitialising; // 是否处于初始化状态
// 日期时间变更监听器
private OnDateTimeChangedListener mOnDateTimeChangedListener;
/**
*
*
*/
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 计算日期偏移量并更新
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};
/**
*
* 23012/24
*/
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
// 12小时制处理跨天情况如11PM到12AM需加一天12AM到11PM需减一天
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
// 12点和11点切换时切换上下午状态
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
// 24小时制处理跨天情况如23点到0点加一天0点到23点减一天
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 更新小时并触发事件
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
// 若日期变更,同步更新年月日
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
/**
*
* 5901
*/
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
// 判断是否跨小时59->0加1小时0->59减1小时
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
if (offset != 0) {
// 调整小时并更新相关控件
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
// 更新上下午状态12小时制
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
} else {
mIsAm = true;
}
updateAmPmControl();
}
// 更新分钟并触发事件
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
/**
*
* /12
*/
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
// 切换上下午时调整小时上午转下午加12小时反之减12小时
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
/**
*
*
*/
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
/**
* 使使12/24
* @param context
*/
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
/**
* 使使12/24
* @param context
* @param date
*/
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
/**
* 使
* @param context
* @param date
* @param is24HourView 使24
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 初始化上下午状态
// 加载布局
inflate(context, R.layout.datetime_picker, this);
// 初始化日期选择器
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 初始化分钟选择器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 长按更新间隔(毫秒)
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 初始化上下午选择器使用系统本地化的AM/PM文本
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// 更新控件到初始状态
updateDateControl();
updateHourControl();
updateAmPmControl();
// 设置时间格式12/24小时制
set24HourView(is24HourView);
// 设置初始时间
setCurrentDate(date);
// 设置启用状态
setEnabled(isEnabled());
// 初始化完成
mInitialising = false;
}
/**
*
* @param enabled
*/
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
/**
*
* @return
*/
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
*
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
*
* @param date
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
*
* @param year
* @param month 0-11
* @param dayOfMonth
* @param hourOfDay 240-23
* @param minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
*
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
*
* @param year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* 0-11
* @return
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
*
* @param month 0-11
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
*
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
*
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* 240-23
* @return
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
* 12/24
* @return
*/
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
// 12小时制转换13->10->12
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* 24
* @param hourOfDay 0-23
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
// 12小时制处理更新上下午状态和小时显示值
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
*
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
*
* @param minute 0-59
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* 24
* @return 24
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* 24/12
* @param is24HourView true24false12
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
// 24小时制隐藏上下午选择器12小时制显示
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
/**
* .
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 计算起始日期当前日期减去3天用于显示近一周
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
// 生成近一周的日期文本
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 默认选中当前日期
mDateSpinner.invalidate();
}
/**
* 12
*/
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
/**
* 12/24
*/
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
*
* @param callback
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
/**
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,137 @@
/*
* 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.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
/**
* 便
* AlertDialogDateTimePicker
*/
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// 用于维护当前选中的日期时间
private Calendar mDate = Calendar.getInstance();
// 是否采用24小时制显示时间
private boolean mIs24HourView;
// 日期时间选择完成后的回调监听器由调用者实现如NoteEditActivity
private OnDateTimeSetListener mOnDateTimeSetListener;
// 自定义的日期时间选择器视图,负责用户选择交互
private DateTimePicker mDateTimePicker;
/**
*
* "确定"
*/
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
/**
*
* @param context NoteEditActivity
* @param date 便
*/
public DateTimePickerDialog(Context context, long date) {
super(context);
// 初始化自定义的日期时间选择器
mDateTimePicker = new DateTimePicker(context);
// 将选择器设置为对话框的内容视图
setView(mDateTimePicker);
// 为日期时间选择器设置变化监听器:当用户选择的日期或时间变化时触发
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新内部维护的日期时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
// 根据新选择的时间更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
// 设置初始日期时间(忽略秒数,确保提醒时间精确到分钟)
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框按钮:"确定"按钮触发onClick方法"取消"按钮不执行操作
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统设置初始化24小时制显示模式
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 初始化对话框标题为初始日期时间
updateTitle(mDate.getTimeInMillis());
}
/**
* 24/12
* @param is24HourView true24false12
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
/**
*
* @param callBack OnDateTimeSetListenerActivity
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
/**
*
* @param date
*/
private void updateTitle(long date) {
// 定义日期时间的显示格式:包含年、月、日、时间
int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME;
// 根据24小时制模式调整显示格式
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR;
// 格式化时间并设置为对话框标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
/**
* "确定"
*
*/
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
// 回调传递选中的时间(毫秒级时间戳)
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,92 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
/**
* 便
*
*/
public class DropdownMenu {
// 触发下拉菜单的按钮
private Button mButton;
// 系统提供的弹出式菜单组件,负责显示下拉选项
private PopupMenu mPopupMenu;
// 菜单容器,存储从资源文件加载的菜单项
private Menu mMenu;
/**
*
* @param context 访
* @param button
* @param menuId IDres/menu
*/
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
// 设置按钮背景为下拉图标(箭头图标)
mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 初始化PopupMenu绑定到触发按钮
mPopupMenu = new PopupMenu(context, mButton);
// 获取菜单容器
mMenu = mPopupMenu.getMenu();
// 从资源文件加载菜单项到菜单容器
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 设置按钮点击事件:点击时显示下拉菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
/**
*
* @param listener
*/
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
/**
* ID
* @param id ID
* @return MenuItem
*/
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
/**
*
* @param title "已选择3项"
*/
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,130 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
* 便便
* CursorAdapter
*/
public class FoldersListAdapter extends CursorAdapter {
/**
*
* IDSNIPPET
*/
public static final String [] PROJECTION = {
NoteColumns.ID, // 文件夹ID
NoteColumns.SNIPPET // 文件夹名称(小米便签中文件夹名称存储在此字段)
};
/**
* Cursor
*/
public static final int ID_COLUMN = 0; // PROJECTION中ID字段的索引
public static final int NAME_COLUMN = 1; // PROJECTION中SNIPPET名称字段的索引
/**
*
* @param context Activity
* @param c
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
/**
*
* @param context
* @param cursor 使
* @param parent ListView
* @return FolderListItem
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
/**
*
* @param view FolderListItem
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 处理根文件夹的特殊名称根文件夹ID为Notes.ID_ROOT_FOLDER显示为“移动到父文件夹”
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
// 将文件夹名称绑定到列表项视图
((FolderListItem) view).bind(folderName);
}
}
/**
*
* @param context
* @param position
* @return
*/
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
// 根文件夹特殊处理其他文件夹直接取SNIPPET字段值
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
/**
* LinearLayout
*/
private class FolderListItem extends LinearLayout {
private TextView mName; // 用于显示文件夹名称的文本控件
/**
*
* @param context
*/
public FolderListItem(Context context) {
super(context);
// 加载文件夹列表项的布局文件folder_list_item.xml
inflate(context, R.layout.folder_list_item, this);
// 获取布局中的文本控件,用于显示文件夹名称
mName = (TextView) findViewById(R.id.tv_folder_name);
}
/**
*
* @param name
*/
public void bind(String name) {
mName.setText(name);
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,288 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
* 便EditText便
*
*/
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
// 当前输入框在多输入框列表中的索引(用于区分不同输入框)
private int mIndex;
// 记录删除键按下前的光标起始位置(用于判断是否删除当前输入框)
private int mSelectionStartBeforeDelete;
// 支持的链接协议常量
private static final String SCHEME_TEL = "tel:" ; // 电话协议(如"tel:10086"
private static final String SCHEME_HTTP = "http:" ; // 网页协议(如"http://www.micode.net"
private static final String SCHEME_EMAIL = "mailto:" ; // 邮件协议(如"mailto:test@micode.net"
// 链接协议与对应菜单资源的映射(用于上下文菜单显示对应操作文字)
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接对应"拨打"菜单
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网页链接对应"打开网页"菜单
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接对应"发送邮件"菜单
}
/**
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener {
/**
*
* @param index
* @param text
*/
void onEditTextDelete(int index, String text);
/**
*
* @param index +1
* @param text
*/
void onEditTextEnter(int index, String text);
/**
*
* @param index
* @param hasText /
*/
void onTextChange(int index, boolean hasText);
}
// 文本变化监听器实例由NoteEditActivity设置
private OnTextViewChangeListener mOnTextViewChangeListener;
/**
*
* @param context NoteEditActivity
*/
public NoteEditText(Context context) {
super(context, null);
mIndex = 0; // 默认索引为0
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
* NoteEditActivity
* @param listener OnTextViewChangeListener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
/**
* +
* @param context
* @param attrs
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
* ++
* @param context
* @param attrs
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/**
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸点在文本中的实际坐标(扣除内边距并加上滚动偏移)
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
// 根据坐标获取对应的文本行和偏移量,设置光标位置
Layout layout = getLayout();
int line = layout.getLineForVertical(y); // 触摸点所在的文本行
int off = layout.getOffsetForHorizontal(line, x); // 行内的水平偏移(光标位置)
Selection.setSelection(getText(), off); // 设置光标
break;
}
return super.onTouchEvent(event);
}
/**
*
* @param keyCode KEYCODE_DELKEYCODE_ENTER
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 回车键事件由onKeyUp处理此处返回false让事件继续传递
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除键按下前的光标起始位置,用于判断是否需要删除输入框
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
// 若光标在起始位置且不是第一个输入框,通知删除当前输入框
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
// 处理回车键:将光标后的文本移至新输入框
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString(); // 光标后文本
setText(getText().subSequence(0, selectionStart)); // 保留光标前文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); // 通知新增输入框
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 失去焦点且文本为空时,通知无文本;否则通知有文本
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
// 仅处理富文本中的链接
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 获取选中区域内的链接
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) { // 仅处理单个链接的情况
int defaultResId = 0;
// 识别链接协议,匹配对应的菜单资源
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
// 未匹配到已知协议时,使用默认菜单文本
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
// 向菜单添加链接操作项,点击后触发链接的默认行为(如拨号、打开浏览器)
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
urls[0].onClick(NoteEditText.this); // 触发URLSpan的点击事件
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,319 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
/**
* 便便UI
*/
public class NoteItemData {
// 数据库查询的列名数组,对应便签表的核心字段
static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 便签ID
NoteColumns.ALERTED_DATE, // 提醒时间
NoteColumns.BG_COLOR_ID, // 背景色ID
NoteColumns.CREATED_DATE, // 创建时间
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改时间
NoteColumns.NOTES_COUNT, // 文件夹包含的便签数量(仅文件夹类型有效)
NoteColumns.PARENT_ID, // 父文件夹ID
NoteColumns.SNIPPET, // 内容摘要(文件夹名或便签内容片段)
NoteColumns.TYPE, // 类型(便签/文件夹/系统文件夹)
NoteColumns.WIDGET_ID, // 桌面插件ID
NoteColumns.WIDGET_TYPE, // 桌面插件类型
};
// 上述PROJECTION数组中各列的索引常量用于从Cursor中提取数据
private static final int ID_COLUMN = 0; // 便签ID索引
private static final int ALERTED_DATE_COLUMN = 1; // 提醒时间索引
private static final int BG_COLOR_ID_COLUMN = 2; // 背景色ID索引
private static final int CREATED_DATE_COLUMN = 3; // 创建时间索引
private static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件索引
private static final int MODIFIED_DATE_COLUMN = 5; // 修改时间索引
private static final int NOTES_COUNT_COLUMN = 6; // 文件夹便签数量索引
private static final int PARENT_ID_COLUMN = 7; // 父文件夹ID索引
private static final int SNIPPET_COLUMN = 8; // 内容摘要索引
private static final int TYPE_COLUMN = 9; // 类型索引
private static final int WIDGET_ID_COLUMN = 10; // 桌面插件ID索引
private static final int WIDGET_TYPE_COLUMN = 11; // 桌面插件类型索引
// 便签核心数据字段
private long mId; // 便签唯一ID
private long mAlertDate; // 提醒时间(毫秒时间戳)
private int mBgColorId; // 背景色ID对应资源文件
private long mCreatedDate; // 创建时间(毫秒时间戳)
private boolean mHasAttachment; // 是否有附件(如图片等)
private long mModifiedDate; // 最后修改时间(毫秒时间戳)
private int mNotesCount; // 文件夹包含的便签数量(仅文件夹类型使用)
private long mParentId; // 所属文件夹ID
private String mSnippet; // 内容摘要(文件夹名或便签内容的简短展示)
private int mType; // 类型Notes.TYPE_NOTE便签TYPE_FOLDER文件夹等
private int mWidgetId; // 关联的桌面插件ID无则为无效值
private int mWidgetType; // 桌面插件类型(无则为无效值)
private String mName; // 通话记录便签的联系人姓名(非通话记录则为空)
private String mPhoneNumber; // 通话记录便签的电话号码(非通话记录则为空)
// 列表位置属性用于UI背景样式适配
private boolean mIsLastItem; // 是否为列表最后一项
private boolean mIsFirstItem; // 是否为列表第一项
private boolean mIsOnlyOneItem; // 是否为列表中唯一一项
private boolean mIsOneNoteFollowingFolder; // 是否为文件夹后的唯一便签项
private boolean mIsMultiNotesFollowingFolder; // 是否为文件夹后的多个便签项之一
/**
* 便
* @param context
* @param cursor 便
*/
public NoteItemData(Context context, Cursor cursor) {
// 从游标中提取核心数据字段
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 移除检查列表标记(用于纯文本展示)
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 处理通话记录便签:提取电话号码并关联联系人姓名
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { // 若属于通话记录文件夹
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber); // 获取联系人姓名
if (mName == null) {
mName = mPhoneNumber; // 无联系人时显示电话号码
}
}
}
if (mName == null) {
mName = "";
}
// 计算当前项在列表中的位置属性
checkPostion(cursor);
}
/**
* UI
* @param cursor
*/
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false; // 是否为最后一项
mIsFirstItem = cursor.isFirst() ? true : false; // 是否为第一项
mIsOnlyOneItem = (cursor.getCount() == 1); // 是否为唯一一项
// 初始化文件夹后便签的位置属性
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
// 若当前是便签项且不是第一项,检查前一项是否为文件夹
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) { // 移动到前一项
// 前一项是文件夹或系统文件夹
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
// 若游标总数大于当前位置+1说明文件夹后有多个便签
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
// 否则文件夹后只有当前一个便签
mIsOneNoteFollowingFolder = true;
}
}
// 移回原位置,避免游标错乱
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
/**
* 便
*/
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
/**
* 便
*/
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
/**
*
*/
public boolean isLast() {
return mIsLastItem;
}
/**
* 便
*/
public String getCallName() {
return mName;
}
/**
*
*/
public boolean isFirst() {
return mIsFirstItem;
}
/**
*
*/
public boolean isSingle() {
return mIsOnlyOneItem;
}
/**
* 便ID
*/
public long getId() {
return mId;
}
/**
*
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
*/
public long getCreatedDate() {
return mCreatedDate;
}
/**
*
*/
public boolean hasAttachment() {
return mHasAttachment;
}
/**
*
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
*/
public long getParentId() {
return mParentId;
}
/**
* 便
*/
public int getNotesCount() {
return mNotesCount;
}
/**
* IDgetParentId
*/
public long getFolderId () {
return mParentId;
}
/**
* 便//
*/
public int getType() {
return mType;
}
/**
*
*/
public int getWidgetType() {
return mWidgetType;
}
/**
* ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
* 便
*/
public String getSnippet() {
return mSnippet;
}
/**
* >0
*/
public boolean hasAlert() {
return (mAlertDate > 0);
}
/**
* 便
*/
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* 便
* @param cursor
* @return 便Notes.TYPE_NOTE
*/
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,259 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
* 便便
* CursorAdapterCursor便
*/
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
private Context mContext; // 上下文对象
private HashMap<Integer, Boolean> mSelectedIndex; // 存储选中项的位置和状态
private int mNotesCount; // 便签总数(不包含文件夹)
private boolean mChoiceMode; // 是否处于选择模式
/**
*
*
*/
public static class AppWidgetAttribute {
public int widgetId; // 插件ID
public int widgetType; // 插件类型
};
/**
*
* @param context
*/
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
/**
*
* @param context
* @param cursor
* @param parent
* @return NotesListItem
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/**
*
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
// 从游标中解析便签数据
NoteItemData itemData = new NoteItemData(context, cursor);
// 绑定数据到视图,设置选择模式和选中状态
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
/**
*
* @param position
* @param checked
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged(); // 通知列表刷新
}
/**
*
* @return
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
* @param mode
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); // 清除选中状态
mChoiceMode = mode;
}
/**
* 便便
* @param checked
*/
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
// 仅处理类型为普通便签的项
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
/**
* ID
* @return IDHashSet
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
// 过滤根文件夹ID不应被选中
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
/**
*
* @return HashSet
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
// 注意:此处不关闭游标,游标由适配器管理
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/**
*
* @return
*/
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
/**
* 便
* @return
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
* @param position
* @return
*/
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
/**
* 便
*/
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
/**
* 便
* @param cursor
*/
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
/**
* 便
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
// 仅统计类型为普通便签的项
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,169 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
* 便
* 便
*/
public class NotesListItem extends LinearLayout {
private ImageView mAlert; // 提醒图标(显示闹钟或通话记录图标)
private TextView mTitle; // 标题/内容文本(显示便签内容、文件夹名称等)
private TextView mTime; // 时间文本(显示最后修改时间)
private TextView mCallName; // 通话记录联系人名称(仅通话记录便签显示)
private NoteItemData mItemData; // 当前项对应的数据模型
private CheckBox mCheckBox; // 多选模式下的复选框
/**
*
* @param context
*/
public NotesListItem(Context context) {
super(context);
// 加载列表项布局文件
inflate(context, R.layout.note_item, this);
// 绑定UI控件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
/**
*
* @param context
* @param data 便
* @param choiceMode
* @param checked
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 处理多选模式:仅普通便签显示复选框并同步选中状态
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
// 处理通话记录文件夹(特殊系统文件夹)
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 显示文件夹名称和包含的便签数量
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record); // 显示通话记录图标
// 处理通话记录文件夹下的便签
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName()); // 显示联系人名称
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 显示便签内容片段
// 显示提醒图标(如有)
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
// 处理普通文件夹和普通便签
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 普通文件夹:显示名称和包含的便签数量
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
// 普通便签:显示内容片段和提醒状态
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
// 显示最后修改时间(相对时间格式,如"1小时前"
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置项背景样式
setBackground(data);
}
/**
* 便
* @param data 便
*/
private void setBackground(NoteItemData data) {
int bgColorId = data.getBgColorId(); // 获取背景色ID
// 普通便签:根据位置(首项/末项/单项等)设置不同背景
if (data.getType() == Notes.TYPE_NOTE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
// 单项或文件夹后的唯一便签
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(bgColorId));
} else if (data.isLast()) {
// 列表中的最后一项
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(bgColorId));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 首项或文件夹后的多个便签中的第一项
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(bgColorId));
} else {
// 中间项
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(bgColorId));
}
} else {
// 文件夹:使用固定的文件夹背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/**
*
* @return 便
*/
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,463 @@
/*
* 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.
*/
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
* 便
* 便
*/
public class NotesPreferenceActivity extends PreferenceActivity {
// 偏好设置存储名称
public static final String PREFERENCE_NAME = "notes_preferences";
// 同步账号名称的偏好键
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 最后同步时间的偏好键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 背景颜色设置的偏好键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 同步账号设置分类的键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 账号权限过滤键
private static final String AUTHORITIES_FILTER_KEY = "authorities";
// 账号设置分类
private PreferenceCategory mAccountCategory;
// 同步服务广播接收器
private GTaskReceiver mReceiver;
// 原始账号列表(用于检测新添加的账号)
private Account[] mOriAccounts;
// 是否添加了新账号的标记
private boolean mHasAddedAccount;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 设置ActionBar支持返回导航
getActionBar().setDisplayHomeAsUpEnabled(true);
// 加载偏好设置布局
addPreferencesFromResource(R.xml.preferences);
// 获取账号设置分类
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 初始化广播接收器并注册,监听同步服务广播
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
// 初始化账号列表和头部视图
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// 如果检测到新添加的账号,自动设置为同步账号
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
// 刷新UI显示
refreshUI();
}
@Override
protected void onDestroy() {
// 注销广播接收器
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置账号项点击事件
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// 首次设置账号,显示账号选择对话框
showSelectAccountAlertDialog();
} else {
// 已设置账号,显示账号变更确认对话框
showChangeAccountConfirmAlertDialog();
}
} else {
// 同步中不允许变更账号
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
mAccountCategory.addPreference(accountPref);
}
/**
*
*/
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 设置同步按钮状态
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 取消同步
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 立即同步
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
// 未设置账号时禁用同步按钮
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 设置最后同步时间显示
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
/**
* UI
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
*
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 自定义对话框标题
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
// 获取谷歌账号列表
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
// 构建账号选择列表
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
// 设置单选列表点击事件
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
}
});
}
// 添加"添加账号"选项
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
// 跳转到添加账号设置页面
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
}
/**
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 自定义对话框标题
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
dialogBuilder.setCustomTitle(titleView);
// 对话框选项:变更账号、移除账号、取消
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 变更账号:显示账号选择对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
// 移除账号
removeSyncAccount();
refreshUI();
}
}
});
dialogBuilder.show();
}
/**
*
* @return
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* @param account
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
editor.commit();
// 清除最后同步时间
setLastSyncTime(this, 0);
// 清除本地GTASK相关信息异步执行
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
editor.commit();
// 清除本地GTASK相关信息异步执行
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
/**
*
* @param context
* @return
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
* @param context
* @param time
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
}
/**
*
* @param context
* @return 0
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/**
* 广
* UI
*/
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 刷新UI显示同步状态
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
/**
*
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// 返回便签列表页面
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return false;
}
}
}

@ -0,0 +1,187 @@
/*
* 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.
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
/**
* 便
*
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 查询便签数据时使用的字段投影
public static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 便签ID
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.SNIPPET // 便签内容摘要
};
// 投影字段对应的索引值
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
private static final String TAG = "NoteWidgetProvider";
/**
*
*
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
// 将小部件ID设为无效值
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
// 更新数据库中关联该小部件的便签记录
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
/**
* 便
* @param context
* @param widgetId ID
* @return 便
*/
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
// 查询条件小部件ID匹配且不在回收站中
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
/**
*
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
/**
* UI
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
* @param privacyMode
*/
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
// 只处理有效的小部件ID
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
// 默认背景颜色ID
int bgId = ResourceParser.getDefaultBgId(context);
// 便签内容摘要
String snippet = "";
// 点击小部件时的跳转意图
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
// 查询与当前小部件关联的便签数据
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
// 检查是否有多个便签关联到同一小部件(异常情况)
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
// 从游标中获取数据
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW); // 查看已有便签
} else {
// 无关联便签时显示默认文本
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 新建便签
}
// 关闭游标释放资源
if (c != null) {
c.close();
}
// 加载小部件布局
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置背景图片
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
// 创建点击事件的PendingIntent
PendingIntent pendingIntent = null;
if (privacyMode) {
// 隐私模式下显示提示文本,点击跳转至便签列表
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
// 正常模式下显示便签内容,点击跳转至编辑页
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
// 设置点击事件
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
/**
* ID
*
*/
protected abstract int getBgResourceId(int bgId);
/**
* ID
*
*/
protected abstract int getLayoutId();
/**
*
* 2x4x
*/
protected abstract int getWidgetType();
}

@ -0,0 +1,76 @@
/*
* 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.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 2x便
* NoteWidgetProvider2x
*/
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
/**
*
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法执行通用更新逻辑如查询数据、绑定事件
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 2xID
* UI
* @return 2xIDR.layout.widget_2x
*/
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
/**
* 2xID
* 便IDbgId2x
* @param bgId 便ID
* @return 2xID
*/
@Override
protected int getBgResourceId(int bgId) {
// 通过资源解析工具获取2x尺寸的背景资源
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
/**
* 2x
*
* @return 2xNotes.TYPE_WIDGET_2X
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,72 @@
/*
* 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.
*/
package net.micode.notes.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 便4xNoteWidgetProvider4x
*/
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
/**
*
* update便
* @param context 访
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 4xID
* widget_4x.xmlUI
* @return 4xID
*/
protected int getLayoutId() {
return R.layout.widget_4x;
}
/**
* IDbgId4x
* ResourceParser便
* @param bgId ID
* @return 4xID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
/**
* 4x
* NotesTYPE_WIDGET_4X
* @return 4x
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;
}
}

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

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

Loading…
Cancel
Save