main
kissnow 6 months ago
parent 282330c5a0
commit 8dc5118e5c

@ -0,0 +1 @@
Subproject commit 282330c5a09a3b019edecd939abc76391beb9c3f

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

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="selectedTabId" value="Android Vitals" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

@ -0,0 +1,65 @@
plugins {
alias(libs.plugins.android.application)
}
android {
packaging {
resources.excludes.add("META-INF/DEPENDENCIES");
resources.excludes.add("META-INF/NOTICE");
resources.excludes.add("META-INF/LICENSE");
resources.excludes.add("META-INF/LICENSE.txt");
resources.excludes.add("META-INF/NOTICE.txt");
}
namespace = "net.micode.notes"
compileSdk = 35
defaultConfig {
applicationId = "net.micode.notes"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.constraintlayout)
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
/*implementation(fileTree(mapOf(
"dir" to "D:\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib",
"include" to listOf("*.aar", "*.jar"),
"exclude" to listOf()
)))*/
implementation(files("D:\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-osgi-4.5.14.jar"))
implementation(files("D:\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-win-4.5.14.jar"))
implementation(files("D:\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpcore-4.4.16.jar"))
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

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

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

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 声明XML版本和编码格式 -->
<!-- 权限声明区域 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- 创建桌面快捷方式权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 网络访问权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 读取联系人权限(需运行时申请) -->
<!-- 账户管理相关权限 -->
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 开机启动广播接收 -->
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notesmaster"
tools:targetApi="31"> <!-- 指定目标 API 级别 -->
<!-- 主启动 Activity -->
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan"
android:exported="true"> <!-- 允许外部应用启动 -->
<intent-filter>
<!-- 主入口点声明 -->
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 笔记编辑 Activity -->
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:exported="true"><!-- 注意:导出需验证 intent 数据 -->
<!-- 支持通过 VIEW 操作打开特定数据类型 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<!-- 支持插入或编辑操作 -->
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<!-- 搜索功能支持 -->
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- 搜索功能元数据 -->
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<!-- 数据提供者 -->
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" /><!-- 支持多进程访问 -->
<!-- 2x2 桌面小部件 -->
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2"
android:exported="true"><!-- 导出需确保无安全漏洞 -->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
<!-- <activity-->
<!-- android:name=".MainActivity"-->
<!-- android:exported="true">-->
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
<!-- </activity>-->
</application>
</manifest>

@ -0,0 +1,44 @@
package net.micode.notes;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import net.micode.notes.databinding.FragmentFirstBinding;
public class FirstFragment extends Fragment {
private FragmentFirstBinding binding;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {
binding = FragmentFirstBinding.inflate(inflater, container, false);
return binding.getRoot();
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.buttonFirst.setOnClickListener(v ->
NavHostFragment.findNavController(FirstFragment.this)
.navigate(R.id.action_FirstFragment_to_SecondFragment)
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

@ -0,0 +1,77 @@
package net.micode.notes;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import net.micode.notes.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
binding.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAnchorView(R.id.fab)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, appBarConfiguration)
|| super.onSupportNavigateUp();
}
}

@ -0,0 +1,44 @@
package net.micode.notes;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import net.micode.notes.databinding.FragmentSecondBinding;
public class SecondFragment extends Fragment {
private FragmentSecondBinding binding;
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState
) {
binding = FragmentSecondBinding.inflate(inflater, container, false);
return binding.getRoot();
}
public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.buttonSecond.setOnClickListener(v ->
NavHostFragment.findNavController(SecondFragment.this)
.navigate(R.id.action_SecondFragment_to_FirstFragment)
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

@ -0,0 +1,74 @@
/*
* 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; // 导入Context类用于访问应用程序环境
import android.database.Cursor; // 导入Cursor类用于数据库查询结果的操作
import android.provider.ContactsContract.CommonDataKinds.Phone; // 导入电话相关的常量
import android.provider.ContactsContract.Data; // 导入数据相关的常量
import android.telephony.PhoneNumberUtils; // 导入电话号工具类
import android.util.Log; // 导入日志工具类
import java.util.HashMap; // 导入HashMap类用于存储联系人缓存
public class Contact { // 定义Contact类
private static HashMap<String, String> sContactCache; //静态哈希表,用于缓存联系人信息
private static final String TAG = "Contact"; // 日志标签
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 = '+')"; // 查询条件,用于匹配来电号码
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,105 @@
/*
* 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类
public class Notes { // 定义Notes类
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; // 系统类型常量
public static final int ID_ROOT_FOLDER = 0; // 默认文件夹
public static final int ID_TEMPARAY_FOLDER = -1; // 不属于文件夹的笔记
public static final int ID_CALL_RECORD_FOLDER = -2; // 用于存储通话记录,便于返回
public static final int ID_TRASH_FOLER = -3; // 垃圾站
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
public static final int TYPE_WIDGET_INVALIDE = -1; // 定义无效小部件类型
public static final int TYPE_WIDGET_2X = 0; // 定义2x小部件类型
public static final int TYPE_WIDGET_4X = 1; // 定义4x小部件类型
public static class DataConstants { // 定义数据常量类
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记内容类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录
}
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); // 定义查询所有笔记和文件夹的Uri
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); // 定义查询数据的Uri
public interface NoteColumns { // 定义笔记接口
public static final String ID = "_id"; // ID
public static final String PARENT_ID = "parent_id"; // 父级ID
public static final String CREATED_DATE = "created_date"; // 创建日期
public static final String MODIFIED_DATE = "modified_date"; // 最后修改日期
public static final String ALERTED_DATE = "alert_date"; // 提醒日期
public static final String SNIPPET = "snippet"; // 文件夹名称或笔记内容
public static final String WIDGET_ID = "widget_id"; // 小部件ID
public static final String WIDGET_TYPE = "widget_type"; // 小部件类型列
public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色ID
public static final String HAS_ATTACHMENT = "has_attachment"; // 附件
public static final String NOTES_COUNT = "notes_count"; // 文件夹中的笔记数量
public static final String TYPE = "type"; // 文件类型:文件夹或笔记
public static final String SYNC_ID = "sync_id"; // 最后同步ID
public static final String LOCAL_MODIFIED = "local_modified"; // 本地修改标签
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 移动前ID
public static final String GTASK_ID = "gtask_id"; // gtask ID
public static final String VERSION = "version"; // 版本号
}
public interface DataColumns { // 数据接口
public static final String ID = "_id"; // ID
public static final String MIME_TYPE = "mime_type"; //MIME类型是一种标准用于标识文档、文件或字节流的性质和格式。在数据库中这个字段可以用来识别不同类型的数据例如文本、图片、音频或视频等。
public static final String NOTE_ID = "note_id"; // 归属note的ID
public static final String CREATED_DATE = "created_date"; // 创建日期
public static final String MODIFIED_DATE = "modified_date"; // 最后修改日期
public static final String CONTENT = "content"; // 数据内容
// 以下5个是通用数据列它们的具体意义取决于MIME类型由MIME_TYPE字段指定
// 不同的MIME类型可能需要存储不同类型的数据这5个字段提供了灵活性允许根据MIME类型来存储相应的数据。
public static final String DATA1 = "data1";
public static final String DATA2 = "data2";
public static final String DATA3 = "data3";
public static final String DATA4 = "data4";
public static final String DATA5 = "data5";
}
public static final class TextNote implements DataColumns { // 定义文本笔记类
public static final String MODE = DATA1; // 模式
public static final int MODE_CHECK_LIST = 1; // 检查列表模式
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 定义了MIME类型用于标识文本标签的目录
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 定义了MIME类型用于标识文本标签的单个项
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 文本笔记的Uri
}
public static final class CallNote implements DataColumns { // 定义通话记录类
public static final String CALL_DATE = DATA1; // 通话日期
public static final String PHONE_NUMBER = DATA3; // 电话号码
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 定义了MIME类型用于标识文本标签的目录
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 定义了MIME类型用于标识文本标签的单个项
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 通话笔记的Uri用于访问通话记录数据
}
}

@ -0,0 +1,333 @@
/*
* 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; // 导入SQLite数据库类
import android.database.sqlite.SQLiteOpenHelper; // 导入SQLite数据库帮助类
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; // 导入笔记列
public class NotesDatabaseHelper extends SQLiteOpenHelper { //笔记数据库帮助类
private static final String DB_NAME = "note.db"; // 数据库名称
private static final int DB_VERSION = 4; // 数据库版本
public interface TABLE { // 表名
public static final String NOTE = "note"; // 笔记
public static final String DATA = "data"; // 数据
}
private static final String TAG = "NotesDatabaseHelper"; // 日志标签
private static NotesDatabaseHelper mInstance;
private static final String CREATE_NOTE_TABLE_SQL = // 创建笔记表的SQL语句
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 笔记ID
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父级ID
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 ''," + // GTASK ID
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本
")";
private static final String CREATE_DATA_TABLE_SQL = // 创建数据表的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
")";
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = // 创建数据表笔记ID索引的SQL语句
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 在数据表上创建索引
// 移动笔记到文件夹时增加文件夹的笔记数量
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";
// 从文件夹移动笔记时减少文件夹的笔记数量
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";
// 向文件夹插入新笔记时增加文件夹的笔记数量
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";
// 从文件夹删除笔记时减少文件夹的笔记数量
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";
// 插入类型为{@link DataConstants#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";
// 当类型为{@link DataConstants#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";
// 当类型为{@link DataConstants#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";
// 删除已删除笔记所关联的数据
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";
// 删除已删除文件夹所关联的笔记
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";
// 将移动到垃圾箱文件夹的文件夹内的笔记移动
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";
public NotesDatabaseHelper(Context context) { // 构造函数 实现NotesDatabaseHelper实例的唯一性以及同步性
super(context, DB_NAME, null, DB_VERSION); // 调用父类构造函数
}
public void createNoteTable(SQLiteDatabase db) { // 创建笔记表
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL
reCreateNoteTableTriggers(db); // 重新创建笔记表触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 日志输出
}
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); // 创建触发器
}
private void createSystemFolder(SQLiteDatabase db) { // 创建系统文件夹
ContentValues values = new ContentValues(); // 创建内容值对象
// 为通话记录创建文件夹
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型
db.insert(TABLE.NOTE, null, values); // 插入文件夹
// 根文件夹,默认文件夹
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型
db.insert(TABLE.NOTE, null, values); // 插入文件夹
// 临时文件夹,用于移动笔记
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型
db.insert(TABLE.NOTE, null, values); // 插入文件夹
// 创建垃圾箱文件夹
values.clear(); // 清空内容值
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型
db.insert(TABLE.NOTE, null, values); // 插入文件夹
}
public void createDataTable(SQLiteDatabase db) { // 创建数据表
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL
reCreateDataTableTriggers(db); // 重新创建数据表触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表笔记ID索引
Log.d(TAG, "data table has been created"); // 日志输出
}
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); // 创建触发器
}
static synchronized NotesDatabaseHelper getInstance(Context context) { // 获取单例实例
if (mInstance == null) { // 如果实例为空
mInstance = new NotesDatabaseHelper(context); // 创建新实例
}
return mInstance; // 返回实例
}
@Override
public void onCreate(SQLiteDatabase db) { // 创建数据库时调用
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 升级数据库时调用
boolean reCreateTriggers = false; // 触发器重建标记
boolean skipV2 = false; // 跳过V2标记
if (oldVersion == 1) { // 如果旧版本为1
upgradeToV2(db); // 升级到V2
skipV2 = true; // 设置跳过V2标记
oldVersion++; // 版本加1
}
if (oldVersion == 2 && !skipV2) { // 如果旧版本为2且未跳过V2
upgradeToV3(db); // 升级到V3
reCreateTriggers = true; // 设置触发器重建标记
oldVersion++; // 版本加1
}
if (oldVersion == 3) { // 如果旧版本为3
upgradeToV4(db); // 升级到V4
oldVersion++; // 版本加1
}
if (reCreateTriggers) { // 如果需要重建触发器
reCreateNoteTableTriggers(db); // 重新创建笔记表触发器
reCreateDataTableTriggers(db); // 重新创建数据表触发器
}
if (oldVersion != newVersion) { // 如果旧版本与新版本不一致
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails"); // 抛出异常
}
}
private void upgradeToV2(SQLiteDatabase db) { // 升级到V2
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 删除笔记表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); // 删除数据表
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
private void upgradeToV3(SQLiteDatabase db) { // 升级到V3
// drop unused triggers
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"); // 删除未使用的触发器
// add a column for gtask id
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); // 为笔记表添加GTASK ID列
// add a trash system folder
ContentValues values = new ContentValues(); // 创建内容值对象
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型
db.insert(TABLE.NOTE, null, values); // 插入垃圾箱文件夹
}
private void upgradeToV4(SQLiteDatabase db) { // 升级到V4
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 为笔记表添加版本列
}
}

@ -0,0 +1,302 @@
/*
* 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; // 导入内容URI处理类
import android.content.ContentValues; // 导入内容值类
import android.content.Intent; // 导入意图类
import android.content.UriMatcher; // 导入URI匹配器类
import android.database.Cursor; // 导入游标类
import android.database.sqlite.SQLiteDatabase; // 导入SQLite数据库类
import android.net.Uri; // 导入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; // 导入数据库表类
public class NotesProvider extends ContentProvider { // 笔记内容提供者类
private static final UriMatcher mMatcher; // URI匹配器实例
private NotesDatabaseHelper mHelper; // 数据库助手实例
private static final String TAG = "NotesProvider"; // 日志标签
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; // 搜索建议URI常量
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 初始化URI匹配器
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 添加笔记URI
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 添加笔记项URI
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 添加数据URI
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 添加数据项URI
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 添加搜索URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 添加搜索建议URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 添加搜索建议URI
}
/**
* x'0A' sqlite '\n'
* '\n'
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," // 笔记ID
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 笔记ID作为额外数据
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 修剪后的摘要作为文本1
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 修剪后的摘要作为文本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; // 意图数据类型
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; // 笔记类型条件
@Override
public boolean onCreate() { // 创建内容提供者
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库助手实例
return true; // 返回成功
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, // 查询方法
String sortOrder) {
Cursor c = null; // 游标初始化
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null; // ID初始化
switch (mMatcher.match(uri)) { // 根据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); // 获取笔记ID
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); // 获取数据ID
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: // 未知URI处理
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c != null) { // 如果游标不为空
c.setNotificationUri(getContext().getContentResolver(), uri); // 设置通知URI
}
return c; // 返回游标
}
@Override
public Uri insert(Uri uri, ContentValues values) { // 插入方法
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
long dataId = 0, noteId = 0, insertedId = 0; // ID初始化
switch (mMatcher.match(uri)) { // 根据URI匹配
case URI_NOTE: // 插入笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 插入笔记表
break;
case URI_DATA: // 插入数据
if (values.containsKey(DataColumns.NOTE_ID)) { // 如果包含笔记ID
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取笔记ID
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString()); // 记录错误日志
}
insertedId = dataId = db.insert(TABLE.DATA, null, values); // 插入数据表
break;
default: // 未知URI处理
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 通知笔记URI
if (noteId > 0) {
getContext().getContentResolver().notifyChange( // 通知内容解析器
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 通知数据URI
if (dataId > 0) {
getContext().getContentResolver().notifyChange( // 通知内容解析器
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId); // 返回插入的URI
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除方法
int count = 0; // 删除计数
String id = null; // ID初始化
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean deleteData = false; // 数据删除标志
switch (mMatcher.match(uri)) { // 根据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
/**
* ID 0
*/
long noteId = Long.valueOf(id); // 转换ID
if (noteId <= 0) { // 如果ID小于等于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); // 获取数据ID
count = db.delete(TABLE.DATA, // 删除特定数据
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true; // 设置数据删除标志
break;
default: // 未知URI处理
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) { // 如果删除成功
if (deleteData) { // 如果删除的是数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI
}
return count; // 返回删除计数
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新方法
int count = 0; // 更新计数
String id = null; // ID初始化
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库
boolean updateData = false; // 数据更新标志
switch (mMatcher.match(uri)) { // 根据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); // 获取笔记ID
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); // 获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id // 更新特定数据
+ parseSelection(selection), selectionArgs);
updateData = true; // 设置数据更新标志
break;
default: // 未知URI处理
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) { // 如果更新成功
if (updateData) { // 如果更新的是数据
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI
}
getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI
}
return count; // 返回更新计数
}
private String parseSelection(String selection) { // 解析选择条件
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); // 返回条件
}
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { // 增加笔记版本
StringBuilder sql = new StringBuilder(120); // SQL语句构建器
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)) { // 如果有ID或选择条件
sql.append(" WHERE "); // 添加WHERE条件
}
if (id > 0) { // 如果有ID
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // 添加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()); // 执行SQL语句
}
@Override
public String getType(Uri uri) { // 获取类型方法
// TODO Auto-generated method stub
return null; // 返回空
}
}

@ -0,0 +1,82 @@
/*
* 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; // 导入Cursor类用于数据库操作
import android.util.Log; // 导入Log类用于日志记录
import net.micode.notes.tool.GTaskStringUtils; // 导入工具类以处理字符串
import org.json.JSONException; // 导入JSONException类用于处理JSON异常
import org.json.JSONObject; // 导入JSONObject类用于处理JSON对象
public class MetaData extends Task { // MetaData类继承自Task类
private final static String TAG = MetaData.class.getSimpleName(); // 定义日志标签
private String mRelatedGid = null; // 存储相关的GID
public void setMeta(String gid, JSONObject metaInfo) { // 设置元数据的方法
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 将GID放入metaInfo中
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, "failed to put related gid"); // 记录错误日志
}
setNotes(metaInfo.toString()); // 将metaInfo转换为字符串并设置为笔记内容
setName(GTaskStringUtils.META_NOTE_NAME); // 设置笔记名称
}
public String getRelatedGid() { // 获取相关GID的方法
return mRelatedGid; // 返回相关GID
}
@Override
public boolean isWorthSaving() { // 判断是否值得保存的方法
return getNotes() != null; // 如果笔记内容不为空则返回true
}
@Override
public void setContentByRemoteJSON(JSONObject js) { // 从远程JSON设置内容的方法
super.setContentByRemoteJSON(js); // 调用父类的方法
if (getNotes() != null) { // 如果笔记内容不为空
try {
JSONObject metaInfo = new JSONObject(getNotes().trim()); // 创建新的JSON对象
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 获取相关GID
} catch (JSONException e) { // 捕获JSON异常
Log.w(TAG, "failed to get related gid"); // 记录警告日志
mRelatedGid = null; // 将相关GID设置为null
}
}
}
@Override
public void setContentByLocalJSON(JSONObject js) { // 从本地JSON设置内容的方法
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); // 抛出非法访问错误
}
@Override
public JSONObject getLocalJSONFromContent() { // 从内容获取本地JSON的方法
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); // 抛出非法访问错误
}
@Override
public int getSyncAction(Cursor c) { // 获取同步操作的方法
throw new IllegalAccessError("MetaData:getSyncAction should not be called"); // 抛出非法访问错误
}
}

@ -0,0 +1,101 @@
/*
* 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; // 导入Cursor类以处理数据库查询结果
import org.json.JSONObject; // 导入JSONObject类以处理JSON数据
public abstract class Node { // 定义一个抽象类Node
public static final int SYNC_ACTION_NONE = 0; // 定义同步操作类型:无操作
public static final int SYNC_ACTION_ADD_REMOTE = 1; // 定义同步操作类型:远程添加
public static final int SYNC_ACTION_ADD_LOCAL = 2; // 定义同步操作类型:本地添加
public static final int SYNC_ACTION_DEL_REMOTE = 3; // 定义同步操作类型:远程删除
public static final int SYNC_ACTION_DEL_LOCAL = 4; // 定义同步操作类型:本地删除
public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 定义同步操作类型:远程更新
public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 定义同步操作类型:本地更新
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 定义同步操作类型:更新冲突
public static final int SYNC_ACTION_ERROR = 8; // 定义同步操作类型:错误
private String mGid; // 存储全局唯一标识符
private String mName; // 存储节点名称
private long mLastModified; // 存储最后修改时间
private boolean mDeleted; // 存储删除状态
public Node() { // 构造函数
mGid = null; // 初始化全局唯一标识符为null
mName = ""; // 初始化节点名称为空字符串
mLastModified = 0; // 初始化最后修改时间为0
mDeleted = false; // 初始化删除状态为false
}
public abstract JSONObject getCreateAction(int actionId); // 抽象方法获取创建操作的JSON对象
public abstract JSONObject getUpdateAction(int actionId); // 抽象方法获取更新操作的JSON对象
public abstract void setContentByRemoteJSON(JSONObject js); // 抽象方法通过远程JSON设置内容
public abstract void setContentByLocalJSON(JSONObject js); // 抽象方法通过本地JSON设置内容
public abstract JSONObject getLocalJSONFromContent(); // 抽象方法从内容获取本地JSON对象
public abstract int getSyncAction(Cursor c); // 抽象方法从Cursor获取同步操作类型
public void setGid(String gid) { // 设置全局唯一标识符
this.mGid = gid; // 将传入的gid赋值给mGid
}
public void setName(String name) { // 设置节点名称
this.mName = name; // 将传入的name赋值给mName
}
public void setLastModified(long lastModified) { // 设置最后修改时间
this.mLastModified = lastModified; // 将传入的lastModified赋值给mLastModified
}
public void setDeleted(boolean deleted) { // 设置删除状态
this.mDeleted = deleted; // 将传入的deleted赋值给mDeleted
}
public String getGid() { // 获取全局唯一标识符
return this.mGid; // 返回mGid
}
public String getName() { // 获取节点名称
return this.mName; // 返回mName
}
public long getLastModified() { // 获取最后修改时间
return this.mLastModified; // 返回mLastModified
}
public boolean getDeleted() { // 获取删除状态
return this.mDeleted; // 返回mDeleted
}
}

@ -0,0 +1,189 @@
/*
* 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; // 包名表示该类属于gtask数据模块
import android.content.ContentResolver; // 导入ContentResolver类用于处理内容提供者的操作
import android.content.ContentUris; // 导入ContentUris类用于处理内容URI
import android.content.ContentValues; // 导入ContentValues类用于存储内容数据
import android.content.Context; // 导入Context类用于获取应用环境
import android.database.Cursor; // 导入Cursor类用于数据库查询结果的操作
import android.net.Uri; // 导入Uri类用于处理URI
import android.util.Log; // 导入Log类用于日志记录
import net.micode.notes.data.Notes; // 导入Notes类表示笔记数据模型
import net.micode.notes.data.Notes.DataColumns; // 导入DataColumns类表示数据列常量
import net.micode.notes.data.Notes.DataConstants; // 导入DataConstants类表示数据常量
import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类表示笔记列常量
import net.micode.notes.data.NotesDatabaseHelper.TABLE; // 导入TABLE类表示数据库表常量
import net.micode.notes.gtask.exception.ActionFailureException; // 导入自定义异常类
import org.json.JSONException; // 导入JSONException类用于处理JSON异常
import org.json.JSONObject; // 导入JSONObject类用于处理JSON对象
public class SqlData { // SqlData类处理与数据库相关的数据操作
private static final String TAG = SqlData.class.getSimpleName(); // 日志标签
private static final int INVALID_ID = -99999; // 无效ID常量
public static final String[] PROJECTION_DATA = new String[] { // 数据列投影
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
public static final int DATA_ID_COLUMN = 0; // 数据ID列索引
public static final int DATA_MIME_TYPE_COLUMN = 1; // 数据MIME类型列索引
public static final int DATA_CONTENT_COLUMN = 2; // 数据内容列索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3; // 数据内容数据1列索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4; // 数据内容数据3列索引
private ContentResolver mContentResolver; // 内容解析器,用于与内容提供者交互
private boolean mIsCreate; // 标识是否为新建数据
private long mDataId; // 数据ID
private String mDataMimeType; // 数据MIME类型
private String mDataContent; // 数据内容
private long mDataContentData1; // 数据内容数据1
private String mDataContentData3; // 数据内容数据3
private ContentValues mDiffDataValues; // 存储差异数据的ContentValues
public SqlData(Context context) { // 构造函数,初始化新数据
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = true; // 设置为创建状态
mDataId = INVALID_ID; // 初始化数据ID为无效ID
mDataMimeType = DataConstants.NOTE; // 设置默认MIME类型为笔记
mDataContent = ""; // 初始化数据内容为空
mDataContentData1 = 0; // 初始化数据内容数据1为0
mDataContentData3 = ""; // 初始化数据内容数据3为空
mDiffDataValues = new ContentValues(); // 初始化差异数据
}
public SqlData(Context context, Cursor c) { // 构造函数从Cursor加载数据
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 设置为非创建状态
loadFromCursor(c); // 从Cursor加载数据
mDiffDataValues = new ContentValues(); // 初始化差异数据
}
private void loadFromCursor(Cursor c) { // 从Cursor加载数据
mDataId = c.getLong(DATA_ID_COLUMN); // 获取数据ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 获取数据MIME类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 获取数据内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 获取数据内容数据1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 获取数据内容数据3
}
public void setContent(JSONObject js) throws JSONException { // 设置内容从JSON对象加载数据
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; // 获取数据ID
if (mIsCreate || mDataId != dataId) { // 如果是创建状态或ID不匹配
mDiffDataValues.put(DataColumns.ID, dataId); // 存储数据ID
}
mDataId = dataId; // 更新数据ID
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE; // 获取数据MIME类型
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { // 如果是创建状态或MIME类型不匹配
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 存储数据MIME类型
}
mDataMimeType = dataMimeType; // 更新数据MIME类型
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; // 获取数据内容数据1
if (mIsCreate || mDataContentData1 != dataContentData1) { // 如果是创建状态或数据1不匹配
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 存储数据内容数据1
}
mDataContentData1 = dataContentData1; // 更新数据内容数据1
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; // 获取数据内容数据3
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { // 如果是创建状态或数据3不匹配
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 存储数据内容数据3
}
mDataContentData3 = dataContentData3; // 更新数据内容数据3
}
public JSONObject getContent() throws JSONException { // 获取内容返回JSON对象
if (mIsCreate) { // 如果是创建状态
Log.e(TAG, "it seems that we haven't created this in database yet"); // 记录错误日志
return null; // 返回null
}
JSONObject js = new JSONObject(); // 创建JSON对象
js.put(DataColumns.ID, mDataId); // 存储数据ID
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 存储数据MIME类型
js.put(DataColumns.CONTENT, mDataContent); // 存储数据内容
js.put(DataColumns.DATA1, mDataContentData1); // 存储数据内容数据1
js.put(DataColumns.DATA3, mDataContentData3); // 存储数据内容数据3
return js; // 返回JSON对象
}
public void commit(long noteId, boolean validateVersion, long version) { // 提交数据到数据库
if (mIsCreate) { // 如果是创建状态
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { // 如果ID无效且包含ID
mDiffDataValues.remove(DataColumns.ID); // 移除ID
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 存储笔记ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新插入数据的ID
} 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; // 设置为非创建状态
}
public long getId() { // 获取数据ID
return mDataId; // 返回数据ID
}
}

@ -0,0 +1,503 @@
/*
* 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; // 导入AppWidgetManager类用于管理小部件
import android.content.ContentResolver; // 导入ContentResolver类用于访问内容提供者
import android.content.ContentValues; // 导入ContentValues类用于存储内容值
import android.content.Context; // 导入Context类提供应用环境信息
import android.database.Cursor; // 导入Cursor类用于数据库查询结果
import android.net.Uri; // 导入Uri类用于处理URI
import android.util.Log; // 导入Log类用于日志记录
import net.micode.notes.data.Notes; // 导入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; // 导入JSONArray类用于处理JSON数组
import org.json.JSONException; // 导入JSONException类处理JSON异常
import org.json.JSONObject; // 导入JSONObject类用于处理JSON对象
import java.util.ArrayList; // 导入ArrayList类用于动态数组
public class SqlNote { // SqlNote类表示一个笔记对象
private static final String TAG = SqlNote.class.getSimpleName(); // 日志标签
private static final int INVALID_ID = -99999; // 无效ID常量
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
};
public static final int ID_COLUMN = 0; // ID列索引
public static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列索引
public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引
public static final int CREATED_DATE_COLUMN = 3; // 创建日期列索引
public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列索引
public static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列索引
public static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列索引
public static final int PARENT_ID_COLUMN = 7; // 父ID列索引
public static final int SNIPPET_COLUMN = 8; // 摘要列索引
public static final int TYPE_COLUMN = 9; // 类型列索引
public static final int WIDGET_ID_COLUMN = 10; // 小部件ID列索引
public static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列索引
public static final int SYNC_ID_COLUMN = 12; // 同步ID列索引
public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改标记列索引
public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始父ID列索引
public static final int GTASK_ID_COLUMN = 15; // GTASK ID列索引
public static final int VERSION_COLUMN = 16; // 版本列索引
private Context mContext; // 上下文对象
private ContentResolver mContentResolver; // 内容解析器
private boolean mIsCreate; // 是否为新建标记
private long mId; // 笔记ID
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建日期
private int mHasAttachment; // 附件标记
private long mModifiedDate; // 修改日期
private long mParentId; // 父ID
private String mSnippet; // 摘要
private int mType; // 笔记类型
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mOriginParent; // 原始父ID
private long mVersion; // 版本号
private ContentValues mDiffNoteValues; // 存储笔记的差异值
private ArrayList<SqlData> mDataList; // 笔记数据列表
public SqlNote(Context context) { // 构造函数,创建新笔记
mContext = context; // 初始化上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = true; // 设置为新建状态
mId = INVALID_ID; // 设置无效ID
mAlertDate = 0; // 初始化提醒日期
mBgColorId = ResourceParser.getDefaultBgId(context); // 获取默认背景颜色ID
mCreatedDate = System.currentTimeMillis(); // 获取当前时间作为创建日期
mHasAttachment = 0; // 初始化附件标记
mModifiedDate = System.currentTimeMillis(); // 获取当前时间作为修改日期
mParentId = 0; // 初始化父ID
mSnippet = ""; // 初始化摘要
mType = Notes.TYPE_NOTE; // 设置笔记类型为普通笔记
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 设置小部件ID为无效值
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 设置小部件类型为无效值
mOriginParent = 0; // 初始化原始父ID
mVersion = 0; // 初始化版本号
mDiffNoteValues = new ContentValues(); // 初始化差异值存储
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
}
public SqlNote(Context context, Cursor c) { // 构造函数从Cursor加载笔记
mContext = context; // 初始化上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 设置为非新建状态
loadFromCursor(c); // 从Cursor加载数据
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果是笔记类型
loadDataContent(); // 加载数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值存储
}
public SqlNote(Context context, long id) { // 构造函数通过ID加载笔记
mContext = context; // 初始化上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 设置为非新建状态
loadFromCursor(id); // 从ID加载数据
mDataList = new ArrayList<SqlData>(); // 初始化数据列表
if (mType == Notes.TYPE_NOTE) // 如果是笔记类型
loadDataContent(); // 加载数据内容
mDiffNoteValues = new ContentValues(); // 初始化差异值存储
}
private void loadFromCursor(long id) { // 从Cursor加载数据
Cursor c = null; // 初始化Cursor
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", // 查询笔记数据
new String[] {
String.valueOf(id) // 通过ID查询
}, null);
if (c != null) { // 如果Cursor不为空
c.moveToNext(); // 移动到下一行
loadFromCursor(c); // 从Cursor加载数据
} else {
Log.w(TAG, "loadFromCursor: cursor = null"); // 日志警告Cursor为空
}
} finally {
if (c != null) // 如果Cursor不为空
c.close(); // 关闭Cursor
}
}
private void loadFromCursor(Cursor c) { // 从Cursor加载数据
mId = c.getLong(ID_COLUMN); // 获取ID
mAlertDate = c.getLong(ALERTED_DATE_COLUMN); // 获取提醒日期
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); // 获取背景颜色ID
mCreatedDate = c.getLong(CREATED_DATE_COLUMN); // 获取创建日期
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); // 获取附件标记
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); // 获取修改日期
mParentId = c.getLong(PARENT_ID_COLUMN); // 获取父ID
mSnippet = c.getString(SNIPPET_COLUMN); // 获取摘要
mType = c.getInt(TYPE_COLUMN); // 获取笔记类型
mWidgetId = c.getInt(WIDGET_ID_COLUMN); // 获取小部件ID
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); // 获取小部件类型
mVersion = c.getLong(VERSION_COLUMN); // 获取版本号
}
private void loadDataContent() { // 加载笔记的数据内容
Cursor c = null; // 初始化Cursor
mDataList.clear(); // 清空数据列表
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, // 查询数据内容
"(note_id=?)", new String[] {
String.valueOf(mId) // 通过笔记ID查询
}, null);
if (c != null) { // 如果Cursor不为空
if (c.getCount() == 0) { // 如果没有数据
Log.w(TAG, "it seems that the note has not data"); // 日志警告,笔记没有数据
return; // 返回
}
while (c.moveToNext()) { // 遍历Cursor
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到数据列表
}
} else {
Log.w(TAG, "loadDataContent: cursor = null"); // 日志警告Cursor为空
}
} finally {
if (c != null) // 如果Cursor不为空
c.close(); // 关闭Cursor
}
}
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) { // 如果是文件夹
// 对于文件夹,只能更新摘要和类型
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; // 获取ID
if (mIsCreate || mId != id) { // 如果是新建或ID不同
mDiffNoteValues.put(NoteColumns.ID, id); // 更新ID
}
mId = id; // 设置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 // 获取背景颜色ID
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) { // 如果是新建或背景颜色ID不同
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); // 更新背景颜色ID
}
mBgColorId = bgColorId; // 设置背景颜色ID
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 // 获取父ID
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) { // 如果是新建或父ID不同
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); // 更新父ID
}
mParentId = parentId; // 设置父ID
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) // 获取小部件ID
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) { // 如果是新建或小部件ID不同
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); // 更新小部件ID
}
mWidgetId = widgetId; // 设置小部件ID
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 // 获取原始父ID
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) { // 如果是新建或原始父ID不同
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); // 更新原始父ID
}
mOriginParent = originParent; // 设置原始父ID
for (int i = 0; i < dataArray.length(); i++) { // 遍历数据数组
JSONObject data = dataArray.getJSONObject(i); // 获取数据对象
SqlData sqlData = null; // 初始化SqlData对象
if (data.has(DataColumns.ID)) { // 如果数据对象有ID
long dataId = data.getLong(DataColumns.ID); // 获取数据ID
for (SqlData temp : mDataList) { // 遍历数据列表
if (dataId == temp.getId()) { // 如果找到匹配的ID
sqlData = temp; // 设置SqlData对象
}
}
}
if (sqlData == null) { // 如果没有找到匹配的SqlData
sqlData = new SqlData(mContext); // 创建新的SqlData对象
mDataList.add(sqlData); // 添加到数据列表
}
sqlData.setContent(data); // 设置SqlData内容
}
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
return false; // 返回失败
}
return true; // 返回成功
}
public JSONObject getContent() { // 获取笔记内容
try {
JSONObject js = new JSONObject(); // 创建JSON对象
if (mIsCreate) { // 如果是新建状态
Log.e(TAG, "it seems that we haven't created this in database yet"); // 日志错误,尚未在数据库中创建
return null; // 返回空
}
JSONObject note = new JSONObject(); // 创建笔记JSON对象
if (mType == Notes.TYPE_NOTE) { // 如果是笔记类型
note.put(NoteColumns.ID, mId); // 添加ID
note.put(NoteColumns.ALERTED_DATE, mAlertDate); // 添加提醒日期
note.put(NoteColumns.BG_COLOR_ID, mBgColorId); // 添加背景颜色ID
note.put(NoteColumns.CREATED_DATE, mCreatedDate); // 添加创建日期
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); // 添加附件标记
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); // 添加修改日期
note.put(NoteColumns.PARENT_ID, mParentId); // 添加父ID
note.put(NoteColumns.SNIPPET, mSnippet); // 添加摘要
note.put(NoteColumns.TYPE, mType); // 添加笔记类型
note.put(NoteColumns.WIDGET_ID, mWidgetId); // 添加小部件ID
note.put(NoteColumns.WIDGET_TYPE, mWidgetType); // 添加小部件类型
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); // 添加原始父ID
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将笔记对象添加到JSON
JSONArray dataArray = new JSONArray(); // 创建数据数组
for (SqlData sqlData : mDataList) { // 遍历数据列表
JSONObject data = sqlData.getContent(); // 获取SqlData内容
if (data != null) { // 如果内容不为空
dataArray.put(data); // 添加到数据数组
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将数据数组添加到JSON
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { // 如果是文件夹或系统类型
note.put(NoteColumns.ID, mId); // 添加ID
note.put(NoteColumns.TYPE, mType); // 添加类型
note.put(NoteColumns.SNIPPET, mSnippet); // 添加摘要
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将笔记对象添加到JSON
}
return js; // 返回JSON对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
}
return null; // 返回空
}
public void setParentId(long id) { // 设置父ID
mParentId = id; // 更新父ID
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 更新差异值
}
public void setGtaskId(String gid) { // 设置GTask ID
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 更新差异值
}
public void setSyncId(long syncId) { // 设置同步ID
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 更新差异值
}
public void resetLocalModified() { // 重置本地修改标记
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 更新差异值
}
public long getId() { // 获取ID
return mId; // 返回ID
}
public long getParentId() { // 获取父ID
return mParentId; // 返回父ID
}
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)) { // 如果ID无效且差异值中包含ID
mDiffNoteValues.remove(NoteColumns.ID); // 移除ID
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入新笔记
try {
mId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新笔记的ID
} catch (NumberFormatException e) { // 捕获数字格式异常
Log.e(TAG, "Get note id error :" + e.toString()); // 记录错误日志
throw new ActionFailureException("create note failed"); // 抛出创建失败异常
}
if (mId == 0) { // 如果ID为0
throw new IllegalStateException("Create thread id failed"); // 抛出非法状态异常
}
if (mType == Notes.TYPE_NOTE) { // 如果是笔记类型
for (SqlData sqlData : mDataList) { // 遍历数据列表
sqlData.commit(mId, false, -1); // 提交SqlData
}
}
} else { // 如果不是新建状态
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { // 如果ID无效
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) // 通过ID更新
});
} else { // 如果需要验证版本
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" // 更新笔记
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion) // 通过ID和版本更新
});
}
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); // 提交SqlData
}
}
}
// 刷新本地信息
loadFromCursor(mId); // 通过ID加载数据
if (mType == Notes.TYPE_NOTE) // 如果是笔记类型
loadDataContent(); // 加载数据内容
mDiffNoteValues.clear(); // 清空差异值
mIsCreate = false; // 设置为非新建状态
}
}

@ -0,0 +1,351 @@
/*
* 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;
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName(); // 任务类的标签,用于日志记录
private boolean mCompleted; // 任务是否完成的标志
private String mNotes; // 任务的备注信息
private JSONObject mMetaInfo; // 任务的元信息
private Task mPriorSibling; // 任务的前一个兄弟任务
private TaskList mParent; // 任务的父任务列表
public Task() {
super(); // 调用父类构造函数
mCompleted = false; // 初始化任务为未完成
mNotes = null; // 初始化备注为空
mPriorSibling = null; // 初始化前一个兄弟任务为空
mParent = null; // 初始化父任务列表为空
mMetaInfo = null; // 初始化元信息为空
}
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象
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); // 设置操作ID
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); // 获取任务在父任务列表中的索引
// entity_delta
JSONObject entity = new JSONObject(); // 创建一个实体的JSON对象
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 设置创建者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); // 将实体信息添加到JSON对象中
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); // 设置父任务ID
// 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()); // 设置任务列表ID
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); // 如果有前一个兄弟任务添加其ID
}
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("fail to generate task-create jsonobject"); // 抛出创建任务JSON对象失败的异常
}
return js; // 返回创建操作的JSON对象
}
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象
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
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 设置任务ID
// entity_delta
JSONObject entity = new JSONObject(); // 创建一个实体的JSON对象
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); // 将实体信息添加到JSON对象中
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("fail to generate task-update jsonobject"); // 抛出更新任务JSON对象失败的异常
}
return js; // 返回更新操作的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)); // 设置任务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"); // 抛出从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(); // 打印堆栈跟踪
}
}
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; // 返回null
}
JSONObject js = new JSONObject(); // 创建一个新的JSON对象
JSONObject note = new JSONObject(); // 创建备注的JSON对象
JSONArray dataArray = new JSONArray(); // 创建数据数组
JSONObject data = new JSONObject(); // 创建数据对象
data.put(DataColumns.CONTENT, name); // 设置数据内容为任务名称
dataArray.put(data); // 将数据对象添加到数据数组
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将数据数组添加到JSON对象
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 设置备注类型为笔记
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将备注信息添加到JSON对象
return js; // 返回JSON对象
} 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; // 返回null
}
}
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; // 设置元信息为null
}
}
}
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; // 返回错误操作
}
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0); // 判断任务是否值得保存
}
public void setCompleted(boolean completed) {
this.mCompleted = completed; // 设置任务完成状态
}
public void setNotes(String notes) {
this.mNotes = notes; // 设置任务备注信息
}
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling; // 设置前一个兄弟任务
}
public void setParent(TaskList parent) {
this.mParent = parent; // 设置父任务列表
}
public boolean getCompleted() {
return this.mCompleted; // 获取任务完成状态
}
public String getNotes() {
return this.mNotes; // 获取任务备注信息
}
public Task getPriorSibling() {
return this.mPriorSibling; // 获取前一个兄弟任务
}
public TaskList getParent() {
return this.mParent; // 获取父任务列表
}
}

@ -0,0 +1,342 @@
/*
* 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; // 导入Cursor类
import android.util.Log; // 导入Log类
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类
import net.micode.notes.gtask.exception.ActionFailureException; // 导入自定义异常类
import net.micode.notes.tool.GTaskStringUtils; // 导入工具类
import org.json.JSONException; // 导入JSONException类
import org.json.JSONObject; // 导入JSONObject类
import java.util.ArrayList; // 导入ArrayList类
public class TaskList extends Node { // TaskList类继承自Node类
private static final String TAG = TaskList.class.getSimpleName(); // 定义日志标签
private int mIndex; // 任务列表索引
private ArrayList<Task> mChildren; // 存储子任务的列表
public TaskList() { // 构造函数
super(); // 调用父类构造函数
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 初始化索引
}
public JSONObject getCreateAction(int actionId) { // 获取创建操作的JSON对象
JSONObject js = new JSONObject(); // 创建JSON对象
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); // 设置操作ID
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); // 设置索引
// entity_delta
JSONObject entity = new JSONObject(); // 创建实体JSON对象
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 设置创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 设置实体类型为组
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体添加到JSON对象中
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("fail to generate tasklist-create jsonobject"); // 抛出自定义异常
}
return js; // 返回创建操作的JSON对象
}
public JSONObject getUpdateAction(int actionId) { // 获取更新操作的JSON对象
JSONObject js = new JSONObject(); // 创建JSON对象
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
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 设置ID
// entity_delta
JSONObject entity = new JSONObject(); // 创建实体JSON对象
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 设置删除状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体添加到JSON对象中
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("fail to generate tasklist-update jsonobject"); // 抛出自定义异常
}
return js; // 返回更新操作的JSON对象
}
public void setContentByRemoteJSON(JSONObject js) { // 根据远程JSON设置内容
if (js != null) { // 检查JSON对象是否为空
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { // 检查是否包含ID
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); // 设置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) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("fail to get tasklist content from jsonobject"); // 抛出自定义异常
}
}
}
public void setContentByLocalJSON(JSONObject js) { // 根据本地JSON设置内容
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { // 检查JSON对象是否为空或是否包含头部注释
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) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
}
}
public JSONObject getLocalJSONFromContent() { // 从内容获取本地JSON
try {
JSONObject js = new JSONObject(); // 创建JSON对象
JSONObject folder = new JSONObject(); // 创建文件夹JSON对象
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); // 将文件夹添加到JSON对象中
return js; // 返回本地JSON对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
return null; // 返回空
}
}
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()) { // 检查同步ID是否匹配
// 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())) { // 检查gtask ID是否匹配
Log.e(TAG, "gtask id doesn't match"); // 记录错误日志
return SYNC_ACTION_ERROR; // 返回错误操作
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // 检查同步ID是否匹配
// 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; // 返回错误操作
}
public int getChildTaskCount() { // 获取子任务数量
return mChildren.size(); // 返回子任务列表的大小
}
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; // 返回添加结果
}
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; // 返回成功
}
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; // 返回移除结果
}
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)); // 移除并添加任务
}
public Task findChildTaskByGid(String gid) { // 根据GID查找子任务
for (int i = 0; i < mChildren.size(); i++) { // 遍历子任务列表
Task t = mChildren.get(i); // 获取任务
if (t.getGid().equals(gid)) { // 检查GID是否匹配
return t; // 返回匹配的任务
}
}
return null; // 返回空
}
public int getChildTaskIndex(Task task) { // 获取子任务索引
return mChildren.indexOf(task); // 返回任务在列表中的位置
}
public Task getChildTaskByIndex(int index) { // 根据索引获取子任务
if (index < 0 || index >= mChildren.size()) { // 检查索引有效性
Log.e(TAG, "getTaskByIndex: invalid index"); // 记录错误日志
return null; // 返回空
}
return mChildren.get(index); // 返回指定索引的任务
}
public Task getChilTaskByGid(String gid) { // 根据GID获取子任务
for (Task task : mChildren) { // 遍历子任务列表
if (task.getGid().equals(gid)) // 检查GID是否匹配
return task; // 返回匹配的任务
}
return null; // 返回空
}
public ArrayList<Task> getChildTaskList() { // 获取子任务列表
return this.mChildren; // 返回子任务列表
}
public void setIndex(int index) { // 设置索引
this.mIndex = index; // 更新索引
}
public int getIndex() { // 获取索引
return this.mIndex; // 返回索引
}
}

@ -0,0 +1,33 @@
/*
* 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; // 定义异常类所在的包
public class ActionFailureException extends RuntimeException { // 定义一个运行时异常类,表示操作失败
private static final long serialVersionUID = 4425249765923293627L; // 序列化ID用于版本控制
public ActionFailureException() { // 默认构造函数
super(); // 调用父类构造函数
}
public ActionFailureException(String paramString) { // 带有错误信息的构造函数
super(paramString); // 调用父类构造函数,传递错误信息
}
public ActionFailureException(String paramString, Throwable paramThrowable) { // 带有错误信息和原因的构造函数
super(paramString, paramThrowable); // 调用父类构造函数,传递错误信息和原因
}
}

@ -0,0 +1,33 @@
/*
* 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; // 定义异常类所在的包
public class NetworkFailureException extends Exception { // 定义网络故障异常类继承自Exception类
private static final long serialVersionUID = 2107610287180234136L; // 序列化ID用于版本控制
public NetworkFailureException() { // 默认构造函数
super(); // 调用父类构造函数
}
public NetworkFailureException(String paramString) { // 带参数的构造函数,接受错误信息
super(paramString); // 调用父类构造函数,传递错误信息
}
public NetworkFailureException(String paramString, Throwable paramThrowable) { // 带参数的构造函数,接受错误信息和根本原因
super(paramString, paramThrowable); // 调用父类构造函数,传递错误信息和根本原因
}
}

@ -0,0 +1,122 @@
/*
* 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; // 导入Notification类用于创建通知
import android.app.NotificationManager; // 导入NotificationManager类用于管理通知
import android.app.PendingIntent; // 导入PendingIntent类用于处理通知点击事件
import android.content.Context; // 导入Context类提供应用环境信息
import android.content.Intent; // 导入Intent类用于启动活动
import android.os.AsyncTask; // 导入AsyncTask类用于异步任务处理
import net.micode.notes.R; // 导入资源文件
import net.micode.notes.ui.NotesListActivity; // 导入笔记列表活动
import net.micode.notes.ui.NotesPreferenceActivity; // 导入笔记偏好设置活动
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> { // 定义GTaskASyncTask类继承自AsyncTask
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 定义同步通知的ID
public interface OnCompleteListener { // 定义完成监听器接口
void onComplete(); // 完成时调用的方法
}
private Context mContext; // 上下文对象
private NotificationManager mNotifiManager; // 通知管理器
private GTaskManager mTaskManager; // 任务管理器
private OnCompleteListener mOnCompleteListener; // 完成监听器
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(); // 调用任务管理器取消同步
}
public void publishProgess(String message) { // 发布进度方法
publishProgress(new String[] { // 发布进度信息
message
});
}
private void showNotification(int tickerId, String content) { // 显示通知方法
PendingIntent pendingIntent; // 声明PendingIntent对象
if (tickerId != R.string.ticker_success) { // 判断是否为成功通知
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE); // 创建PendingIntent
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE); // 创建PendingIntent
}
Notification.Builder builder = new Notification.Builder(mContext) // 创建通知构建器
.setAutoCancel(true) // 设置自动取消
.setContentTitle(mContext.getString(R.string.app_name)) // 设置通知标题
.setContentText(content) // 设置通知内容
.setContentIntent(pendingIntent) // 设置点击通知后的意图
.setWhen(System.currentTimeMillis()) // 设置通知时间
.setOngoing(true); // 设置为正在进行的通知
Notification notification=builder.getNotification(); // 获取构建的通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // 发送通知
}
@Override
protected Integer doInBackground(Void... unused) { // 在后台执行的任务
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext))); // 发布登录进度
return mTaskManager.sync(mContext, this); // 调用任务管理器进行同步
}
@Override
protected void onProgressUpdate(String... progress) { // 进度更新方法
showNotification(R.string.ticker_syncing, progress[0]); // 显示同步进度通知
if (mContext instanceof GTaskSyncService) { // 判断上下文是否为GTaskSyncService
((GTaskSyncService) mContext).sendBroadcast(progress[0]); // 发送广播
}
}
@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,583 @@
/*
* 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;
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName(); // 定义日志标签
private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google任务的基本URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // 获取任务的URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 提交任务的URL
private static GTaskClient mInstance = null; // GTaskClient的单例实例
private DefaultHttpClient mHttpClient; // HTTP客户端
private String mGetUrl; // 获取任务的URL
private String mPostUrl; // 提交任务的URL
private long mClientVersion; // 客户端版本
private boolean mLoggedin; // 登录状态
private long mLastLoginTime; // 上次登录时间
private int mActionId; // 动作ID
private Account mAccount; // Google账户
private JSONArray mUpdateArray; // 更新数组
private GTaskClient() { // 构造函数
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
public static synchronized GTaskClient getInstance() { // 获取单例实例
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
public boolean login(Activity activity) { // 登录方法
// 我们假设cookie在5分钟后过期
// 然后我们需要重新登录
final long interval = 1000 * 60 * 5; // 登录间隔时间
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false; // 登录状态设为false
}
// 账户切换后需要重新登录
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false; // 登录状态设为false
}
if (mLoggedin) {
Log.d(TAG, "already logged in"); // 已经登录
return true; // 返回登录成功
}
mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间
String authToken = loginGoogleAccount(activity, false); // 登录Google账户
if (authToken == null) {
Log.e(TAG, "login google account failed"); // 登录失败
return false; // 返回登录失败
}
// 如果需要,使用自定义域登录
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); // 构建自定义域URL
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig"; // 更新获取任务的URL
mPostUrl = url.toString() + "r/ig"; // 更新提交任务的URL
if (tryToLoginGtask(activity, authToken)) { // 尝试登录Gtask
mLoggedin = true; // 登录成功
}
}
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; // 恢复获取任务的URL
mPostUrl = GTASK_POST_URL; // 恢复提交任务的URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试登录Gtask
return false; // 返回登录失败
}
}
mLoggedin = true; // 登录成功
return true; // 返回登录成功
}
private String loginGoogleAccount(Activity activity, boolean invalidateToken) { // 登录Google账户
String authToken; // 认证令牌
AccountManager accountManager = AccountManager.get(activity); // 获取账户管理器
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取Google账户
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account"); // 没有可用的Google账户
return null; // 返回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; // 返回null
}
// 现在获取令牌
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; // 赋值为null
}
return authToken; // 返回认证令牌
}
private boolean tryToLoginGtask(Activity activity, String authToken) { // 尝试登录Gtask
if (!loginGtask(authToken)) { // 登录Gtask失败
// 可能认证令牌过期,现在使令牌失效并重试
authToken = loginGoogleAccount(activity, true); // 重新获取认证令牌
if (authToken == null) {
Log.e(TAG, "login google account failed"); // 登录Google账户失败
return false; // 返回登录失败
}
if (!loginGtask(authToken)) { // 再次尝试登录Gtask
Log.e(TAG, "login gtask failed"); // 登录Gtask失败
return false; // 返回登录失败
}
}
return true; // 返回登录成功
}
private boolean loginGtask(String authToken) { // 登录Gtask
int timeoutConnection = 10000; // 连接超时设置
int timeoutSocket = 15000; // 套接字超时设置
HttpParams httpParameters = new BasicHttpParams(); // 创建HTTP参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // 设置连接超时
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); // 设置套接字超时
mHttpClient = new DefaultHttpClient(httpParameters); // 创建HTTP客户端
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); // 创建Cookie存储
mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 禁用Expect: 100-continue
// 登录Gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken; // 构建登录URL
HttpGet httpGet = new HttpGet(loginUrl); // 创建GET请求
HttpResponse response = null; // 初始化响应
response = mHttpClient.execute(httpGet); // 执行请求
// 获取Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); // 获取Cookie列表
boolean hasAuthCookie = false; // 认证Cookie标志
for (Cookie cookie : cookies) { // 遍历Cookie
if (cookie.getName().contains("GTL")) { // 检查是否包含认证Cookie
hasAuthCookie = true; // 设置标志为true
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie"); // 没有认证Cookie
}
// 获取客户端版本
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript开始标记
String jsEnd = ")}</script>"; // JavaScript结束标记
int begin = resString.indexOf(jsBegin); // 查找开始位置
int end = resString.lastIndexOf(jsEnd); // 查找结束位置
String jsString = null; // 初始化JavaScript字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JavaScript字符串
}
JSONObject js = new JSONObject(jsString); // 创建JSON对象
mClientVersion = js.getLong("v"); // 获取客户端版本
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析错误
e.printStackTrace(); // 打印堆栈信息
return false; // 返回登录失败
} catch (Exception e) {
// 捕获所有异常
Log.e(TAG, "httpget gtask_url failed"); // GET请求失败
return false; // 返回登录失败
}
return true; // 返回登录成功
}
private int getActionId() { // 获取动作ID
return mActionId++; // 返回并递增动作ID
}
private HttpPost createHttpPost() { // 创建HTTP POST请求
HttpPost httpPost = new HttpPost(mPostUrl); // 创建POST请求
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头
httpPost.setHeader("AT", "1"); // 设置自定义请求头
return httpPost; // 返回POST请求
}
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()); // 处理GZIP编码
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true); // 创建Inflater
input = new InflaterInputStream(entity.getContent(), inflater); // 处理Deflate编码
}
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(); // 关闭输入流
}
}
private JSONObject postRequest(JSONObject js) throws NetworkFailureException { // 发送POST请求
if (!mLoggedin) {
Log.e(TAG, "please login first"); // 请先登录
throw new ActionFailureException("not logged in"); // 抛出异常
}
HttpPost httpPost = createHttpPost(); // 创建POST请求
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); // 创建参数列表
list.add(new BasicNameValuePair("r", js.toString())); // 添加请求参数
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建请求实体
httpPost.setEntity(entity); // 设置请求实体
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost); // 执行请求
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 返回JSON对象
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString()); // 客户端协议异常
e.printStackTrace(); // 打印堆栈信息
throw new NetworkFailureException("postRequest failed"); // 抛出网络异常
} catch (IOException e) {
Log.e(TAG, e.toString()); // IO异常
e.printStackTrace(); // 打印堆栈信息
throw new NetworkFailureException("postRequest failed"); // 抛出网络异常
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
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"); // 抛出操作失败异常
}
}
public void createTask(Task task) throws NetworkFailureException { // 创建任务
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
// 动作列表
actionList.put(task.getCreateAction(getActionId())); // 添加创建任务的动作
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 客户端版本
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)); // 设置任务ID
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("create task: handing jsonobject failed"); // 抛出操作失败异常
}
}
public void createTaskList(TaskList tasklist) throws NetworkFailureException { // 创建任务列表
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
// 动作列表
actionList.put(tasklist.getCreateAction(getActionId())); // 添加创建任务列表的动作
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 客户端版本
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)); // 设置任务列表ID
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("create tasklist: handing jsonobject failed"); // 抛出操作失败异常
}
}
public void commitUpdate() throws NetworkFailureException { // 提交更新
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
// 动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); // 设置动作列表
// 客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本
postRequest(jsPost); // 发送POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("commit update: handing jsonobject failed"); // 抛出操作失败异常
}
}
}
public void addUpdateNode(Node node) throws NetworkFailureException { // 添加更新节点
if (node != null) {
// 更新项过多可能导致错误
// 设置最大为10项
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate(); // 提交更新
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray(); // 初始化更新数组
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新动作
}
}
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException { // 移动任务
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建动作对象
// 动作列表
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); // 设置动作类型为移动
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务ID
if (preParent == curParent && task.getPriorSibling() != null) {
// 仅在任务列表内移动且不是第一个时设置prioring_sibling_id
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); // 设置优先兄弟ID
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置源列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置目标父级ID
if (preParent != curParent) {
// 仅在任务列表之间移动时设置dest_list
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); // 设置目标列表ID
}
actionList.put(action); // 添加动作到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本
postRequest(jsPost); // 发送POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("move task: handing jsonobject failed"); // 抛出操作失败异常
}
}
public void deleteNode(Node node) throws NetworkFailureException { // 删除节点
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
// 动作列表
node.setDeleted(true); // 设置节点为删除状态
actionList.put(node.getUpdateAction(getActionId())); // 添加更新动作
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本
postRequest(jsPost); // 发送POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("delete node: handing jsonobject failed"); // 抛出操作失败异常
}
}
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); // 创建GET请求
HttpResponse response = null; // 初始化响应
response = mHttpClient.execute(httpGet); // 执行请求
// 获取任务列表
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript开始标记
String jsEnd = ")}</script>"; // JavaScript结束标记
int begin = resString.indexOf(jsBegin); // 查找开始位置
int end = resString.lastIndexOf(jsEnd); // 查找结束位置
String jsString = null; // 初始化JavaScript字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JavaScript字符串
}
JSONObject js = new JSONObject(jsString); // 创建JSON对象
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()); // IO异常
e.printStackTrace(); // 打印堆栈信息
throw new NetworkFailureException("gettasklists: httpget failed"); // 抛出网络异常
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("get task lists: handing jasonobject failed"); // 抛出操作失败异常
}
}
public JSONArray getTaskList(String listGid) throws NetworkFailureException { // 获取指定任务列表
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建动作对象
// 动作列表
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); // 设置动作类型为获取所有
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除项
actionList.put(action); // 添加动作到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本
JSONObject jsResponse = postRequest(jsPost); // 发送POST请求并获取响应
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 返回任务数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // JSON解析异常
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("get task list: handing jsonobject failed"); // 抛出操作失败异常
}
}
public Account getSyncAccount() { // 获取同步账户
return mAccount; // 返回账户
}
public void resetUpdateArray() { // 重置更新数组
mUpdateArray = null; // 清空更新数组
}
}

@ -0,0 +1,797 @@
/*
* 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; // 包名表示该类属于远程GTask模块
import android.app.Activity; // 导入Activity类
import android.content.ContentResolver; // 导入内容解析器类
import android.content.ContentUris; // 导入内容URI工具类
import android.content.ContentValues; // 导入内容值类
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
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.NoteColumns; // 导入笔记列类
import net.micode.notes.gtask.data.MetaData; // 导入元数据类
import net.micode.notes.gtask.data.Node; // 导入节点类
import net.micode.notes.gtask.data.SqlNote; // 导入SQL笔记类
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.DataUtils; // 导入数据工具类
import net.micode.notes.tool.GTaskStringUtils; // 导入GTask字符串工具类
import org.json.JSONArray; // 导入JSON数组类
import org.json.JSONException; // 导入JSON异常类
import org.json.JSONObject; // 导入JSON对象类
import java.util.HashMap; // 导入哈希表类
import java.util.HashSet; // 导入哈希集合类
import java.util.Iterator; // 导入迭代器类
import java.util.Map; // 导入映射类
public class GTaskManager { // GTask管理器类
private static final String TAG = GTaskManager.class.getSimpleName(); // 日志标签
public static final int STATE_SUCCESS = 0; // 同步成功状态
public static final int STATE_NETWORK_ERROR = 1; // 网络错误状态
public static final int STATE_INTERNAL_ERROR = 2; // 内部错误状态
public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中状态
public static final int STATE_SYNC_CANCELLED = 4; // 同步已取消状态
private static GTaskManager mInstance = null; // GTaskManager单例实例
private Activity mActivity; // 当前活动
private Context mContext; // 上下文
private ContentResolver mContentResolver; // 内容解析器
private boolean mSyncing; // 同步状态标志
private boolean mCancelled; // 取消状态标志
private HashMap<String, TaskList> mGTaskListHashMap; // GTask任务列表映射
private HashMap<String, Node> mGTaskHashMap; // GTask节点映射
private HashMap<String, MetaData> mMetaHashMap; // 元数据映射
private TaskList mMetaList; // 元任务列表
private HashSet<Long> mLocalDeleteIdMap; // 本地删除ID集合
private HashMap<String, Long> mGidToNid; // GID到NID的映射
private HashMap<Long, String> mNidToGid; // NID到GID的映射
private GTaskManager() { // 构造函数
mSyncing = false; // 初始化同步状态
mCancelled = false; // 初始化取消状态
mGTaskListHashMap = new HashMap<String, TaskList>(); // 初始化GTask任务列表映射
mGTaskHashMap = new HashMap<String, Node>(); // 初始化GTask节点映射
mMetaHashMap = new HashMap<String, MetaData>(); // 初始化元数据映射
mMetaList = null; // 初始化元任务列表
mLocalDeleteIdMap = new HashSet<Long>(); // 初始化本地删除ID集合
mGidToNid = new HashMap<String, Long>(); // 初始化GID到NID的映射
mNidToGid = new HashMap<Long, String>(); // 初始化NID到GID的映射
}
public static synchronized GTaskManager getInstance() { // 获取GTaskManager单例实例
if (mInstance == null) { // 如果实例为空
mInstance = new GTaskManager(); // 创建新实例
}
return mInstance; // 返回实例
}
public synchronized void setActivityContext(Activity activity) { // 设置活动上下文
// 用于获取认证令牌
mActivity = activity; // 赋值活动
}
public int sync(Context context, GTaskASyncTask asyncTask) { // 同步方法
if (mSyncing) { // 如果正在同步
Log.d(TAG, "Sync is in progress"); // 记录日志
return STATE_SYNC_IN_PROGRESS; // 返回同步进行中状态
}
mContext = context; // 赋值上下文
mContentResolver = mContext.getContentResolver(); // 获取内容解析器
mSyncing = true; // 设置同步状态为正在同步
mCancelled = false; // 设置取消状态为未取消
mGTaskListHashMap.clear(); // 清空GTask任务列表映射
mGTaskHashMap.clear(); // 清空GTask节点映射
mMetaHashMap.clear(); // 清空元数据映射
mLocalDeleteIdMap.clear(); // 清空本地删除ID集合
mGidToNid.clear(); // 清空GID到NID的映射
mNidToGid.clear(); // 清空NID到GID的映射
try {
GTaskClient client = GTaskClient.getInstance(); // 获取GTask客户端实例
client.resetUpdateArray(); // 重置更新数组
// 登录Google任务
if (!mCancelled) { // 如果未取消
if (!client.login(mActivity)) { // 登录失败
throw new NetworkFailureException("login google task failed"); // 抛出网络失败异常
}
}
// 从Google获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); // 发布进度
initGTaskList(); // 初始化GTask列表
// 执行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); // 发布同步进度
syncContent(); // 同步内容
} catch (NetworkFailureException e) { // 捕获网络失败异常
Log.e(TAG, e.toString()); // 记录错误日志
return STATE_NETWORK_ERROR; // 返回网络错误状态
} catch (ActionFailureException e) { // 捕获操作失败异常
Log.e(TAG, e.toString()); // 记录错误日志
return STATE_INTERNAL_ERROR; // 返回内部错误状态
} catch (Exception e) { // 捕获其他异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
return STATE_INTERNAL_ERROR; // 返回内部错误状态
} finally { // 最终执行
mGTaskListHashMap.clear(); // 清空GTask任务列表映射
mGTaskHashMap.clear(); // 清空GTask节点映射
mMetaHashMap.clear(); // 清空元数据映射
mLocalDeleteIdMap.clear(); // 清空本地删除ID集合
mGidToNid.clear(); // 清空GID到NID的映射
mNidToGid.clear(); // 清空NID到GID的映射
mSyncing = false; // 设置同步状态为未同步
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回状态
}
private void initGTaskList() throws NetworkFailureException { // 初始化GTask列表
if (mCancelled) // 如果已取消
return; // 返回
GTaskClient client = GTaskClient.getInstance(); // 获取GTask客户端实例
try {
JSONArray jsTaskLists = client.getTaskLists(); // 获取任务列表的JSON数组
// 首先初始化元列表
mMetaList = null; // 初始化元列表
for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历任务列表
JSONObject object = jsTaskLists.getJSONObject(i); // 获取JSON对象
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取名称
if (name // 如果名称是元文件夹前缀
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList(); // 创建新的任务列表
mMetaList.setContentByRemoteJSON(object); // 设置内容
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid); // 获取元数据的JSON数组
for (int j = 0; j < jsMetas.length(); j++) { // 遍历元数据
object = (JSONObject) jsMetas.getJSONObject(j); // 获取JSON对象
MetaData metaData = new MetaData(); // 创建元数据对象
metaData.setContentByRemoteJSON(object); // 设置内容
if (metaData.isWorthSaving()) { // 如果值得保存
mMetaList.addChildTask(metaData); // 添加子任务
if (metaData.getGid() != null) { // 如果GID不为空
mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 添加到元数据映射
}
}
}
}
}
// 如果元列表不存在,则创建
if (mMetaList == null) {
mMetaList = new TaskList(); // 创建新的任务列表
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META); // 设置名称
GTaskClient.getInstance().createTaskList(mMetaList); // 创建任务列表
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历任务列表
JSONObject object = jsTaskLists.getJSONObject(i); // 获取JSON对象
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取名称
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) // 如果名称以文件夹前缀开头
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) { // 并且不是元文件夹
TaskList tasklist = new TaskList(); // 创建新的任务列表
tasklist.setContentByRemoteJSON(object); // 设置内容
mGTaskListHashMap.put(gid, tasklist); // 添加到GTask任务列表映射
mGTaskHashMap.put(gid, tasklist); // 添加到GTask节点映射
// 加载任务
JSONArray jsTasks = client.getTaskList(gid); // 获取任务的JSON数组
for (int j = 0; j < jsTasks.length(); j++) { // 遍历任务
object = (JSONObject) jsTasks.getJSONObject(j); // 获取JSON对象
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID
Task task = new Task(); // 创建任务对象
task.setContentByRemoteJSON(object); // 设置内容
if (task.isWorthSaving()) { // 如果值得保存
task.setMetaInfo(mMetaHashMap.get(gid)); // 设置元信息
tasklist.addChildTask(task); // 添加子任务
mGTaskHashMap.put(gid, task); // 添加到GTask节点映射
}
}
}
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 记录错误日志
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("initGTaskList: handing JSONObject failed"); // 抛出操作失败异常
}
}
private void syncContent() throws NetworkFailureException { // 同步内容
int syncType; // 同步类型
Cursor c = null; // 游标
String gid; // GID
Node node; // 节点
mLocalDeleteIdMap.clear(); // 清空本地删除ID集合
if (mCancelled) { // 如果已取消
return; // 返回
}
// 处理本地删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, // 查询笔记
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) // 排除系统类型和垃圾桶
}, null);
if (c != null) { // 如果游标不为空
while (c.moveToNext()) { // 遍历游标
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行内容同步,删除远程节点
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除ID集合
}
} else {
Log.w(TAG, "failed to query trash folder"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
// 首先同步文件夹
syncFolder(); // 同步文件夹
// 处理数据库中存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, // 查询笔记
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) // 仅查询笔记类型,排除垃圾桶
}, NoteColumns.TYPE + " DESC"); // 按类型降序排列
if (c != null) { // 如果游标不为空
while (c.moveToNext()) { // 遍历游标
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 更新GID到NID的映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 更新NID到GID的映射
syncType = node.getSyncAction(c); // 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { // 如果GID为空
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE; // 设置为添加远程节点
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL; // 设置为删除本地节点
}
}
doContentSync(syncType, node, c); // 执行内容同步
}
} else {
Log.w(TAG, "failed to query existing note in database"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
// 遍历剩余的项目
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator(); // 获取迭代器
while (iter.hasNext()) { // 遍历映射
Map.Entry<String, Node> entry = iter.next(); // 获取映射项
node = entry.getValue(); // 获取节点
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行内容同步,添加本地节点
}
// mCancelled可能被其他线程设置因此我们需要逐个检查
// 清空本地删除表
if (!mCancelled) { // 如果未取消
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { // 批量删除本地删除的笔记失败
throw new ActionFailureException("failed to batch-delete local deleted notes"); // 抛出操作失败异常
}
}
// 刷新本地同步ID
if (!mCancelled) { // 如果未取消
GTaskClient.getInstance().commitUpdate(); // 提交更新
refreshLocalSyncId(); // 刷新本地同步ID
}
}
private void syncFolder() throws NetworkFailureException { // 同步文件夹
Cursor c = null; // 游标
String gid; // GID
Node node; // 节点
int syncType; // 同步类型
if (mCancelled) { // 如果已取消
return; // 返回
}
// 处理根文件夹
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); // 查询根文件夹
if (c != null) { // 如果游标不为空
c.moveToNext(); // 移动到下一项
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); // 更新GID到NID的映射
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); // 更新NID到GID的映射
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) // 如果名称不等于默认文件夹
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 执行内容同步,更新远程节点
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 执行内容同步,添加远程节点
}
} else {
Log.w(TAG, "failed to query root folder"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
// 处理通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER) // 查询通话记录文件夹
}, null);
if (c != null) { // 如果游标不为空
if (c.moveToNext()) { // 移动到下一项
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); // 更新GID到NID的映射
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); // 更新NID到GID的映射
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE)) // 如果名称不等于通话记录文件夹
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 执行内容同步,更新远程节点
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 执行内容同步,添加远程节点
}
}
} else {
Log.w(TAG, "failed to query call note folder"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
// 处理本地存在的文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) // 查询文件夹类型,排除垃圾桶
}, NoteColumns.TYPE + " DESC"); // 按类型降序排列
if (c != null) { // 如果游标不为空
while (c.moveToNext()) { // 遍历游标
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 更新GID到NID的映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 更新NID到GID的映射
syncType = node.getSyncAction(c); // 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { // 如果GID为空
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE; // 设置为添加远程节点
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL; // 设置为删除本地节点
}
}
doContentSync(syncType, node, c); // 执行内容同步
}
} else {
Log.w(TAG, "failed to query existing folder"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
// 处理远程添加的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); // 获取迭代器
while (iter.hasNext()) { // 遍历映射
Map.Entry<String, TaskList> entry = iter.next(); // 获取映射项
gid = entry.getKey(); // 获取GID
node = entry.getValue(); // 获取节点
if (mGTaskHashMap.containsKey(gid)) { // 如果映射中包含GID
mGTaskHashMap.remove(gid); // 从映射中移除节点
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行内容同步,添加本地节点
}
}
if (!mCancelled) // 如果未取消
GTaskClient.getInstance().commitUpdate(); // 提交更新
}
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { // 执行内容同步
if (mCancelled) { // 如果已取消
return; // 返回
}
MetaData meta; // 元数据
switch (syncType) { // 根据同步类型执行不同操作
case Node.SYNC_ACTION_ADD_LOCAL: // 本地添加
addLocalNode(node); // 添加本地节点
break;
case Node.SYNC_ACTION_ADD_REMOTE: // 远程添加
addRemoteNode(node, c); // 添加远程节点
break;
case Node.SYNC_ACTION_DEL_LOCAL: // 本地删除
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); // 获取元数据
if (meta != null) { // 如果元数据不为空
GTaskClient.getInstance().deleteNode(meta); // 删除远程节点
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除ID集合
break;
case Node.SYNC_ACTION_DEL_REMOTE: // 远程删除
meta = mMetaHashMap.get(node.getGid()); // 获取元数据
if (meta != null) { // 如果元数据不为空
GTaskClient.getInstance().deleteNode(meta); // 删除远程节点
}
GTaskClient.getInstance().deleteNode(node); // 删除远程节点
break;
case Node.SYNC_ACTION_UPDATE_LOCAL: // 本地更新
updateLocalNode(node, c); // 更新本地节点
break;
case Node.SYNC_ACTION_UPDATE_REMOTE: // 远程更新
updateRemoteNode(node, c); // 更新远程节点
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT: // 更新冲突
// 合并两个修改可能是个好主意
// 现在只是简单地使用本地更新
updateRemoteNode(node, c); // 更新远程节点
break;
case Node.SYNC_ACTION_NONE: // 无操作
break;
case Node.SYNC_ACTION_ERROR: // 错误
default: // 默认情况
throw new ActionFailureException("unkown sync action type"); // 抛出操作失败异常
}
}
private void addLocalNode(Node node) throws NetworkFailureException { // 添加本地节点
if (mCancelled) { // 如果已取消
return; // 返回
}
SqlNote sqlNote; // SQL笔记
if (node instanceof TaskList) { // 如果是任务列表
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { // 如果是默认文件夹
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 创建SQL笔记
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { // 如果是通话记录文件夹
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 创建SQL笔记
} else {
sqlNote = new SqlNote(mContext); // 创建SQL笔记
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容
sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父ID
}
} else { // 如果是任务
sqlNote = new SqlNote(mContext); // 创建SQL笔记
JSONObject js = node.getLocalJSONFromContent(); // 获取本地内容的JSON对象
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { // 如果包含笔记头
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记头的JSON对象
if (note.has(NoteColumns.ID)) { // 如果包含ID
long id = note.getLong(NoteColumns.ID); // 获取ID
if (DataUtils.existInNoteDatabase(mContentResolver, id)) { // 如果ID在数据库中存在
// ID不可用必须创建一个新的
note.remove(NoteColumns.ID); // 移除ID
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) { // 如果包含数据头
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
for (int i = 0; i < dataArray.length(); i++) { // 遍历数据数组
JSONObject data = dataArray.getJSONObject(i); // 获取数据的JSON对象
if (data.has(DataColumns.ID)) { // 如果包含数据ID
long dataId = data.getLong(DataColumns.ID); // 获取数据ID
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { // 如果数据ID在数据库中存在
// 数据ID不可用必须创建一个新的
data.remove(DataColumns.ID); // 移除数据ID
}
}
}
}
} catch (JSONException e) { // 捕获JSON异常
Log.w(TAG, e.toString()); // 记录警告日志
e.printStackTrace(); // 打印堆栈跟踪
}
sqlNote.setContent(js); // 设置内容
Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); // 获取父ID
if (parentId == null) { // 如果父ID为空
Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志
throw new ActionFailureException("cannot add local node"); // 抛出操作失败异常
}
sqlNote.setParentId(parentId.longValue()); // 设置父ID
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid()); // 设置GTask ID
sqlNote.commit(false); // 提交
// 更新GID-NID映射
mGidToNid.put(node.getGid(), sqlNote.getId()); // 更新GID到NID的映射
mNidToGid.put(sqlNote.getId(), node.getGid()); // 更新NID到GID的映射
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据
}
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { // 更新本地节点
if (mCancelled) { // 如果已取消
return; // 返回
}
SqlNote sqlNote; // SQL笔记
// 在本地更新笔记
sqlNote = new SqlNote(mContext, c); // 创建SQL笔记
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER); // 获取父ID
if (parentId == null) { // 如果父ID为空
Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志
throw new ActionFailureException("cannot update local node"); // 抛出操作失败异常
}
sqlNote.setParentId(parentId.longValue()); // 设置父ID
sqlNote.commit(true); // 提交
// 更新元信息
updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { // 添加远程节点
if (mCancelled) { // 如果已取消
return; // 返回
}
SqlNote sqlNote = new SqlNote(mContext, c); // 创建SQL笔记
Node n; // 节点
// 远程更新
if (sqlNote.isNoteType()) { // 如果是笔记类型
Task task = new Task(); // 创建任务对象
task.setContentByLocalJSON(sqlNote.getContent()); // 设置内容
String parentGid = mNidToGid.get(sqlNote.getParentId()); // 获取父GID
if (parentGid == null) { // 如果父GID为空
Log.e(TAG, "cannot find task's parent tasklist"); // 记录错误日志
throw new ActionFailureException("cannot add remote task"); // 抛出操作失败异常
}
mGTaskListHashMap.get(parentGid).addChildTask(task); // 添加子任务
GTaskClient.getInstance().createTask(task); // 创建任务
n = (Node) task; // 赋值节点
// 添加元数据
updateRemoteMeta(task.getGid(), sqlNote); // 更新远程元数据
} else { // 如果是任务列表
TaskList tasklist = null; // 任务列表
// 如果文件夹已经存在,则跳过
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; // 文件夹名称前缀
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) // 如果是根文件夹
folderName += GTaskStringUtils.FOLDER_DEFAULT; // 添加默认文件夹后缀
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) // 如果是通话记录文件夹
folderName += GTaskStringUtils.FOLDER_CALL_NOTE; // 添加通话记录文件夹后缀
else
folderName += sqlNote.getSnippet(); // 添加文件夹片段
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); // 获取迭代器
while (iter.hasNext()) { // 遍历映射
Map.Entry<String, TaskList> entry = iter.next(); // 获取映射项
String gid = entry.getKey(); // 获取GID
TaskList list = entry.getValue(); // 获取任务列表
if (list.getName().equals(folderName)) { // 如果名称匹配
tasklist = list; // 赋值任务列表
if (mGTaskHashMap.containsKey(gid)) { // 如果映射中包含GID
mGTaskHashMap.remove(gid); // 从映射中移除节点
}
break; // 退出循环
}
}
// 如果没有匹配,可以添加
if (tasklist == null) { // 如果任务列表为空
tasklist = new TaskList(); // 创建新的任务列表
tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置内容
GTaskClient.getInstance().createTaskList(tasklist); // 创建任务列表
mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 添加到GTask任务列表映射
}
n = (Node) tasklist; // 赋值节点
}
// 更新本地笔记
sqlNote.setGtaskId(n.getGid()); // 设置GTask ID
sqlNote.commit(false); // 提交
sqlNote.resetLocalModified(); // 重置本地修改标志
sqlNote.commit(true); // 提交
// GID-ID映射
mGidToNid.put(n.getGid(), sqlNote.getId()); // 更新GID到NID的映射
mNidToGid.put(sqlNote.getId(), n.getGid()); // 更新NID到GID的映射
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { // 更新远程节点
if (mCancelled) { // 如果已取消
return; // 返回
}
SqlNote sqlNote = new SqlNote(mContext, c); // 创建SQL笔记
// 远程更新
node.setContentByLocalJSON(sqlNote.getContent()); // 设置内容
GTaskClient.getInstance().addUpdateNode(node); // 添加更新节点
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据
// 如果需要,移动任务
if (sqlNote.isNoteType()) { // 如果是笔记类型
Task task = (Task) node; // 赋值任务
TaskList preParentList = task.getParent(); // 获取之前的父任务列表
String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 获取当前父GID
if (curParentGid == null) { // 如果当前父GID为空
Log.e(TAG, "cannot find task's parent tasklist"); // 记录错误日志
throw new ActionFailureException("cannot update remote task"); // 抛出操作失败异常
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取当前父任务列表
if (preParentList != curParentList) { // 如果之前的父任务列表与当前不同
preParentList.removeChildTask(task); // 从之前的父任务列表中移除任务
curParentList.addChildTask(task); // 添加到当前父任务列表
GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 移动任务
}
}
// 清除本地修改标志
sqlNote.resetLocalModified(); // 重置本地修改标志
sqlNote.commit(true); // 提交
}
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { // 更新远程元数据
if (sqlNote != null && sqlNote.isNoteType()) { // 如果SQL笔记不为空且是笔记类型
MetaData metaData = mMetaHashMap.get(gid); // 获取元数据
if (metaData != null) { // 如果元数据不为空
metaData.setMeta(gid, sqlNote.getContent()); // 设置元数据
GTaskClient.getInstance().addUpdateNode(metaData); // 添加更新节点
} else { // 如果元数据为空
metaData = new MetaData(); // 创建新的元数据
metaData.setMeta(gid, sqlNote.getContent()); // 设置元数据
mMetaList.addChildTask(metaData); // 添加子任务
mMetaHashMap.put(gid, metaData); // 添加到元数据映射
GTaskClient.getInstance().createTask(metaData); // 创建任务
}
}
}
private void refreshLocalSyncId() throws NetworkFailureException { // 刷新本地同步ID
if (mCancelled) { // 如果已取消
return; // 返回
}
// 获取最新的GTask列表
mGTaskHashMap.clear(); // 清空GTask节点映射
mGTaskListHashMap.clear(); // 清空GTask任务列表映射
mMetaHashMap.clear(); // 清空元数据映射
initGTaskList(); // 初始化GTask列表
Cursor c = null; // 游标
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, // 查询笔记
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) // 排除系统类型和垃圾桶
}, NoteColumns.TYPE + " DESC"); // 按类型降序排列
if (c != null) { // 如果游标不为空
while (c.moveToNext()) { // 遍历游标
String gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取GID
Node node = mGTaskHashMap.get(gid); // 获取节点
if (node != null) { // 如果节点不为空
mGTaskHashMap.remove(gid); // 从映射中移除节点
ContentValues values = new ContentValues(); // 创建内容值
values.put(NoteColumns.SYNC_ID, node.getLastModified()); // 更新同步ID
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null); // 更新数据库
} else {
Log.e(TAG, "something is missed"); // 记录错误日志
throw new ActionFailureException(
"some local items don't have gid after sync"); // 抛出操作失败异常
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id"); // 记录警告日志
}
} finally {
if (c != null) { // 如果游标不为空
c.close(); // 关闭游标
c = null; // 置空游标
}
}
}
public String getSyncAccount() { // 获取同步账户
return GTaskClient.getInstance().getSyncAccount().name; // 返回同步账户名称
}
public void cancelSync() { // 取消同步
mCancelled = true; // 设置取消状态为已取消
}
}

@ -0,0 +1,128 @@
/*
* 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; // 导入Activity类
import android.app.Service; // 导入Service类
import android.content.Context; // 导入Context类
import android.content.Intent; // 导入Intent类
import android.os.Bundle; // 导入Bundle类
import android.os.IBinder; // 导入IBinder接口
public class GTaskSyncService extends Service { // 定义GTaskSyncService类继承自Service
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"; // 定义广播名称常量
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 定义同步状态广播的常量
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 定义进度消息广播的常量
private static GTaskASyncTask mSyncTask = null; // 定义异步任务的静态变量
private static String mSyncProgress = ""; // 定义同步进度的静态变量
private void startSync() { // 开始同步的方法
if (mSyncTask == null) { // 如果当前没有同步任务
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { // 创建新的异步任务
public void onComplete() { // 任务完成时的回调
mSyncTask = null; // 清空同步任务
sendBroadcast(""); // 发送广播
stopSelf(); // 停止服务
}
});
sendBroadcast(""); // 发送广播
mSyncTask.execute(); // 执行异步任务
}
}
private void cancelSync() { // 取消同步的方法
if (mSyncTask != null) { // 如果当前有同步任务
mSyncTask.cancelSync(); // 取消同步任务
}
}
@Override
public void onCreate() { // 服务创建时调用
mSyncTask = null; // 初始化同步任务为null
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) { // 服务启动时调用
Bundle bundle = intent.getExtras(); // 获取Intent中的附加数据
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; // 返回START_STICKY以确保服务在被杀死后重新启动
}
return super.onStartCommand(intent, flags, startId); // 调用父类的方法
}
@Override
public void onLowMemory() { // 当系统内存不足时调用
if (mSyncTask != null) { // 如果当前有同步任务
mSyncTask.cancelSync(); // 取消同步任务
}
}
public IBinder onBind(Intent intent) { // 绑定服务时调用
return null; // 返回null表示不支持绑定
}
public void sendBroadcast(String msg) { // 发送广播的方法
mSyncProgress = msg; // 更新同步进度
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播Intent
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加同步状态到Intent
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加进度消息到Intent
sendBroadcast(intent); // 发送广播
}
public static void startSync(Activity activity) { // 静态方法开始同步
GTaskManager.getInstance().setActivityContext(activity); // 设置活动上下文
Intent intent = new Intent(activity, GTaskSyncService.class); // 创建Intent以启动服务
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加操作类型到Intent
activity.startService(intent); // 启动服务
}
public static void cancelSync(Context context) { // 静态方法取消同步
Intent intent = new Intent(context, GTaskSyncService.class); // 创建Intent以启动服务
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加操作类型到Intent
context.startService(intent); // 启动服务
}
public static boolean isSyncing() { // 静态方法检查是否正在同步
return mSyncTask != null; // 返回同步任务是否为null
}
public static String getProgressString() { // 静态方法获取同步进度
return mSyncProgress; // 返回同步进度
}
}

@ -0,0 +1,249 @@
/*
* 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; // 导入内容URI工具类
import android.content.ContentValues; // 导入内容值类
import android.content.Context; // 导入上下文类
import android.content.OperationApplicationException; // 导入操作应用异常类
import android.net.Uri; // 导入URI类
import android.os.RemoteException; // 导入远程异常类
import android.util.Log; // 导入日志类
import net.micode.notes.data.Notes; // 导入Notes数据类
import net.micode.notes.data.Notes.CallNote; // 导入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; // 导入ArrayList类
public class Note { // 定义Note类
private ContentValues mNoteDiffValues; // 存储笔记差异值的内容
private NoteData mNoteData; // 存储笔记数据的对象
private static final String TAG = "Note"; // 日志标签
/**
* ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) { // 获取新笔记ID的方法
// 在数据库中创建一个新笔记
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); // 设置父文件夹ID
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); // 插入新笔记并获取URI
long noteId = 0; // 初始化笔记ID
try {
noteId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI中获取笔记ID
} catch (NumberFormatException e) { // 捕获数字格式异常
Log.e(TAG, "Get note id error :" + e.toString()); // 记录错误日志
noteId = 0; // 设置笔记ID为0
}
if (noteId == -1) { // 检查笔记ID是否无效
throw new IllegalStateException("Wrong note id:" + noteId); // 抛出非法状态异常
}
return noteId; // 返回新笔记ID
}
public Note() { // Note类的构造函数
mNoteDiffValues = new ContentValues(); // 初始化笔记差异值内容
mNoteData = new NoteData(); // 初始化笔记数据对象
}
public void setNoteValue(String key, String value) { // 设置笔记值的方法
mNoteDiffValues.put(key, value); // 将键值对添加到差异值内容中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改日期
}
public void setTextData(String key, String value) { // 设置文本数据的方法
mNoteData.setTextData(key, value); // 调用NoteData对象的方法设置文本数据
}
public void setTextDataId(long id) { // 设置文本数据ID的方法
mNoteData.setTextDataId(id); // 调用NoteData对象的方法设置文本数据ID
}
public long getTextDataId() { // 获取文本数据ID的方法
return mNoteData.mTextDataId; // 返回文本数据ID
}
public void setCallDataId(long id) { // 设置通话数据ID的方法
mNoteData.setCallDataId(id); // 调用NoteData对象的方法设置通话数据ID
}
public void setCallData(String key, String value) { // 设置通话数据的方法
mNoteData.setCallData(key, value); // 调用NoteData对象的方法设置通话数据
}
public boolean isLocalModified() { // 检查是否本地修改的方法
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); // 返回是否有本地修改
}
public boolean syncNote(Context context, long noteId) { // 同步笔记的方法
if (noteId <= 0) { // 检查笔记ID是否合法
throw new IllegalArgumentException("Wrong note id:" + noteId); // 抛出非法参数异常
}
if (!isLocalModified()) { // 如果没有本地修改
return true; // 返回同步成功
}
/**
* {@link NoteColumns#LOCAL_MODIFIED}
* {@link NoteColumns#MODIFIED_DATE}
*/
if (context.getContentResolver().update( // 更新笔记
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) { // 如果更新失败
Log.e(TAG, "Update note error, should not happen"); // 记录错误日志
// 不返回,继续执行
}
mNoteDiffValues.clear(); // 清空差异值内容
if (mNoteData.isLocalModified() // 如果笔记数据有本地修改
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) { // 尝试推送到内容解析器
return false; // 返回同步失败
}
return true; // 返回同步成功
}
private class NoteData { // 定义内部类NoteData
private long mTextDataId; // 文本数据ID
private ContentValues mTextDataValues; // 存储文本数据的内容
private long mCallDataId; // 通话数据ID
private ContentValues mCallDataValues; // 存储通话数据的内容
private static final String TAG = "NoteData"; // 日志标签
public NoteData() { // NoteData类的构造函数
mTextDataValues = new ContentValues(); // 初始化文本数据内容
mCallDataValues = new ContentValues(); // 初始化通话数据内容
mTextDataId = 0; // 初始化文本数据ID
mCallDataId = 0; // 初始化通话数据ID
}
boolean isLocalModified() { // 检查是否本地修改的方法
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; // 返回是否有本地修改
}
void setTextDataId(long id) { // 设置文本数据ID的方法
if(id <= 0) { // 检查ID是否合法
throw new IllegalArgumentException("Text data id should larger than 0"); // 抛出非法参数异常
}
mTextDataId = id; // 设置文本数据ID
}
void setCallDataId(long id) { // 设置通话数据ID的方法
if (id <= 0) { // 检查ID是否合法
throw new IllegalArgumentException("Call data id should larger than 0"); // 抛出非法参数异常
}
mCallDataId = id; // 设置通话数据ID
}
void setCallData(String key, String value) { // 设置通话数据的方法
mCallDataValues.put(key, value); // 将键值对添加到通话数据内容中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改日期
}
void setTextData(String key, String value) { // 设置文本数据的方法
mTextDataValues.put(key, value); // 将键值对添加到文本数据内容中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 设置本地修改标志
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改日期
}
Uri pushIntoContentResolver(Context context, long noteId) { // 推送数据到内容解析器的方法
/**
*
*/
if (noteId <= 0) { // 检查笔记ID是否合法
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); // 设置笔记ID
if (mTextDataId == 0) { // 如果文本数据ID为0
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); // 设置MIME类型
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues); // 插入文本数据并获取URI
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); // 设置文本数据ID
} catch (NumberFormatException e) { // 捕获数字格式异常
Log.e(TAG, "Insert new text data fail with noteId" + noteId); // 记录错误日志
mTextDataValues.clear(); // 清空文本数据内容
return null; // 返回null
}
} else { // 如果文本数据ID不为0
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); // 设置笔记ID
if (mCallDataId == 0) { // 如果通话数据ID为0
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); // 设置MIME类型
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues); // 插入通话数据并获取URI
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); // 设置通话数据ID
} catch (NumberFormatException e) { // 捕获数字格式异常
Log.e(TAG, "Insert new call data fail with noteId" + noteId); // 记录错误日志
mCallDataValues.clear(); // 清空通话数据内容
return null; // 返回null
}
} else { // 如果通话数据ID不为0
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); // 返回URI
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
return null; // 返回null
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 记录错误日志
return null; // 返回null
}
}
return null; // 返回null
}
}
}

@ -0,0 +1,368 @@
/*
* 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; // 导入内容URI处理类
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; // 导入笔记背景资源解析工具
public class WorkingNote {
// 工作笔记对象
private Note mNote;
// 笔记ID
private long mNoteId;
// 笔记内容
private String mContent;
// 笔记模式
private int mMode;
private long mAlertDate; // 提醒日期
private long mModifiedDate; // 修改日期
private int mBgColorId; // 背景颜色ID
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mFolderId; // 文件夹ID
private Context mContext; // 上下文
private static final String TAG = "WorkingNote"; // 日志标签
private boolean mIsDeleted; // 是否被删除
private NoteSettingChangedListener mNoteSettingStatusListener; // 笔记设置状态监听器
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID, // 数据ID
DataColumns.CONTENT, // 数据内容
DataColumns.MIME_TYPE, // 数据MIME类型
DataColumns.DATA1, // 数据字段1
DataColumns.DATA2, // 数据字段2
DataColumns.DATA3, // 数据字段3
DataColumns.DATA4, // 数据字段4
};
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID, // 父级ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.MODIFIED_DATE // 修改日期
};
private static final int DATA_ID_COLUMN = 0; // 数据ID列索引
private static final int DATA_CONTENT_COLUMN = 1; // 数据内容列索引
private static final int DATA_MIME_TYPE_COLUMN = 2; // 数据MIME类型列索引
private static final int DATA_MODE_COLUMN = 3; // 数据模式列索引
private static final int NOTE_PARENT_ID_COLUMN = 0; // 笔记父级ID列索引
private static final int NOTE_ALERTED_DATE_COLUMN = 1; // 笔记提醒日期列索引
private static final int NOTE_BG_COLOR_ID_COLUMN = 2; // 笔记背景颜色ID列索引
private static final int NOTE_WIDGET_ID_COLUMN = 3; // 笔记小部件ID列索引
private static final int NOTE_WIDGET_TYPE_COLUMN = 4; // 笔记小部件类型列索引
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; // 笔记修改日期列索引
// 新建笔记构造函数
private WorkingNote(Context context, long folderId) {
mContext = context; // 初始化上下文
mAlertDate = 0; // 初始化提醒日期
mModifiedDate = System.currentTimeMillis(); // 初始化修改日期为当前时间
mFolderId = folderId; // 初始化文件夹ID
mNote = new Note(); // 创建新的笔记对象
mNoteId = 0; // 初始化笔记ID
mIsDeleted = false; // 初始化为未删除状态
mMode = 0; // 初始化模式
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 初始化小部件类型为无效
}
// 现有笔记构造函数
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context; // 初始化上下文
mNoteId = noteId; // 初始化笔记ID
mFolderId = folderId; // 初始化文件夹ID
mIsDeleted = false; // 初始化为未删除状态
mNote = new Note(); // 创建新的笔记对象
loadNote(); // 加载笔记数据
}
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); // 获取父级ID
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); // 获取背景颜色ID
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); // 获取小部件ID
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(); // 加载笔记数据
}
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId) // 查询条件为笔记ID
}, 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)); // 设置文本数据ID
} else if (DataConstants.CALL_NOTE.equals(type)) { // 如果是通话笔记
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); // 设置通话数据ID
} 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); // 抛出异常
}
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId); // 创建空白笔记
note.setBgColorId(defaultBgColorId); // 设置默认背景颜色ID
note.setWidgetId(widgetId); // 设置小部件ID
note.setWidgetType(widgetType); // 设置小部件类型
return note; // 返回创建的笔记
}
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0); // 加载现有笔记
}
public synchronized boolean saveNote() {
if (isWorthSaving()) { // 检查是否值得保存
if (!existInDatabase()) { // 如果笔记不存在于数据库
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { // 获取新笔记ID
Log.e(TAG, "Create new note fail with id:" + mNoteId); // 记录错误日志
return false; // 返回保存失败
}
}
mNote.syncNote(mContext, mNoteId); // 同步笔记数据
/**
*
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged(); // 通知小部件已更改
}
return true; // 返回保存成功
} else {
return false; // 返回保存失败
}
}
public boolean existInDatabase() {
return mNoteId > 0; // 检查笔记是否存在于数据库
}
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) { // 检查笔记是否值得保存
return false; // 返回不值得保存
} else {
return true; // 返回值得保存
}
}
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l; // 设置笔记设置状态监听器
}
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); // 通知提醒状态已更改
}
}
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) { // 如果背景颜色ID发生变化
mBgColorId = id; // 更新背景颜色ID
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged(); // 通知背景颜色已更改
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); // 设置笔记背景颜色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) { // 如果小部件ID发生变化
mWidgetId = id; // 更新小部件ID
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); // 设置笔记小部件ID
}
}
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) { // 如果笔记内容发生变化
mContent = text; // 更新笔记内容
mNote.setTextData(DataColumns.CONTENT, mContent); // 设置笔记内容
}
}
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)); // 设置父级ID为通话记录文件夹
}
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false); // 检查是否存在提醒
}
public String getContent() {
return mContent; // 获取笔记内容
}
public long getAlertDate() {
return mAlertDate; // 获取提醒日期
}
public long getModifiedDate() {
return mModifiedDate; // 获取修改日期
}
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId); // 获取笔记背景资源ID
}
public int getBgColorId() {
return mBgColorId; // 获取背景颜色ID
}
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId); // 获取笔记标题背景资源ID
}
public int getCheckListMode() {
return mMode; // 获取笔记模式
}
public long getNoteId() {
return mNoteId; // 获取笔记ID
}
public long getFolderId() {
return mFolderId; // 获取文件夹ID
}
public int getWidgetId() {
return mWidgetId; // 获取小部件ID
}
public int getWidgetType() {
return mWidgetType; // 获取小部件类型
}
public interface NoteSettingChangedListener {
/**
*
*/
void onBackgroundColorChanged();
/**
*
*/
void onClockAlertChanged(long date, boolean set);
/**
*
*/
void onWidgetChanged();
/**
*
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -0,0 +1,341 @@
/*
* 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;
public class BackupUtils {
private static final String TAG = "BackupUtils"; // 日志标签
// Singleton 相关
private static BackupUtils sInstance;
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;
private BackupUtils(Context context) {
mTextExport = new TextExport(context); // 初始化文本导出工具
}
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); // 检查外部存储是否可用
}
public int exportToText() {
return mTextExport.exportToText(); // 导出为文本
}
public String getExportedTextFileName() {
return mTextExport.mFileName; // 获取导出文件名
}
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; // 获取导出文件目录
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
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,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
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;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
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; // 文件目录
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); // 获取导出笔记的格式
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id]; // 获取指定格式
}
/**
*
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于该文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", 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(); // 关闭游标
}
}
/**
* ID
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", 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.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString()); // 记录错误日志
}
}
/**
*
*/
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted"); // 记录SD卡未挂载的日志
return STATE_SD_CARD_UNMOUONTED; // 返回SD卡未挂载状态
}
PrintStream ps = getExportToTextPrintStream(); // 获取打印流
if (ps == null) {
Log.e(TAG, "get print stream error"); // 记录获取打印流错误的日志
return STATE_SYSTEM_ERROR; // 返回系统错误状态
}
// 首先导出文件夹及其笔记
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(); // 关闭游标
}
// 导出根文件夹中的笔记
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; // 返回成功状态
}
/**
* {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format); // 生成导出文件
if (file == null) {
Log.e(TAG, "create file to exported failed"); // 记录创建文件失败的日志
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; // 返回打印流
}
}
/**
*
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory()); // 获取外部存储目录
sb.append(context.getString(filePathResId)); // 添加文件路径
File filedir = new File(sb.toString()); // 创建文件夹对象
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,302 @@
/*
* 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; // 导入内容URI工具类
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; // 导入哈希集合类
public class DataUtils { // 数据工具类
public static final String TAG = "DataUtils"; // 日志标签
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { // 批量删除笔记
if (ids == null) { // 检查ID集合是否为空
Log.d(TAG, "the ids is null"); // 记录调试日志
return true; // 返回真
}
if (ids.size() == 0) { // 检查ID集合是否为空
Log.d(TAG, "no id is in the hashset"); // 记录调试日志
return true; // 返回真
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); // 创建操作列表
for (long id : ids) { // 遍历ID集合
if(id == Notes.ID_ROOT_FOLDER) { // 检查是否为根文件夹ID
Log.e(TAG, "Don't delete system folder root"); // 记录错误日志
continue; // 跳过当前循环
}
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, "delete notes failed, ids:" + 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; // 返回假
}
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); // 更新笔记
}
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, // 批量移动到文件夹
long folderId) {
if (ids == null) { // 检查ID集合是否为空
Log.d(TAG, "the ids is 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); // 设置目标文件夹ID
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, "delete notes failed, ids:" + 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; // 返回假
}
/**
* {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) { // 获取用户文件夹数量
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, // 查询笔记内容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, "get folder count failed:" + e.toString()); // 记录错误日志
} finally {
cursor.close(); // 关闭游标
}
}
}
return count; // 返回计数
}
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { // 检查笔记在数据库中是否可见
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), // 查询笔记内容URI
null, // 查询所有列
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, // 查询条件
new String [] {String.valueOf(type)}, // 查询参数
null); // 排序
boolean exist = false; // 初始化存在标志
if (cursor != null) { // 检查游标是否为空
if (cursor.getCount() > 0) { // 检查记录数
exist = true; // 设置存在标志
}
cursor.close(); // 关闭游标
}
return exist; // 返回存在标志
}
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { // 检查笔记是否存在于数据库
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), // 查询笔记内容URI
null, // 查询所有列
null, // 查询条件
null, // 查询参数
null); // 排序
boolean exist = false; // 初始化存在标志
if (cursor != null) { // 检查游标是否为空
if (cursor.getCount() > 0) { // 检查记录数
exist = true; // 设置存在标志
}
cursor.close(); // 关闭游标
}
return exist; // 返回存在标志
}
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { // 检查数据是否存在于数据库
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), // 查询数据内容URI
null, // 查询所有列
null, // 查询条件
null, // 查询参数
null); // 排序
boolean exist = false; // 初始化存在标志
if (cursor != null) { // 检查游标是否为空
if (cursor.getCount() > 0) { // 检查记录数
exist = true; // 设置存在标志
}
cursor.close(); // 关闭游标
}
return exist; // 返回存在标志
}
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { // 检查文件夹名称是否可见
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, // 查询笔记内容URI
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + // 查询条件
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + // 排除垃圾文件夹
" AND " + NoteColumns.SNIPPET + "=?", // 查询名称
new String[] { name }, // 查询参数
null); // 排序
boolean exist = false; // 初始化存在标志
if(cursor != null) { // 检查游标是否为空
if(cursor.getCount() > 0) { // 检查记录数
exist = true; // 设置存在标志
}
cursor.close(); // 关闭游标
}
return exist; // 返回存在标志
}
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) { // 获取文件夹笔记小部件
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, // 查询笔记内容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); // 获取小部件ID
widget.widgetType = c.getInt(1); // 获取小部件类型
set.add(widget); // 添加到集合
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, e.toString()); // 记录错误日志
}
} while (c.moveToNext()); // 移动到下一条记录
}
c.close(); // 关闭游标
}
return set; // 返回小部件集合
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { // 根据笔记ID获取呼叫号码
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, // 查询数据内容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, "Get call number fails " + e.toString()); // 记录错误日志
} finally {
cursor.close(); // 关闭游标
}
}
return ""; // 返回空字符串
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { // 根据电话号码和呼叫日期获取笔记ID
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, // 查询数据内容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); // 返回笔记ID
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, "Get call note id fails " + e.toString()); // 记录错误日志
}
}
cursor.close(); // 关闭游标
}
return 0; // 返回0
}
public static String getSnippetById(ContentResolver resolver, long noteId) { // 根据笔记ID获取摘要
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, // 查询笔记内容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("Note is not found with id: " + noteId); // 抛出异常
}
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,112 @@
/*
* 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; // 定义包名
public class GTaskStringUtils { // 定义GTask字符串工具类
public final static String GTASK_JSON_ACTION_ID = "action_id"; // 定义GTask JSON中的动作ID字段
public final static String GTASK_JSON_ACTION_LIST = "action_list"; // 定义GTask JSON中的动作列表字段
public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // 定义GTask JSON中的动作类型字段
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // 定义GTask JSON中的创建动作类型
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // 定义GTask JSON中的获取所有动作类型
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // 定义GTask JSON中的移动动作类型
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // 定义GTask JSON中的更新动作类型
public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // 定义GTask JSON中的创建者ID字段
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // 定义GTask JSON中的子实体字段
public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // 定义GTask JSON中的客户端版本字段
public final static String GTASK_JSON_COMPLETED = "completed"; // 定义GTask JSON中的完成状态字段
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // 定义GTask JSON中的当前列表ID字段
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // 定义GTask JSON中的默认列表ID字段
public final static String GTASK_JSON_DELETED = "deleted"; // 定义GTask JSON中的删除状态字段
public final static String GTASK_JSON_DEST_LIST = "dest_list"; // 定义GTask JSON中的目标列表字段
public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // 定义GTask JSON中的目标父级字段
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // 定义GTask JSON中的目标父级类型字段
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; // 定义GTask JSON中的实体增量字段
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // 定义GTask JSON中的实体类型字段
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // 定义GTask JSON中的获取已删除项字段
public final static String GTASK_JSON_ID = "id"; // 定义GTask JSON中的ID字段
public final static String GTASK_JSON_INDEX = "index"; // 定义GTask JSON中的索引字段
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // 定义GTask JSON中的最后修改时间字段
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // 定义GTask JSON中的最新同步点字段
public final static String GTASK_JSON_LIST_ID = "list_id"; // 定义GTask JSON中的列表ID字段
public final static String GTASK_JSON_LISTS = "lists"; // 定义GTask JSON中的列表字段
public final static String GTASK_JSON_NAME = "name"; // 定义GTask JSON中的名称字段
public final static String GTASK_JSON_NEW_ID = "new_id"; // 定义GTask JSON中的新ID字段
public final static String GTASK_JSON_NOTES = "notes"; // 定义GTask JSON中的备注字段
public final static String GTASK_JSON_PARENT_ID = "parent_id"; // 定义GTask JSON中的父级ID字段
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // 定义GTask JSON中的前一个兄弟ID字段
public final static String GTASK_JSON_RESULTS = "results"; // 定义GTask JSON中的结果字段
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // 定义GTask JSON中的源列表字段
public final static String GTASK_JSON_TASKS = "tasks"; // 定义GTask JSON中的任务字段
public final static String GTASK_JSON_TYPE = "type"; // 定义GTask JSON中的类型字段
public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // 定义GTask JSON中的组类型
public final static String GTASK_JSON_TYPE_TASK = "TASK"; // 定义GTask JSON中的任务类型
public final static String GTASK_JSON_USER = "user"; // 定义GTask JSON中的用户字段
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // 定义MIUI文件夹前缀
public final static String FOLDER_DEFAULT = "Default"; // 定义默认文件夹名称
public final static String FOLDER_CALL_NOTE = "Call_Note"; // 定义通话记录文件夹名称
public final static String FOLDER_META = "METADATA"; // 定义元数据文件夹名称
public final static String META_HEAD_GTASK_ID = "meta_gid"; // 定义元数据中的GTask 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,181 @@
/*
* 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; // 导入Android上下文类
import android.preference.PreferenceManager; // 导入Android偏好设置管理类
import net.micode.notes.R; // 导入资源文件
import net.micode.notes.ui.NotesPreferenceActivity; // 导入笔记偏好活动类
public class ResourceParser { // 定义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 // 红色标题背景
};
public static int getNoteBgResource(int id) { // 获取笔记背景资源
return BG_EDIT_RESOURCES[id]; // 返回对应id的背景资源
}
public static int getNoteTitleBgResource(int id) { // 获取笔记标题背景资源
return BG_EDIT_TITLE_RESOURCES[id]; // 返回对应id的标题背景资源
}
}
public static int getDefaultBgId(Context context) { // 获取默认背景ID
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( // 检查用户偏好设置
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); // 随机返回背景ID
} 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 // 红色单个背景
};
public static int getNoteBgFirstRes(int id) { // 获取笔记项第一个背景资源
return BG_FIRST_RESOURCES[id]; // 返回对应id的第一个背景资源
}
public static int getNoteBgLastRes(int id) { // 获取笔记项最后一个背景资源
return BG_LAST_RESOURCES[id]; // 返回对应id的最后一个背景资源
}
public static int getNoteBgSingleRes(int id) { // 获取笔记项单个背景资源
return BG_SINGLE_RESOURCES[id]; // 返回对应id的单个背景资源
}
public static int getNoteBgNormalRes(int id) { // 获取笔记项正常背景资源
return BG_NORMAL_RESOURCES[id]; // 返回对应id的正常背景资源
}
public static int getFolderBgRes() { // 获取文件夹背景资源
return R.drawable.list_folder; // 返回文件夹背景资源
}
}
public static class WidgetBgResources { // 定义内部类:小部件背景资源
private final static int [] BG_2X_RESOURCES = new int [] { // 2x小部件背景资源数组
R.drawable.widget_2x_yellow, // 黄色2x小部件背景
R.drawable.widget_2x_blue, // 蓝色2x小部件背景
R.drawable.widget_2x_white, // 白色2x小部件背景
R.drawable.widget_2x_green, // 绿色2x小部件背景
R.drawable.widget_2x_red, // 红色2x小部件背景
};
public static int getWidget2xBgResource(int id) { // 获取2x小部件背景资源
return BG_2X_RESOURCES[id]; // 返回对应id的2x小部件背景资源
}
private final static int [] BG_4X_RESOURCES = new int [] { // 4x小部件背景资源数组
R.drawable.widget_4x_yellow, // 黄色4x小部件背景
R.drawable.widget_4x_blue, // 蓝色4x小部件背景
R.drawable.widget_4x_white, // 白色4x小部件背景
R.drawable.widget_4x_green, // 绿色4x小部件背景
R.drawable.widget_4x_red // 红色4x小部件背景
};
public static int getWidget4xBgResource(int id) { // 获取4x小部件背景资源
return BG_4X_RESOURCES[id]; // 返回对应id的4x小部件背景资源
}
}
public static class TextAppearanceResources { // 定义内部类:文本外观资源
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { // 文本外观资源数组
R.style.TextAppearanceNormal, // 正常文本外观
R.style.TextAppearanceMedium, // 中等文本外观
R.style.TextAppearanceLarge, // 大文本外观
R.style.TextAppearanceSuper // 超大文本外观
};
public static int getTexAppearanceResource(int id) { // 获取文本外观资源
/**
* HACKME: IDbug
* ID
* {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) { // 检查ID是否超出范围
return BG_DEFAULT_FONT_SIZE; // 返回默认字体大小
}
return TEXTAPPEARANCE_RESOURCES[id]; // 返回对应id的文本外观资源
}
public static int getResourcesSize() { // 获取资源大小
return TEXTAPPEARANCE_RESOURCES.length; // 返回文本外观资源的长度
}
}
}

@ -0,0 +1,158 @@
/*
* 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 {
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);
}
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
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();
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
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 e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
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);
}
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);
startActivity(intent);
break;
default:
break;
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
}
}
}

@ -0,0 +1,65 @@
/*
* 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 {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_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);
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
}
}

@ -0,0 +1,30 @@
/*
* 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 {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

@ -0,0 +1,485 @@
/*
* 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;
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;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 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;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
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();
}
};
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) {
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;
}
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 {
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));
}
}
};
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;
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();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
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);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
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);
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);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@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;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
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));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
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();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
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());
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();
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
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);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,90 @@
/*
* 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;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long 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());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
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,61 @@
/*
* 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;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,80 @@
/*
* 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;
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
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);
}
}
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,873 @@
/*
* 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.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
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.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
/**
* Starting from the searched result
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump);
showToast(R.string.error_note_not_exist);
finish();
return false;
} else {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;
}
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support");
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
return true;
}
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
}
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
if (time > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
}

@ -0,0 +1,217 @@
/*
* 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;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
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);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
public void setIndex(int index) {
mIndex = index;
}
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@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);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
}
@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);
}
@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);
}
@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) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,224 @@
/*
* 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;
public class NoteItemData {
static final String [] PROJECTION = 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,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
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;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
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);
}
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) {
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;
}
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;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -0,0 +1,954 @@
/*
* 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.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
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;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
private ListEditState mState;
private BackgroundQueryHandler mBackgroundQueryHandler;
private NotesListAdapter mNotesListAdapter;
private ListView mNotesListView;
private Button mAddNewNote;
private boolean mDispatch;
private int mOriginY;
private int mDispatchY;
private TextView mTitleBar;
private long mCurrentFolderId;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem;
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
initResources();
/**
* Insert an introduction when user firstly use this application
*/
setAppInfoFromRawRes();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
char [] buf = new char[1024];
int len = 0;
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
} finally {
if(in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString());
if (note.saveNote()) {
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();
} else {
Log.e(TAG, "Save introduction note error");
return;
}
}
}
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
}
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mNotesListView = (ListView) findViewById(R.id.notes_list);
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
mNotesListView.setOnItemLongClickListener(this);
mNotesListAdapter = new NotesListAdapter(this);
mNotesListView.setAdapter(mNotesListAdapter);
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
mAddNewNote.setOnClickListener(this);
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
mState = ListEditState.NOTE_LIST;
mModeCallBack = new ModeCallback();
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
mNotesListAdapter.setChoiceMode(true);
mNotesListView.setLongClickable(false);
mAddNewNote.setVisibility(View.GONE);
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu();
return true;
}
});
return true;
}
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// Update dropdown menu
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
public void finishActionMode() {
mActionMode.finish();
}
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
switch (item.getItemId()) {
case R.id.delete:
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
}
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
showFolderListMenu(cursor);
} else {
Log.e(TAG, "Query folder failed");
}
break;
default:
return;
}
}
}
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode();
}
});
builder.show();
}
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// if not synced, delete notes directly
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens");
}
} else {
// in sync mode, we'll move the deleted note into the trash
// folder
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
return widgets;
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
mModeCallBack.finishActionMode();
}
}.execute();
}
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
if (!isSyncMode()) {
// if not synced, delete folder directly
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// in sync mode, we'll move the deleted folder into the trash folder
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
if (widgets != null) {
for (AppWidgetAttribute widget : widgets) {
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
}
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, data.getId());
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mState = ListEditState.CALL_RECORD_FOLDER;
mAddNewNote.setVisibility(View.GONE);
} else {
mState = ListEditState.SUB_FOLDER;
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mTitleBar.setText(R.string.call_record_folder_name);
} else {
mTitleBar.setText(data.getSnippet());
}
mTitleBar.setVisibility(View.VISIBLE);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note:
createNewNote();
break;
default:
break;
}
}
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
showSoftInput();
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet());
builder.setTitle(getString(R.string.menu_folder_change_name));
} else {
Log.e(TAG, "The long click data item is null");
return;
}
} else {
etName.setText("");
builder.setTitle(this.getString(R.string.menu_create_folder));
}
builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName);
}
});
final Dialog dialog = builder.setView(view).show();
final Button positive = (Button)dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etName);
String name = etName.getText().toString();
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show();
etName.setSelection(0, etName.length());
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name);
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values);
}
dialog.dismiss();
}
});
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
}
/**
* When the name edit text is null, disable the positive button
*/
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false);
} else {
positive.setEnabled(true);
}
}
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
}
@Override
public void onBackPressed() {
switch (mState) {
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
startAsyncNotesListQuery();
mTitleBar.setVisibility(View.GONE);
break;
case CALL_RECORD_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mState = ListEditState.NOTE_LIST;
mAddNewNote.setVisibility(View.VISIBLE);
mTitleBar.setVisibility(View.GONE);
startAsyncNotesListQuery();
break;
case NOTE_LIST:
super.onBackPressed();
break;
default:
break;
}
}
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet());
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view);
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete);
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
}
}
};
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null);
}
super.onContextMenuClosed(menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem);
break;
case MENU_FOLDER_DELETE:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_folder));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId());
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
// set sync or sync_cancel
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu);
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu);
} else {
Log.e(TAG, "Wrong state:" + mState);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {
exportNoteToText();
break;
}
case R.id.menu_sync: {
if (isSyncMode()) {
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this);
} else {
GTaskSyncService.cancelSync(this);
}
} else {
startPreferenceActivity();
}
break;
}
case R.id.menu_setting: {
startPreferenceActivity();
break;
}
case R.id.menu_new_note: {
createNewNote();
break;
}
case R.id.menu_search:
onSearchRequested();
break;
default:
break;
}
return true;
}
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
@Override
protected void onPostExecute(Integer result) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export));
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute();
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
NoteItemData item = ((NotesListItem) view).getItemData();
if (mNotesListAdapter.isInChoiceMode()) {
if (item.getType() == Notes.TYPE_NOTE) {
position = position - mNotesListView.getHeaderViewsCount();
mModeCallBack.onItemCheckedStateChanged(null, position, id,
!mNotesListAdapter.isSelectedItem(position));
}
return;
}
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER
|| item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item);
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST");
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item);
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER");
}
break;
default:
break;
}
}
}
}
private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) {
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
if (mNotesListView.startActionMode(mModeCallBack) != null) {
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
return false;
}
}

@ -0,0 +1,184 @@
/*
* 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;
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;
public int widgetType;
};
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
@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()));
}
}
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
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);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
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);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
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;
}
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
@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,122 @@
/*
* 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;
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
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);
}
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);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,388 @@
/*
* 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);
/* using the app icon for navigation */
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();
// need to set sync account automatically if user has added a new
// account
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;
}
}
}
}
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)) {
// the first time to set account
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
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);
// set button state
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)));
// set last sync time
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);
}
}
}
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();
}
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
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();
// clean up last sync time
setLastSyncTime(this, 0);
// clean up local gtask related info
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();
// clean up local gtask related info
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();
}
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
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();
}
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
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,132 @@
/*
* 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,
NoteColumns.BG_COLOR_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();
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])});
}
}
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
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);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_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);
/**
* Generate the pending intent to start host for the widget
*/
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);
}
}
}
protected abstract int getBgResourceId(int bgId);
protected abstract int getLayoutId();
protected abstract int getWidgetType();
}

@ -0,0 +1,47 @@
/*
* 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;
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -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.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;
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
protected int getLayoutId() {
return R.layout.widget_4x;
}
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
@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,21 @@
<?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

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

Loading…
Cancel
Save