Compare commits

...

1 Commits
main ... master

Author SHA1 Message Date
wsy 31c98c6cca v3
1 year ago

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

@ -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="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-12-13T12:59:39.583359Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\诗雨\.android\avd\Pixel_6_Pro_API_35.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

@ -0,0 +1,20 @@
<?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>
<option name="resolveExternalAnnotations" value="false" />
</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,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<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,59 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "net.micode.notes"
compileSdk = 34
defaultConfig {
applicationId = "net.micode.notes"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
packaging {
resources.excludes.add("META-INF/DEPENDENCIES");
resources.excludes.add("META-INF/NOTICE");
resources.excludes.add("META-INF/LICENSE");
resources.excludes.add("META-INF/LICENSE.txt");
resources.excludes.add("META-INF/NOTICE.txt");
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
// implementation(fileTree(mapOf(
// "dir" to "D:\\Code\\AndroidCode\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib",
// "include" to listOf("*.aar", "*.jar"),
// "exclude" to listOf("")
// )))
//修改为如下代码:
implementation(files("D:\\Program Files\\Android\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-osgi-4.5.14.jar"))
implementation(files("D:\\Program Files\\Android\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpclient-win-4.5.14.jar"))
implementation(files("D:\\Program Files\\Android\\Notesmaster\\httpcomponents-client-4.5.14-bin\\lib\\httpcore-4.4.16.jar"))
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

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

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

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

@ -0,0 +1,31 @@
package net.micode.notes;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
// 重写onCreate方法
@Override
protected void onCreate(Bundle savedInstanceState) {
// 调用父类的onCreate方法
super.onCreate(savedInstanceState);
// 启用EdgeToEdge
EdgeToEdge.enable(this);
// 设置布局
setContentView(R.layout.activity_main);
// 设置ViewCompat的OnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
// 获取系统栏的Insets
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
// 设置View的Padding
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
// 返回Insets
return insets;
});
}

@ -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.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
public class Contact {
// 定义一个静态的HashMap用于缓存联系人信息
private static HashMap<String, String> sContactCache;
// 定义一个常量,用于打印日志
private static final String TAG = "Contact";
// 定义一个常量用于查询联系人信息的SQL语句
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) {
// 如果联系人缓存为空则创建一个新的HashMap
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 如果sContactCache中包含phoneNumber键
if(sContactCache.containsKey(phoneNumber)) {
// 返回sContactCache中phoneNumber键对应的值
return sContactCache.get(phoneNumber);
}
// 将电话号码转换为Caller ID的最小匹配格式并替换掉"+"号
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 通过ContentResolver查询Data表获取电话号码对应的联系人姓名
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());
// 返回null
return null;
} finally {
// 关闭游标
cursor.close();
}
} else {
// 没有匹配的联系人
Log.d(TAG, "No contact matched with number:" + phoneNumber);
// 返回null
return null;
}
}
}

@ -0,0 +1,327 @@
/*
* 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;
public class Notes {
// 定义Notes类的权限
public static final String AUTHORITY = "micode_notes";
// 定义Notes类的标签
public static final String TAG = "Notes";
// 定义Notes类的类型笔记
public static final int TYPE_NOTE = 0;
// 定义Notes类的类型文件夹
public static final int TYPE_FOLDER = 1;
// 定义Notes类的类型系统
public static final int TYPE_SYSTEM = 2;
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID
public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID
public static final int ID_CALL_RECORD_FOLDER = -2; // 呼叫记录文件夹ID
public static final int ID_TRASH_FOLER = -3; // 回收站文件夹ID
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色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; // 呼叫笔记类型
}
/**
* Uri to query all notes and folders
*/
// 定义一个常量表示Note表的Uri
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
// 定义一个常量表示内容提供者的数据URI
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量表示ID
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量表示父节点的ID
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 声明一个常量,表示创建日期
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量,表示修改日期
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量,表示警报日期
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
// 定义一个常量,用于表示代码片段
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
// 声明一个常量用于表示小部件的ID
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
// 声明一个常量,表示小部件类型
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量用于表示背景颜色的ID
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*/
// 定义一个常量,表示是否有附件
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量,表示笔记数量
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
// 定义一个常量,表示类型
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量用于同步的ID
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
// 定义一个常量,表示本地修改
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
// 定义一个常量表示原始父级ID
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
// 定义一个常量用于存储任务ID
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
// 定义一个常量,表示版本号
public static final String VERSION = "version";
}
// 定义一个接口DataColumns
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量表示ID
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
// 定义一个常量表示MIME类型
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量表示笔记的ID
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 声明一个常量,表示创建日期
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 声明一个常量,表示修改日期
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
// 定义一个常量CONTENT值为"content"
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 定义一个常量DATA1值为"data1"
// 定义一个常量DATA1值为"data1"
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 定义一个常量DATA2值为"data2"
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 定义一个常量DATA3值为"data3"
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 定义一个常量DATA4值为"data4"
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 定义一个常量DATA5值为"data5"
public static final String DATA5 = "data5";
}
// 实现DataColumns接口的TextNote类
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
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";
// 定义一个常量,表示内容项类型为文本笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 定义一个常量表示内容URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// 定义一个名为CallNote的公共静态内部类实现DataColumns接口
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
// 定义一个常量,表示调用日期
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
// 定义一个常量,表示电话号码
public static final String PHONE_NUMBER = DATA3;
// 定义一个常量,表示内容类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 定义一个常量,表示内容项类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 定义一个常量表示内容URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,489 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
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;
// 定义创建NOTE表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
// 定义ID列类型为INTEGER为主键
NoteColumns.ID + " INTEGER PRIMARY KEY," +
// 定义PARENT_ID列类型为INTEGER默认值为0
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
// 定义ALERTED_DATE列类型为INTEGER默认值为0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
// 定义BG_COLOR_ID列类型为INTEGER默认值为0
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
// 定义CREATED_DATE列类型为INTEGER默认值为当前时间戳
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
// 定义HAS_ATTACHMENT列类型为INTEGER默认值为0
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
// 定义MODIFIED_DATE列类型为INTEGER默认值为当前时间戳
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
// 定义NOTES_COUNT列类型为INTEGER默认值为0
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
// 定义SNIPPET列类型为TEXT默认值为空字符串
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
// 定义TYPE列类型为INTEGER默认值为0
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
// 定义WIDGET_ID列类型为INTEGER默认值为0
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
// 定义WIDGET_TYPE列类型为INTEGER默认值为-1
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
// 定义SYNC_ID列类型为INTEGER默认值为0
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
// 定义LOCAL_MODIFIED列类型为INTEGER默认值为0
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
// 定义ORIGIN_PARENT_ID列类型为INTEGER默认值为0
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
// 定义GTASK_ID列类型为TEXT默认值为空字符串
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
// 定义VERSION列类型为INTEGER默认值为0
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
// 创建数据表包含ID、MIME_TYPE、NOTE_ID、CREATED_DATE、MODIFIED_DATE、CONTENT、DATA1、DATA2、DATA3、DATA4、DATA5等字段
DataColumns.ID + " INTEGER PRIMARY KEY," + // ID字段主键
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME_TYPE字段文本类型不能为空
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // NOTE_ID字段整型不能为空默认值为0
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // CREATED_DATE字段整型不能为空默认值为当前时间戳
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // MODIFIED_DATE字段整型不能为空默认值为当前时间戳
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // CONTENT字段文本类型不能为空默认值为空字符串
DataColumns.DATA1 + " INTEGER," + // DATA1字段整型
DataColumns.DATA2 + " INTEGER," + // DATA2字段整型
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // DATA3字段文本类型不能为空默认值为空字符串
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // DATA4字段文本类型不能为空默认值为空字符串
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // DATA5字段文本类型不能为空默认值为空字符串
")";
// 创建一个名为note_id_index的索引如果不存在则创建
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* Increase folder's note count when move note to the folder
*/
// 定义一个静态常量用于创建一个触发器在更新Note表的PARENT_ID字段后增加对应文件夹的NOTES_COUNT字段
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
// 创建一个触发器,当更新文件夹时,增加文件夹数量
"CREATE TRIGGER increase_folder_count_on_update "+ // 创建一个触发器名称为increase_folder_count_on_update
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + // 在Note表上当PARENT_ID字段更新后
" BEGIN " + // 开始
// 更新表TABLE.NOTE将NoteColumns.NOTES_COUNT字段加1
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
// 条件为NoteColumns.ID等于new.NoteColumns.PARENT_ID
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* Decrease folder's note count when move note from folder
*/
// 定义一个常量,用于在更新触发器上减少文件夹计数
// 定义一个常量,用于表示在更新触发器中减少文件夹数量的操作
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
// 添加注释
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
// 检查旧记录的父ID是否与当前记录的ID相同
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
* Increase folder's note count when insert new note to the folder
*/
// 定义一个常量,用于表示在插入时增加文件夹数量的触发器
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE + // 在NOTE表上创建一个名为increase_folder_count_on_insert的触发器
" BEGIN " +
" UPDATE " + TABLE.NOTE + // 更新NOTE表
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 将NOTES_COUNT字段加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 条件为ID等于新记录的PARENT_ID
" END";
/**
* Decrease folder's note count when delete note from the folder
*/
// 定义一个常量,用于在删除文件夹时减少文件夹计数器的触发器
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
// 创建一个触发器在删除NOTE表中的数据后减少FOLDER表中的文件夹数量
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
// 更新表"TABLE.NOTE",将"NoteColumns.NOTES_COUNT"字段的值减1
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
// 在"NoteColumns.ID"字段的值等于"old." + NoteColumns.PARENT_ID"的情况下
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
// 并且"NoteColumns.NOTES_COUNT"字段的值大于0的情况下
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* Update note's content when insert data with type {@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" +
// 更新表NOTE将SNIPPET字段设置为new表中的CONTENT字段
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
// 如果NOTE_ID字段等于new表中的NOTE_ID字段
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
// 定义一个常量,用于更新触发器时更新数据注释的内容
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA + // 创建一个名为update_note_content_on_update的触发器在更新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";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
// 定义一个常量,用于表示删除触发器时更新数据注释的内容
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " + // 创建一个名为update_note_content_on_delete的触发器
" AFTER delete ON " + TABLE.DATA + // 在删除操作后触发
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
// 更新表NOTE将SNIPPET字段设置为空字符串
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
// 添加条件只更新ID等于old.Note_ID的记录
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Delete datas belong to note which has been deleted
*/
// 定义一个常量,用于删除触发器时删除数据
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";
/**
* Delete notes belong to folder which has been deleted
*/
// 定义一个常量,用于删除文件夹时触发删除笔记的触发器
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";
/**
* Move notes belong to folder which has been moved to trash folder
*/
// 定义一个静态常量用于创建一个触发器当更新Note表时如果新父ID等于垃圾文件夹ID则将旧父ID更新为垃圾文件夹ID
// 私有静态常量,用于触发将文件夹移动到垃圾桶的操作
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) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建Note表执行SQL语句重新创建Note表触发器创建系统文件夹并打印日志
public void createNoteTable(SQLiteDatabase db) {
// 执行创建笔记表的SQL语句
db.execSQL(CREATE_NOTE_TABLE_SQL);
// 重新创建笔记表触发器
reCreateNoteTableTriggers(db);
// 创建系统文件夹
createSystemFolder(db);
// 打印日志信息表示note表已经创建
Log.d(TAG, "note table has been created");
}
// 重新创建NoteTable触发器
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除increase_folder_count_on_update触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
// 删除decrease_folder_count_on_update触发器
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
// 删除decrease_folder_count_on_delete触发器
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
// 删除delete_data_on_delete触发器
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
// 删除increase_folder_count_on_insert触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
// 删除folder_delete_notes_on_delete触发器
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
// 删除folder_move_notes_on_trash触发器
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对象用于存储数据
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*/
// 将Notes.ID_CALL_RECORD_FOLDER的值放入values中
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
// 将Notes.TYPE_SYSTEM的值放入values中
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 将values中的值插入到TABLE.NOTE表中
db.insert(TABLE.NOTE, null, values);
/**
* root folder which is default folder
*/
// 清空values
values.clear();
// 将Notes.ID_ROOT_FOLDER放入values中
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
// 将Notes.TYPE_SYSTEM放入values中
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 将values插入到TABLE.NOTE表中
db.insert(TABLE.NOTE, null, values);
/**
* temporary folder which is used for moving note
*/
// 清空values
values.clear();
// 将NoteColumns.ID的值设为Notes.ID_TEMPARAY_FOLDER
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
// 将NoteColumns.TYPE的值设为Notes.TYPE_SYSTEM
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 将values插入到TABLE.NOTE表中
db.insert(TABLE.NOTE, null, values);
/**
* create trash folder
*/
// 清空values
values.clear();
// 将ID设置为垃圾桶文件夹的ID
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
// 将类型设置为系统类型
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 将values插入到NOTE表中
db.insert(TABLE.NOTE, null, values);
}
// 创建数据表
public void createDataTable(SQLiteDatabase db) {
// 执行创建数据表的SQL语句
db.execSQL(CREATE_DATA_TABLE_SQL);
// 重新创建数据表触发器
reCreateDataTableTriggers(db);
// 执行创建数据表索引的SQL语句
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
// 打印日志,表示数据表已经创建
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);
}
// 获取NotesDatabaseHelper实例
static synchronized NotesDatabaseHelper getInstance(Context context) {
// 如果实例为空,则创建新的实例
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
// 返回实例
return mInstance;
}
// 创建数据库表
@Override
public void onCreate(SQLiteDatabase db) {
// 创建note表
createNoteTable(db);
// 创建data表
createDataTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 定义两个布尔变量用于判断是否需要重新创建触发器和跳过v2版本
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 如果旧版本是1则升级到v2版本并跳过v2版本
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 如果旧版本是2并且没有跳过v2版本则升级到v3版本并重新创建触发器
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 如果旧版本为3则升级到版本4
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果需要重新创建触发器,则重新创建笔记表和数据表触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 如果旧版本不等于新版本,则抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
private void upgradeToV2(SQLiteDatabase db) {
// 删除旧版本的NOTE表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
// 删除旧版本的DATA表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
// 创建新版本的NOTE表
createNoteTable(db);
// 创建新版本的DATA表
createDataTable(db);
}
// 将数据库升级到V3版本
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
// 删除名为update_note_modified_date_on_insert的触发器如果存在
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
// 删除名为update_note_modified_date_on_delete的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
// 删除名为update_note_modified_date_on_update的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
// 向数据库中的NOTE表添加一个名为GTASK_ID的列类型为TEXT默认值为空字符串
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
// 创建一个ContentValues对象用于存储要插入的数据
ContentValues values = new ContentValues();
// 将ID_TRASH_FOLER的值放入ContentValues对象中
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
// 将TYPE_SYSTEM的值放入ContentValues对象中
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// 将ContentValues对象中的数据插入到数据库的NOTE表中
db.insert(TABLE.NOTE, null, values);
}
// 将数据库升级到V4版本
private void upgradeToV4(SQLiteDatabase db) {
// 执行SQL语句在NOTE表中添加一个名为VERSION的列类型为INTEGER默认值为0
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -0,0 +1,441 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// 定义一个UriMatcher对象用于匹配Uri
private static final UriMatcher mMatcher;
// 定义一个NotesDatabaseHelper对象用于操作数据库
private NotesDatabaseHelper mHelper;
// 定义一个常量用于标识NotesProvider
private static final String TAG = "NotesProvider";
// 定义一个常量用于标识URI_NOTE
private static final int URI_NOTE = 1;
// 定义一个常量用于标识URI_NOTE_ITEM
private static final int URI_NOTE_ITEM = 2;
// 定义一个常量用于标识URI_DATA
private static final int URI_DATA = 3;
// 定义一个常量用于标识URI_DATA_ITEM
private static final int URI_DATA_ITEM = 4;
// 定义一个常量用于标识URI_SEARCH
private static final int URI_SEARCH = 5;
// 定义一个常量用于标识URI_SEARCH_SUGGEST
private static final int URI_SEARCH_SUGGEST = 6;
// 静态代码块用于初始化UriMatcher对象
static {
// 创建UriMatcher对象NO_MATCH表示没有匹配的URI
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 添加URI匹配规则匹配Notes.AUTHORITY下的note匹配成功返回URI_NOTE
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 添加URI匹配规则匹配Notes.AUTHORITY下的note/#匹配成功返回URI_NOTE_ITEM
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 添加URI匹配规则匹配Notes.AUTHORITY下的data匹配成功返回URI_DATA
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 添加URI匹配规则匹配Notes.AUTHORITY下的data/#匹配成功返回URI_DATA_ITEM
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 添加URI匹配规则匹配Notes.AUTHORITY下的search匹配成功返回URI_SEARCH
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
// 添加URI匹配规则匹配Notes.AUTHORITY下的search/*匹配成功返回URI_SEARCH_SUGGEST
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
// 添加URI匹配规则匹配Notes.AUTHORITY下的search/*/*匹配成功返回URI_SEARCH_SUGGEST
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
// 定义一个常量,用于搜索笔记的投影
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
// 将笔记的ID作为搜索建议的额外数据
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
// 将笔记的摘要作为搜索建议的文本1
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
// 将笔记的摘要作为搜索建议的文本2
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
// 将搜索结果的图标设置为drawable/search_result
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
// 将搜索建议的意图动作设置为Intent.ACTION_VIEW
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
// 将搜索建议的意图数据设置为Notes.TextNote.CONTENT_TYPE
+ "'" + 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
// 笔记的父ID不等于垃圾桶文件夹
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
@Override
// 重写onCreate方法
public boolean onCreate() {
// 获取NotesDatabaseHelper的实例
mHelper = NotesDatabaseHelper.getInstance(getContext());
// 返回true
return true;
}
@Override
// 重写query方法用于查询数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// 声明一个Cursor对象
Cursor c = null;
// 获取可读的数据库
SQLiteDatabase db = mHelper.getReadableDatabase();
// 声明一个id变量
String id = null;
// 根据传入的uri执行不同的数据库查询操作
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 查询note表
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
// 查询note表中指定id的记录
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
// 查询data表
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
// 查询data表中指定id的记录
id = uri.getPathSegments().get(1);
// 查询数据库中指定表的数据返回一个Cursor对象
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
// 搜索URI
case URI_SEARCH:
// 搜索建议URI
case URI_SEARCH_SUGGEST:
// 查询搜索结果
// 如果sortOrder不为空或者projection不为空则抛出异常
if (sortOrder != null || projection != null) {
// 抛出IllegalArgumentException异常异常信息为"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
// 获取搜索字符串
String searchString = null;
// 如果匹配到URI_SEARCH_SUGGEST
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
// 如果URI的路径段大于1
if (uri.getPathSegments().size() > 1) {
// 获取路径段的第二个元素作为搜索字符串
searchString = uri.getPathSegments().get(1);
}
} else {
// 获取uri中的查询参数pattern
searchString = uri.getQueryParameter("pattern");
}
// 如果查询参数为空则返回null
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);
}
// 如果结果集不为空则设置通知URI
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
// 返回结果集
return c;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 获取可写的数据库
SQLiteDatabase db = mHelper.getWritableDatabase();
// 定义变量用于存储插入的数据ID
long dataId = 0, noteId = 0, insertedId = 0;
// 根据传入的URI进行匹配
switch (mMatcher.match(uri)) {
// 如果匹配到URI_NOTE
case URI_NOTE:
// 在NOTE表中插入数据并获取插入的数据ID
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
// 如果匹配到URI_DATA
case URI_DATA:
// 如果values中包含NOTE_ID
if (values.containsKey(DataColumns.NOTE_ID)) {
// 获取NOTE_ID
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
// 否则,打印错误日志
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
// 在DATA表中插入数据并获取插入的数据ID
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
// 如果没有匹配到任何URI
default:
// 抛出异常
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 初始化删除的记录数
int count = 0;
// 初始化ID
String id = null;
// 获取可写的数据库
SQLiteDatabase db = mHelper.getWritableDatabase();
// 初始化删除数据标志
boolean deleteData = false;
// 根据URI匹配不同的删除操作
switch (mMatcher.match(uri)) {
// 删除笔记
case URI_NOTE:
// 添加删除条件
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
// 执行删除操作
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
// 删除笔记项
case URI_NOTE_ITEM:
// 获取笔记项的ID
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
// 将id转换为long类型
long noteId = Long.valueOf(id);
// 如果noteId小于等于0则跳出循环
if (noteId <= 0) {
break;
}
// 删除指定id的note
// 删除指定ID的笔记
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
// 处理URI_DATA
case URI_DATA:
// 删除数据
count = db.delete(TABLE.DATA, selection, selectionArgs);
// 标记删除数据
deleteData = true;
break;
// 删除数据项
case URI_DATA_ITEM:
// 获取数据项的id
id = uri.getPathSegments().get(1);
// 删除数据项
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
// 标记删除数据
deleteData = true;
break;
// 未知URI
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果删除了数据
if (count > 0) {
// 如果删除了数据,通知内容解析器
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知内容解析器
getContext().getContentResolver().notifyChange(uri, null);
}
// 返回删除的条数
return count;
}
// 重写update方法用于更新数据库中的数据
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 定义一个计数器,用于记录更新的数据条数
int count = 0;
// 定义一个字符串用于存储要更新的数据的id
String id = null;
// 获取可写的数据库
SQLiteDatabase db = mHelper.getWritableDatabase();
// 定义一个布尔值,用于判断是否更新了数据
boolean updateData = false;
// 根据传入的uri进行匹配根据匹配结果执行不同的操作
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 增加note的版本号
increaseNoteVersion(-1, selection, selectionArgs);
// 更新note表
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 获取uri中的id
id = uri.getPathSegments().get(1);
// 增加note的版本号
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
// 更新note表
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 更新data表
count = db.update(TABLE.DATA, values, selection, selectionArgs);
// 标记更新data
updateData = true;
break;
case URI_DATA_ITEM:
// 获取uri中的id
id = uri.getPathSegments().get(1);
// 更新data表
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
// 标记更新data
updateData = true;
break;
default:
// 抛出异常未知uri
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果count大于0
if (count > 0) {
// 如果updateData为true
if (updateData) {
// 通知ContentResolver更新Notes.CONTENT_NOTE_URI
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知ContentResolver更新uri
getContext().getContentResolver().notifyChange(uri, null);
}
// 返回count
return count;
}
// 解析selection字符串
private String parseSelection(String selection) {
// 如果selection不为空
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
// 创建一个StringBuilder对象用于拼接SQL语句
StringBuilder sql = new StringBuilder(120);
// 拼接UPDATE语句
sql.append("UPDATE ");
// 拼接表名
sql.append(TABLE.NOTE);
// 拼接SET语句
sql.append(" SET ");
// 拼接版本号字段
sql.append(NoteColumns.VERSION);
// 拼接版本号增加1的语句
sql.append("=" + NoteColumns.VERSION + "+1 ");
// 如果id大于0或者selection不为空则拼接WHERE语句
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
// 如果id大于0则拼接id等于指定值的语句
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
// 判断selection是否为空
if (!TextUtils.isEmpty(selection)) {
// 如果id大于0则调用parseSelection方法解析selection否则直接赋值给selectString
String selectString = id > 0 ? parseSelection(selection) : selection;
// 遍历selectionArgs数组将每个元素替换selectString中的第一个问号
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
// 将selectString追加到sql中
sql.append(selectString);
}
// 获取可写的数据库
mHelper.getWritableDatabase().execSQL(sql.toString());
}
@Override
public String getType(Uri 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;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
public String getRelatedGid() {
return mRelatedGid;
}
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -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;
import org.json.JSONObject;
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.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;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
public long getId() {
return mDataId;
}
}

@ -0,0 +1,505 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999;
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;
public static final int ALERTED_DATE_COLUMN = 1;
public static final int BG_COLOR_ID_COLUMN = 2;
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;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private int mHasAttachment;
private long mModifiedDate;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private long mOriginParent;
private long mVersion;
private ContentValues mDiffNoteValues;
private ArrayList<SqlData> mDataList;
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true;
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
private void loadFromCursor(long id) {
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext();
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = createDate;
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = hasAttachment;
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate;
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = widgetId;
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
sqlData.setContent(data);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
}
return true;
}
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
public long getId() {
return mId;
}
public long getParentId() {
return mParentId;
}
public String getSnippet() {
return mSnippet;
}
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
}
}
}
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues.clear();
mIsCreate = false;
}
}

@ -0,0 +1,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();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js;
}
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
}
return js;
}
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
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;
}
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
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;
}
}
}
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,343 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends 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) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
return js;
}
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
return js;
}
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
Log.e(TAG, "invalid system folder");
} else {
Log.e(TAG, "error type");
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
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) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(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) {
for (Task task : mChildren) {
if (task.getGid().equals(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;
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 {
private static final long serialVersionUID = 2107610287180234136L;
public NetworkFailureException() {
super();
}
public NetworkFailureException(String paramString) {
super(paramString);
}
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}

@ -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.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义一个静态常量用于标识通知的ID
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义一个接口,用于监听任务完成
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) {
// Notification notification = new Notification(R.drawable.notification, mContext
// .getString(tickerId), System.currentTimeMillis());
// notification.defaults = Notification.DEFAULT_LIGHTS;
// notification.flags = Notification.FLAG_AUTO_CANCEL;
// PendingIntent pendingIntent;
// if (tickerId != R.string.ticker_success) {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesPreferenceActivity.class), 0);
//
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
// 显示通知
private void showNotification(int tickerId, String content) {
// 创建一个PendingIntent用于在通知被点击时启动相应的Activity
PendingIntent pendingIntent;
if (tickerId != R.string.ticker_success) {
// 如果tickerId不等于R.string.ticker_success则创建一个PendingIntent用于启动NotesPreferenceActivity
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
// 如果tickerId等于R.string.ticker_success则创建一个PendingIntent用于启动NotesListActivity
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
// 创建一个Notification.Builder对象用于构建通知
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
// 重写doInBackground方法用于在后台执行同步操作
protected Integer doInBackground(Void... unused) {
// 发布进度,显示同步账户名称
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
// 调用mTaskManager的sync方法执行同步操作并返回结果
return mTaskManager.sync(mContext, this);
}
@Override
// 重写父类方法,当进度更新时调用
protected void onProgressUpdate(String... progress) {
// 显示通知通知内容为正在同步进度为progress[0]
showNotification(R.string.ticker_syncing, progress[0]);
// 如果上下文是GTaskSyncService的实例
if (mContext instanceof GTaskSyncService) {
// 发送广播广播内容为progress[0]
((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方法
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,852 @@
/*
* 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 {
// 定义TAG常量用于日志输出
private static final String TAG = GTaskClient.class.getSimpleName();
// 定义GTask的URL
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 定义GTask的GET URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 定义GTask的POST URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 定义GTaskClient的实例
private static GTaskClient mInstance = null;
// 定义DefaultHttpClient实例
private DefaultHttpClient mHttpClient;
// 定义GET URL
private String mGetUrl;
private String mPostUrl;
// 客户端版本
private long mClientVersion;
// 是否已登录
private boolean mLoggedin;
// 最后一次登录时间
private long mLastLoginTime;
// 动作ID
private int mActionId;
// 账户信息
private Account mAccount;
// 更新数组
private JSONArray mUpdateArray;
// 构造函数
private GTaskClient() {
// 初始化HTTP客户端
mHttpClient = null;
// 获取URL
mGetUrl = GTASK_GET_URL;
// 提交URL
mPostUrl = GTASK_POST_URL;
// 客户端版本
mClientVersion = -1;
// 是否已登录
mLoggedin = false;
// 最后一次登录时间
mLastLoginTime = 0;
// 动作ID
mActionId = 1;
// 账户信息
mAccount = null;
// 更新数组
mUpdateArray = null;
}
// 获取GTaskClient实例
public static synchronized GTaskClient getInstance() {
// 如果实例为空则创建一个新的GTaskClient实例
if (mInstance == null) {
mInstance = new GTaskClient();
}
// 返回单例实例
return mInstance;
}
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
final long interval = 1000 * 60 * 5;
// 如果上次登录时间加上5分钟小于当前时间则表示cookie已过期需要重新登录
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// need to re-login after account switch
// 如果已经登录并且同步账户的名称与NotesPreferenceActivity中的同步账户名称不相等则将mLoggedin设置为false
if (mLoggedin
// 判断条件如果当前同步账户的名称与NotesPreferenceActivity中的同步账户名称不相等
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
// 设置登录状态为未登录
mLoggedin = false;
}
// 判断是否已经登录
if (mLoggedin) {
// 如果已经登录,打印日志信息
Log.d(TAG, "already logged in");
// 返回true
return true;
}
// 获取当前时间
mLastLoginTime = System.currentTimeMillis();
// 登录Google账号
String authToken = loginGoogleAccount(activity, false);
// 如果登录失败
if (authToken == null) {
// 打印错误日志
Log.e(TAG, "login google account failed");
// 返回false
return false;
}
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 如果mAccount.name不是以"gmail.com"或"googlemail.com"结尾,则执行以下代码
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
// 创建一个StringBuilder对象并将GTASK_URL和"a/"拼接起来
int index = mAccount.name.indexOf('@') + 1;
// 获取mAccount.name中'@'符号的索引并加1
String suffix = mAccount.name.substring(index);
// 获取mAccount.name中'@'符号后面的字符串
url.append(suffix + "/");
// 将suffix和"/"拼接起来并添加到url中
mGetUrl = url.toString() + "ig";
// 将url和"ig"拼接起来并赋值给mGetUrl
mPostUrl = url.toString() + "r/ig";
// 将url和"r/ig"拼接起来并赋值给mPostUrl
if (tryToLoginGtask(activity, authToken)) {
// 如果tryToLoginGtask方法返回true则执行以下代码
mLoggedin = true;
// 将mLoggedin设置为true
}
}
// try to login with google official url
// 如果没有登录
if (!mLoggedin) {
// 设置获取URL
mGetUrl = GTASK_GET_URL;
// 设置提交URL
mPostUrl = GTASK_POST_URL;
// 尝试登录
if (!tryToLoginGtask(activity, authToken)) {
// 如果登录失败返回false
return false;
}
}
// 设置登录状态为已登录
// 返回true
mLoggedin = true;
return true;
}
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
// 定义一个字符串变量authToken
String authToken;
// 获取AccountManager实例
AccountManager accountManager = AccountManager.get(activity);
// 获取所有类型为com.google的账户
Account[] accounts = accountManager.getAccountsByType("com.google");
// 如果没有可用的google账户
if (accounts.length == 0) {
// 打印错误日志
Log.e(TAG, "there is no available google account");
// 返回null
return null;
}
// 获取同步账户名称
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
// 初始化账户为null
Account account = null;
// 遍历账户列表
for (Account a : accounts) {
// 如果账户名称与同步账户名称相同
if (a.name.equals(accountName)) {
// 将账户赋值给account
account = a;
// 跳出循环
break;
}
}
// 如果account不为null
if (account != null) {
// 将account赋值给mAccount
mAccount = account;
} else {
// 如果account为null则打印错误日志
Log.e(TAG, "unable to get an account with the same name in the settings");
// 返回null
return null;
}
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
// 获取认证令牌的结果
Bundle authTokenBundle = accountManagerFuture.getResult();
// 从结果中获取认证令牌
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要无效化令牌则无效化令牌并重新登录Google账户
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
// 获取认证令牌失败,打印错误日志
Log.e(TAG, "get auth token failed");
authToken = null;
}
// 返回认证令牌
return authToken;
}
// 尝试登录Gtask
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 如果登录失败
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
// 登录Google账号
authToken = loginGoogleAccount(activity, true);
// 如果登录失败
if (authToken == null) {
// 打印错误日志
Log.e(TAG, "login google account failed");
return false;
}
// 判断是否登录gtask失败
if (!loginGtask(authToken)) {
// 打印错误日志
Log.e(TAG, "login gtask failed");
// 返回false
return false;
}
}
// 返回true
return true;
}
// 登录Gtask
private boolean loginGtask(String authToken) {
// 设置连接超时时间
int timeoutConnection = 10000;
// 设置Socket超时时间
int timeoutSocket = 15000;
// 创建HttpParams对象
HttpParams httpParameters = new BasicHttpParams();
// 设置连接超时时间
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// 设置Socket超时时间
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
// 创建DefaultHttpClient对象
mHttpClient = new DefaultHttpClient(httpParameters);
// 创建BasicCookieStore对象
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
// 设置CookieStore
mHttpClient.setCookieStore(localBasicCookieStore);
// 设置是否使用Expect: 100-continue
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
// 构造登录URL
String loginUrl = mGetUrl + "?auth=" + authToken;
// 创建HttpGet对象
HttpGet httpGet = new HttpGet(loginUrl);
// 执行请求
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
// 获取CookieStore中的所有Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
// 定义一个布尔变量用于判断是否有认证Cookie
boolean hasAuthCookie = false;
// 遍历所有Cookie
for (Cookie cookie : cookies) {
// 如果Cookie的名称包含"GTL"则说明有认证Cookie
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
// 如果没有认证Cookie则输出警告日志
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// get the client version
// 获取响应内容
String resString = getResponseContent(response.getEntity());
// 定义js开始字符串
String jsBegin = "_setup(";
// 定义js结束字符串
String jsEnd = ")}</script>";
// 获取js开始字符串在响应内容中的位置
int begin = resString.indexOf(jsBegin);
// 获取js结束字符串在响应内容中的位置
int end = resString.lastIndexOf(jsEnd);
// 定义js字符串
String jsString = null;
// 如果开始字符串和结束字符串都存在,并且开始字符串的位置在结束字符串之前
if (begin != -1 && end != -1 && begin < end) {
// 获取js字符串
jsString = resString.substring(begin + jsBegin.length(), end);
}
// 将jsString转换为JSONObject对象
JSONObject js = new JSONObject(jsString);
// 获取JSONObject对象中的v字段的值并将其赋值给mClientVersion变量
mClientVersion = js.getLong("v");
// 捕获JSONException异常并打印异常信息
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
// 捕获所有其他异常,并打印异常信息
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
// 返回true表示成功获取到mClientVersion的值
return true;
}
private int getActionId() {
// 获取动作ID
return mActionId++;
}
private HttpPost createHttpPost() {
// 创建HttpPost对象
HttpPost httpPost = new HttpPost(mPostUrl);
// 设置请求头Content-Type为application/x-www-form-urlencoded;charset=utf-8
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
// 设置请求头AT为1
httpPost.setHeader("AT", "1");
// 返回HttpPost对象
return httpPost;
}
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();
// 如果contentEncoding不为空且等于gzip则将input设置为GZIPInputStream
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
// 如果contentEncoding不为空且等于deflate则将input设置为InflaterInputStream
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
// 创建一个Inflater对象true表示使用ZLIB压缩算法
Inflater inflater = new Inflater(true);
// 创建一个InflaterInputStream对象将entity.getContent()作为输入流inflater作为解压缩算法
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
// 创建一个InputStreamReader对象用于将输入流转换为字符流
InputStreamReader isr = new InputStreamReader(input);
// 创建一个BufferedReader对象用于读取字符流
BufferedReader br = new BufferedReader(isr);
// 创建一个StringBuilder对象用于存储读取的字符流
StringBuilder sb = new StringBuilder();
while (true) {
// 读取一行数据
String buff = br.readLine();
// 如果读取到的数据为空,则返回已经读取到的数据
if (buff == null) {
return sb.toString();
}
// 将读取到的数据添加到StringBuilder中
sb = sb.append(buff);
}
} finally {
// 关闭输入流
input.close();
}
}
// 发送post请求
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
// 如果没有登录,抛出异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
// 创建HttpPost对象
HttpPost httpPost = createHttpPost();
try {
// 创建一个LinkedList对象用于存储BasicNameValuePair对象
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
// 将一个BasicNameValuePair对象添加到LinkedList中该对象包含一个键值对键为"r"值为js.toString()的字符串表示
list.add(new BasicNameValuePair("r", js.toString()));
// 创建一个UrlEncodedFormEntity对象将list参数转换为UTF-8编码的表单数据
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
// 将UrlEncodedFormEntity对象设置为httpPost的实体
httpPost.setEntity(entity);
// execute the post
// 执行httpPost请求
HttpResponse response = mHttpClient.execute(httpPost);
// 获取响应内容
String jsString = getResponseContent(response.getEntity());
// 返回一个JSONObject对象
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
// 捕获ClientProtocolException异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出NetworkFailureException异常
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
// 捕获IOException异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出NetworkFailureException异常
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
// 捕获JSONException异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出ActionFailureException异常
throw new ActionFailureException("unable to convert response content to jsonobject");
} catch (Exception e) {
// 捕获其他异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出ActionFailureException异常
throw new ActionFailureException("error occurs when posting request");
}
}
public void createTask(Task task) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象
JSONArray actionList = new JSONArray();
// action_list
// 将task的createAction方法返回的值放入actionList中
actionList.put(task.getCreateAction(getActionId()));
// 将actionList放入jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 将mClientVersion放入jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
// 获取返回的JSON对象中的results数组中的第一个元素
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 将返回的JSON对象中的new_id赋值给task的gid
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 打印错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出异常
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 创建任务列表
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象
JSONArray actionList = new JSONArray();
// action_list
// 将tasklist中获取的创建动作添加到actionList中
actionList.put(tasklist.getCreateAction(getActionId()));
// 将actionList添加到jsPost中键为GTASK_JSON_ACTION_LIST
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
// 从jsResponse中获取GTaskStringUtils.GTASK_JSON_RESULTS数组中的第一个元素并将其转换为JSONObject类型
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 将JSONObject中的GTaskStringUtils.GTAS键对应的值设置到tasklist的gid属性中
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 捕获JSONException异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出自定义异常ActionFailureException并传入异常信息
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
public void commitUpdate() throws NetworkFailureException {
// 如果mUpdateArray不为空则执行以下操作
if (mUpdateArray != null) {
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// action_list
// 将mUpdateArray放入jsPost中键为GTASK_JSON_ACTION_LIST
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version
// 将客户端版本号添加到jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求
postRequest(jsPost);
// 将mUpdateArray置为null
mUpdateArray = null;
// 捕获JSONException异常
} catch (JSONException e) {
// 打印异常信息
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出ActionFailureException异常异常信息为"commit update: handing jsonobject failed"
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
public void addUpdateNode(Node node) throws NetworkFailureException {
// 检查传入的节点是否为空
if (node != null) {
// too many update items may result in an error
// set max to 10 items
// 如果mUpdateArray不为空且长度大于10则执行commitUpdate()方法
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
// 如果mUpdateArray为空则创建一个新的JSONArray对象
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
// 将node的更新动作添加到mUpdateArray中
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
// 将任务task从preParent移动到curParent
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象
JSONArray actionList = new JSONArray();
// 创建一个JSONObject对象
JSONObject action = new JSONObject();
// action_list
// 将GTASK_JSON_ACTION_TYPE键的值设置为GTASK_JSON_ACTION_TYPE_MOVE
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
// 将GTASK_JSON_ACTION_ID键的值设置为getActionId()方法的返回值
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 将GTASK_JSON_ID键的值设置为task对象的getGid()方法的返回值
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
// 如果preParent和curParent相等且task对象的getPriorSibling()方法返回值不为null
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
// 将任务的前一个兄弟节点的ID放入action中
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
// 将任务的前一个父节点的GID放入action中
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
// 将任务的新父节点的GID放入action中
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
// 如果任务的前一个父节点和新父节点不同
if (preParent != curParent) {
// put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
// 将action添加到actionList中
actionList.put(action);
// 将actionList添加到jsPost中键为GTASK_JSON_ACTION_LIST
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
// 捕获JSON异常
} catch (JSONException e) {
// 打印错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出ActionFailureException异常异常信息为"move task: handing jsonobject failed"
// 抛出ActionFailureException异常异常信息为"move task: handing jsonobject failed"
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
// 删除节点
public void deleteNode(Node node) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象
JSONArray actionList = new JSONArray();
// action_list
// 设置节点为已删除状态
node.setDeleted(true);
// 将节点的更新操作添加到action_list中
actionList.put(node.getUpdateAction(getActionId()));
// 将action_list添加到jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 将客户端版本号添加到jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求
postRequest(jsPost);
// 将mUpdateArray置为null
mUpdateArray = null;
} catch (JSONException e) {
// 捕获JSONException异常
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出ActionFailureException异常
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对象用于发送GET请求
HttpGet httpGet = new HttpGet(mGetUrl);
// 声明一个HttpResponse对象用于接收服务器返回的响应
HttpResponse response = null;
// 使用HttpClient执行HttpGet请求并将响应赋值给response对象
response = mHttpClient.execute(httpGet);
// get the task list
String resString = getResponseContent(response.getEntity());
// 获取响应内容
String jsBegin = "_setup(";
// 定义js字符串开始标志
String jsEnd = ")}</script>";
// 定义js字符串结束标志
int begin = resString.indexOf(jsBegin);
// 获取js字符串开始位置
int end = resString.lastIndexOf(jsEnd);
// 获取js字符串结束位置
String jsString = null;
// 定义js字符串
if (begin != -1 && end != -1 && begin < end) {
// 如果开始位置、结束位置都存在且开始位置在结束位置之前
jsString = resString.substring(begin + jsBegin.length(), end);
// 获取js字符串
}
// 将jsString转换为JSONObject对象
JSONObject js = new JSONObject(jsString);
// 返回JSONObject对象中键为"t"的JSONObject对象中键为GTASK_JSON_LISTS的JSONArray对象
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
// 捕获ClientProtocolException异常
} catch (ClientProtocolException e) {
// 打印异常信息
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出网络失败异常
throw new NetworkFailureException("gettasklists: httpget failed");
// 捕获IO异常
} catch (IOException e) {
// 打印错误日志
Log.e(TAG, e.toString());
// 打印堆栈信息
e.printStackTrace();
catch (JSONException e) {
// 捕获JSONException异常
Log.e(TAG, e.toString());
// 打印异常信息
e.printStackTrace();
// 抛出自定义异常ActionFailureException并传入异常信息
throw new ActionFailureException("get task lists: handing jasonobject failed") throw new NetworkFailureException("gettasklists: httpget failed");
} ;
}
}
// 根据listGid获取任务列表
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
// 提交更新
commitUpdate();
try {
// 创建一个JSONObject对象
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象
JSONArray actionList = new JSONArray();
// 创建一个JSONObject对象
JSONObject action = new JSONObject();
// action_list
// 设置action类型为获取所有任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
// 设置action的id
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 设置任务的list id
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
// 设置是否获取已删除的任务
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
// 将action添加到actionList中
actionList.put(action);
// 将actionList添加到jsPost中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送post请求获取响应
JSONObject jsResponse = postRequest(jsPost);
// 返回响应中的任务数组
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
// 捕获JSONException异常
} catch (JSONException e) {
// 打印错误日志
Log.e(TAG, e.toString());
// 打印异常堆栈信息
e.printStackTrace();
// 抛出自定义异常ActionFailureException并传入错误信息
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
// 获取同步账户
public Account getSyncAccount() {
// 返回mAccount
return mAccount;
}
// 重置更新数组
public void resetUpdateArray() {
// 将更新数组置为空
mUpdateArray = null;
}
}

@ -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;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
public class GTaskSyncService extends 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;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync();
break;
default:
break;
}
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
public IBinder onBind(Intent intent) {
return null;
}
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
public static boolean isSyncing() {
return mSyncTask != null;
}
public static String getProgressString() {
return mSyncProgress;
}
}

@ -0,0 +1,334 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
public class Note {
// 定义一个ContentValues对象用于存储Note的差值
private ContentValues mNoteDiffValues;
// 定义一个NoteData对象用于存储Note的数据
private NoteData mNoteData;
// 定义一个常量,用于打印日志
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
// Set the created date and modified date to the current time
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
// Set the type of the note to be a note
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// Set the local modified flag to 1
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// Set the parent folder id
values.put(NoteColumns.PARENT_ID, folderId);
// Insert the new note into the database
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
// 获取noteId
long noteId = 0;
try {
// 将uri的路径段转换为long类型
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果转换失败,则打印错误日志
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
// 如果noteId为-1则抛出异常
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
// 返回noteId
return noteId;
}
// 构造函数初始化mNoteDiffValues和mNoteData
public Note() {
// 初始化mNoteDiffValues为ContentValues类型
mNoteDiffValues = new ContentValues();
// 初始化mNoteData为NoteData类型
mNoteData = new NoteData();
}
// 设置笔记的值
public void setNoteValue(String key, String value) {
// 将key和value放入mNoteDiffValues中
mNoteDiffValues.put(key, value);
// 将LOCAL_MODIFIED字段设置为1
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 将MODIFIED_DATE字段设置为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
public void setTextData(String key, String value) {
// 设置文本数据
mNoteData.setTextData(key, value);
}
public void setTextDataId(long id) {
// 设置文本数据ID
mNoteData.setTextDataId(id);
}
public long getTextDataId() {
// 获取文本数据ID
return mNoteData.mTextDataId;
}
public void setCallDataId(long id) {
// 设置通话数据ID
mNoteData.setCallDataId(id);
}
public void setCallData(String key, String value) {
// 设置通话数据
mNoteData.setCallData(key, value);
}
public boolean isLocalModified() {
// 判断本地是否修改
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
public boolean syncNote(Context context, long noteId) {
// 同步笔记
if (noteId <= 0) {
// 如果笔记ID小于等于0抛出异常
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 如果本地没有修改则返回true
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
// 如果更新笔记失败,则打印错误日志
// 更新指定id的note如果更新成功返回值为0
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
// 如果笔记数据是本地修改的并且将笔记数据推送到内容解析器中失败则返回false
if (mNoteData.isLocalModified()
// 如果mNoteData.pushIntoContentResolver(context, noteId)返回null则返回false
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
// 否则返回true
return true;
}
private class NoteData {
// 文本数据ID
private long mTextDataId;
// 文本数据内容
private ContentValues mTextDataValues;
// 通话数据ID
private long mCallDataId;
// 通话数据内容
private ContentValues mCallDataValues;
// 日志标签
private static final String TAG = "NoteData";
// 构造函数
public NoteData() {
// 初始化文本数据内容
mTextDataValues = new ContentValues();
// 初始化通话数据内容
mCallDataValues = new ContentValues();
// 初始化文本数据ID
mTextDataId = 0;
// 初始化通话数据ID
mCallDataId = 0;
}
// 判断文本数据是否被修改
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
// 设置文本数据id
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
// 设置通话数据id
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = 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) {
// 将key和value存入mTextDataValues中
mTextDataValues.put(key, value);
// 将LOCAL_MODIFIED字段设置为1
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 将MODIFIED_DATE字段设置为当前时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
// 检查noteId是否小于等于0
if (noteId <= 0) {
// 如果小于等于0抛出异常
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
// 如果mTextDataValues中有数据
if(mTextDataValues.size() > 0) {
// 将noteId存入mTextDataValues中
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
// 如果mTextDataId为0
if (mTextDataId == 0) {
// 将MIME_TYPE字段设置为TextNote.CONTENT_ITEM_TYPE
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 将mTextDataValues插入到Notes.CONTENT_DATA_URI中
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
// 将uri路径段中的第二个值转换为Long类型并设置给mTextDataId
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果转换失败则记录错误日志并清空mTextDataValues返回null
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
// 如果mTextDataId不为0则创建一个ContentProviderOperation对象用于更新数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
// 将mTextDataValues中的值设置到ContentProviderOperation对象中
builder.withValues(mTextDataValues);
// 将ContentProviderOperation对象添加到operationList中
operationList.add(builder.build());
}
// 清空mTextDataValues
mTextDataValues.clear();
}
// 如果mCallDataValues的大小大于0则执行以下操作
if(mCallDataValues.size() > 0) {
// 将noteId设置到mCallDataValues中
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
// 如果mCallDataId为0则执行以下操作
if (mCallDataId == 0) {
// 将CallNote.CONTENT_ITEM_TYPE设置到mCallDataValues中
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
// 使用context.getContentResolver().insert()方法插入数据并获取Uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
// 将Uri路径段中的第二个值转换为Long类型并设置给mCallDataId
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果转换失败则记录错误日志并清空mCallDataValues返回null
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
// 创建一个ContentProviderOperation对象用于更新数据
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);
// 如果操作结果为空或者长度为0或者第一个结果为空则返回null
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
// 捕获RemoteException异常并打印错误日志
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
// 捕获OperationApplicationException异常并打印错误日志
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
// 如果操作列表中没有操作则返回null
return null;
}
}
}

@ -0,0 +1,498 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
// Alert date
private long mAlertDate;
// Modified date
private long mModifiedDate;
// Background color id
private int mBgColorId;
// Widget id
private int mWidgetId;
// Widget type
private int mWidgetType;
// Folder id
private long mFolderId;
// Context
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, // 笔记类型
DataColumns.DATA1, // 笔记数据1
DataColumns.DATA2, // 笔记数据2
DataColumns.DATA3, // 笔记数据3
DataColumns.DATA4, // 笔记数据4
};
public static final String[] NOTE_PROJECTION = new String[] {
// 笔记的父ID
NoteColumns.PARENT_ID,
// 笔记的提醒日期
NoteColumns.ALERTED_DATE,
// 笔记的背景颜色ID
NoteColumns.BG_COLOR_ID,
// 笔记的小部件ID
NoteColumns.WIDGET_ID,
// 笔记的小部件类型
NoteColumns.WIDGET_TYPE,
// 笔记的修改日期
NoteColumns.MODIFIED_DATE
};
// 数据ID列
private static final int DATA_ID_COLUMN = 0;
// 数据内容列
private static final int DATA_CONTENT_COLUMN = 1;
// 数据MIME类型列
private static final int DATA_MIME_TYPE_COLUMN = 2;
// 数据模式列
private static final int DATA_MODE_COLUMN = 3;
// 笔记父ID列
private static final int NOTE_PARENT_ID_COLUMN = 0;
// 笔记提醒日期列
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
// 笔记背景颜色ID列
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// 笔记小部件ID列
private static final int NOTE_WIDGET_ID_COLUMN = 3;
// 笔记小部件类型列
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
// 笔记修改日期列
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
private WorkingNote(Context context, long folderId) {
// 初始化上下文
mContext = context;
// 初始化提醒日期
mAlertDate = 0;
// 初始化修改日期
mModifiedDate = System.currentTimeMillis();
// 初始化文件夹ID
mFolderId = folderId;
// 初始化Note对象
mNote = new Note();
// 初始化NoteID
mNoteId = 0;
// 初始化是否删除
mIsDeleted = false;
// 初始化模式
mMode = 0;
// 初始化小部件类型
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
// 初始化上下文
mContext = context;
// 初始化NoteID
mNoteId = noteId;
// 初始化文件夹ID
mFolderId = folderId;
// 初始化是否删除
mIsDeleted = false;
// 初始化Note对象
mNote = new Note();
// 加载Note
loadNote();
}
// 加载笔记
private void loadNote() {
// 从ContentResolver中查询指定id的笔记
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
// 如果查询结果不为空
if (cursor != null) {
// 如果查询结果中有数据
if (cursor.moveToFirst()) {
// 获取笔记的父文件夹id
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
// 获取笔记的背景颜色id
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
// 获取笔记的widget id
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
// 获取笔记的widget类型
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() {
// 查询数据库获取指定id的笔记数据
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
// 如果查询结果不为空
if (cursor != null) {
// 如果查询结果中有数据
if (cursor.moveToFirst()) {
// 遍历查询结果
do {
// 获取数据类型
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
// 如果数据类型为笔记
if (DataConstants.NOTE.equals(type)) {
// 获取笔记内容
mContent = cursor.getString(DATA_CONTENT_COLUMN);
// 获取笔记模式
mMode = cursor.getInt(DATA_MODE_COLUMN);
// 设置笔记内容id
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
// 如果数据类型为电话笔记
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 设置电话笔记id
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
// 如果数据类型不正确
} else {
// 打印错误日志
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
// 关闭查询结果
cursor.close();
// 如果查询结果为空
} else {
// 打印错误日志
Log.e(TAG, "No data with id:" + mNoteId);
// 抛出异常
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
// 创建一个空的WorkingNote对象
WorkingNote note = new WorkingNote(context, folderId);
// 设置背景颜色
note.setBgColorId(defaultBgColorId);
// 设置widgetId
note.setWidgetId(widgetId);
// 设置widgetType
note.setWidgetType(widgetType);
// 返回创建的WorkingNote对象
return note;
}
// 加载工作笔记
public static WorkingNote load(Context context, long id) {
// 创建一个新的工作笔记对象传入上下文、id和0
return new WorkingNote(context, id, 0);
}
public synchronized boolean saveNote() {
// 如果需要保存
if (isWorthSaving()) {
// 如果数据库中不存在
if (!existInDatabase()) {
// 获取新的笔记ID
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
// 如果获取失败,打印错误日志
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
// 同步笔记
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
// 如果mWidgetId不等于AppWidgetManager.INVALID_APPWIDGET_IDmWidgetType不等于Notes.TYPE_WIDGET_INVALIDE并且mNoteSettingStatusListener不为空则调用mNoteSettingStatusListener的onWidgetChanged方法
mNoteSettingStatusListener.onWidgetChanged();
}
// 返回true
return true;
} else {
// 否则返回false
return false;
}
}
public boolean existInDatabase() {
// 判断mNoteId是否大于0如果大于0则表示该笔记存在于数据库中
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) {
// 设置NoteSettingChangedListener监听器
mNoteSettingStatusListener = l;
}
public void setAlertDate(long date, boolean set) {
// 设置提醒日期
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
// 如果NoteSettingChangedListener监听器不为空则调用onClockAlertChanged方法
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
public void markDeleted(boolean mark) {
// 标记删除
mIsDeleted = mark;
// 如果widgetId不为INVALID_APPWIDGET_ID且widgetType不为INVALIDE且NoteSettingChangedListener监听器不为空则调用onWidgetChanged方法
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
public void setBgColorId(int id) {
// 如果传入的id和当前背景颜色id不同
if (id != mBgColorId) {
// 更新背景颜色id
mBgColorId = id;
// 如果有监听器则调用监听器的onBackgroundColorChanged方法
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
// 更新笔记的背景颜色id
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
public void setCheckListMode(int mode) {
// 如果传入的模式和当前模式不同
if (mMode != mode) {
// 如果有监听器则调用监听器的onCheckListModeChanged方法
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) {
// 如果传入的id与mWidgetId不同则更新mWidgetId并更新mNote中的NoteColumns.WIDGET_ID值
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
public void setWorkingText(String text) {
// 如果传入的text与mContent不同则更新mContent并更新mNote中的DataColumns.CONTENT值
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
public void convertToCallNote(String phoneNumber, long callDate) {
// 将mNote转换为CallNote并设置CallNote.CALL_DATE和CallNote.PHONE_NUMBER值
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
// 设置NoteColumns.PARENT_ID值为Notes.ID_CALL_RECORD_FOLDER
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
public boolean hasClockAlert() {
// 如果mAlertDate大于0则返回true否则返回false
return (mAlertDate > 0 ? true : false);
}
public String getContent() {
// 返回mContent值
return mContent;
}
public long getAlertDate() {
// 返回mAlertDate值
return mAlertDate;
}
public long getModifiedDate() {
// 返回mModifiedDate值
return mModifiedDate;
}
public int getBgColorResId() {
// 返回NoteBgResources.getNoteBgResource(mBgColorId)的值
return NoteBgResources.getNoteBgResource(mBgColorId);
}
public int getBgColorId() {
// 返回mBgColorId值
return mBgColorId;
}
public int getTitleBgResId() {
// 返回NoteBgResources.getNoteTitleBgResource(mBgColorId)的值
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
public int getCheckListMode() {
// 返回mMode值
return mMode;
}
public long getNoteId() {
// 返回mNoteId值
return mNoteId;
}
public long getFolderId() {
// 返回mFolderId值
return mFolderId;
}
public int getWidgetId() {
// 返回mWidgetId的值
return mWidgetId;
}
public int getWidgetType() {
// 返回mWidgetType的值
return mWidgetType;
}
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
// 当背景颜色改变时调用此方法
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
// 当时钟警报发生变化时调用此方法
// date警报日期
// set警报是否已设置
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
// 当小部件发生变化时调用此方法
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
// 当列表模式改变时调用此方法
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -0,0 +1,492 @@
/*
* 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 stuff
// 定义一个静态变量用于保存BackupUtils的实例
private static BackupUtils sInstance;
// 获取BackupUtils实例
public static synchronized BackupUtils getInstance(Context context) {
// 如果sInstance为空则创建一个新的BackupUtils实例
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
// 返回sInstance实例
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
// 定义一个常量表示SD卡未挂载的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
// 定义一个常量,表示备份文件不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
// 定义一个常量,表示数据被销毁的状态
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
// 定义一个常量,表示系统错误状态
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
// 定义一个常量,表示成功状态
public static final int STATE_SUCCESS = 4;
// 声明一个TextExport类型的变量mTextExport
private TextExport mTextExport;
// 定义一个BackupUtils类构造函数接收一个Context参数
private BackupUtils(Context context) {
// 创建一个TextExport对象传入Context参数
mTextExport = new TextExport(context);
}
// 检查外部存储是否可用
private static boolean externalStorageAvailable() {
// 返回外部存储的状态是否为MEDIA_MOUNTED
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 导出为文本文件
public int exportToText() {
// 调用mTextExport对象的exportToText方法
return mTextExport.exportToText();
}
// 获取导出的文本文件名
public String getExportedTextFileName() {
// 返回mTextExport对象的mFileName属性
return mTextExport.mFileName;
}
// 获取导出的文本文件目录
public String getExportedTextFileDir() {
// 返回mTextExport对象的mFileDirectory属性
return mTextExport.mFileDirectory;
}
// 定义一个静态内部类TextExport
private static class TextExport {
// 定义一个常量数组NOTE_PROJECTION包含NoteColumns中的ID、MODIFIED_DATE、SNIPPET、TYPE四个字段
private static final String[] NOTE_PROJECTION = {
// 获取NoteColumns中的ID、MODIFIED_DATE、SNIPPET字段
// NoteColumns.ID 表示Note表的ID列
NoteColumns.ID,
// 添加注释
NoteColumns.MODIFIED_DATE, // 修改日期
// 定义NoteColumns类中的SNIPPET常量
NoteColumns.SNIPPET,
// 定义一个常量表示NoteColumns中的TYPE列
NoteColumns.TYPE
};
// 定义一个常量表示笔记的ID列
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,
// 数据列的MIME类型
DataColumns.MIME_TYPE,
// 数据列1
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 定义一个常量,表示数据列的内容
private static final int DATA_COLUMN_CONTENT = 0;
// 定义一个常量表示数据列的MIME类型
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;
// 定义一个Context类型的成员变量
private Context mContext;
// 声明一个字符串类型的私有变量mFileName
private String mFileName;
// 文件目录
private String mFileDirectory;
// 构造函数用于创建TextExport对象
public TextExport(Context context) {
// 获取导出笔记的格式数组
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
// 保存上下文
mContext = context;
// 初始化文件名为空字符串
mFileName = "";
// 初始化文件目录为空字符串
mFileDirectory = "";
}
// 根据id获取格式
private String getFormat(int id) {
// 返回TEXT_FORMAT数组中索引为id的元素
return TEXT_FORMAT[id];
}
/**
// 导出由folder id标识的文件夹到文本
* Export the folder identified by folder id to text
*/
// 将文件夹导出到文本文件
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
// 获取指定文件夹下的所有笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
// 定义一个常量用于查询Note表中的数据
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
// 定义一个变量folderId用于存储文件夹的ID
folderId
// 定义一个无符号长整型变量
}, null);
// 判断notesCursor是否为空
if (notesCursor != null) {
// 如果notesCursor指向的数据表中有数据
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
// 打印日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
// 获取字符串资源,格式为"yyyy-MM-dd HH:mm"
mContext.getString(R.string.format_datetime_mdhm),
// 获取notesCursor中NOTE_COLUMN_MODIFIED_DATE列的值并将其转换为long类型
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 从notesCursor中获取NOTE_COLUMN_ID列的值并将其赋值给noteId变量
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
// 导出笔记到文本
exportNoteToText(noteId, ps);
// 使用while循环遍历notesCursor中的数据
} while (notesCursor.moveToNext());
}
// 关闭游标
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
// 将笔记导出到文本文件
private void exportNoteToText(String noteId, PrintStream ps) {
// 获取Notes表中的数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
// 查询数据指定投影为DATA_PROJECTION条件为DataColumns.NOTE_ID等于noteId不指定排序
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
// 如果dataCursor不为空
if (dataCursor != null) {
// 如果dataCursor可以移动到第一行
if (dataCursor.moveToFirst()) {
// do 关键字用于开始一个循环,循环体内的代码会一直执行,直到遇到 break 关键字
do {
// 获取数据的光标
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
// 判断mimeType是否等于DataConstants.CALL_NOTE
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
// 从dataCursor中获取电话号码
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
// 获取dataCursor中DATA_COLUMN_CALL_DATE列的值并将其赋值给callDate变量
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
// 获取数据光标中指定列的字符串值
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 判断phoneNumber是否为空
if (!TextUtils.isEmpty(phoneNumber)) {
// 打印格式化后的字符串
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
// 打印格式化后的字符串
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
// 格式化日期
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
// 判断location是否为空
if (!TextUtils.isEmpty(location)) {
// 使用String.format方法将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));
}
}
// 如果dataCursor可以移动到下一个位置则继续循环
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {
// 向ps写入一个换行符和一个字母数字字符
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
// 捕获IOException异常并打印错误日志
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
// 如果外部存储不可用,则输出日志信息
Log.d(TAG, "Media was not mounted");
// 返回SD卡未挂载状态
return STATE_SD_CARD_UNMOUONTED;
}
// 获取导出到文本的PrintStream
PrintStream ps = getExportToTextPrintStream();
// 如果获取失败
if (ps == null) {
// 打印错误日志
Log.e(TAG, "get print stream error");
// 返回系统错误状态
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
// 查询数据库,获取文件夹和通话记录文件夹的信息
Cursor folderCursor = mContext.getContentResolver().query(
// 定义一个常量表示笔记的URI
Notes.CONTENT_NOTE_URI,
// 声明一个名为NOTE_PROJECTION的常量
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);
// 如果folderCursor不为空
if (folderCursor != null) {
// 如果游标指向的数据集不为空
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
// 声明一个字符串变量folderName用于存储文件夹名称
String folderName = "";
// 如果文件夹游标中的ID等于Notes中的ID_CALL_RECORD_FOLDER则将文件夹名称设置为上下文中的call_record_folder_name字符串
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);
}
// 判断folderName是否为空
if (!TextUtils.isEmpty(folderName)) {
// 使用String.format方法将folderName格式化为指定格式并输出到ps流中
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
// 获取文件夹ID
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
// 将文件夹导出到文本
exportFolderToText(folderId, ps);
// 移动到下一个文件夹
} while (folderCursor.moveToNext());
}
// 关闭文件夹游标
folderCursor.close();
}
// Export notes in root's folder
// 获取一个游标查询Notes表中type为Notes.TYPE_NOTE且parent_id为0的数据 Cursor noteCursor = mContext.getContentResolver().query(
Cursor noteCursor = mContext.getContentResolver().query(
// 定义一个Notes类其中包含一个CONTENT_NOTE_URI常量用于表示Notes表的URI
Notes.CONTENT_NOTE_URI,
// 定义一个常量,用于表示投影
NOTE_PROJECTION,
// 拼接查询条件查询类型为Notes.TYPE_NOTE且父ID为NoteColumns.PARENT_ID的记录
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
// 如果noteCursor不为空
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))));
// Query data belong to this note
// 获取当前游标中的noteId
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
// 将noteId导出到文本中
exportNoteToText(noteId, ps);
// 如果游标还有下一个元素,则继续循环
} while (noteCursor.moveToNext());
}
// 关闭游标
noteCursor.close();
}
// 关闭ps
ps.close();
// 返回成功状态
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
// 生成一个挂载在SD卡上的文件
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
// 如果文件生成失败则返回null
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);
// 创建PrintStream对象
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// 文件未找到异常,打印异常信息
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// 空指针异常,打印异常信息
e.printStackTrace();
return null;
}
// 返回PrintStream对象
return ps;
}
}
/**
* Generate the text file to store imported data
*/
// 生成一个挂载在SD卡上的文件
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// 创建一个StringBuilder对象
StringBuilder sb = new StringBuilder();
// 将SD卡的根目录添加到StringBuilder中
sb.append(Environment.getExternalStorageDirectory());
// 将文件路径字符串添加到StringBuilder中
sb.append(context.getString(filePathResId));
// 创建一个File对象表示文件路径
File filedir = new File(sb.toString());
// 将文件名格式字符串添加到StringBuilder中
sb.append(context.getString(
fileNameFormatResId,
// 使用当前时间格式化文件名
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
// 创建一个File对象表示文件名
File file = new File(sb.toString());
try {
// 如果文件路径不存在,则创建文件夹
if (!filedir.exists()) {
filedir.mkdir();
}
// 如果文件不存在,则创建文件
if (!file.exists()) {
file.createNewFile();
}
// 返回文件对象
return file;
} catch (SecurityException e) {
// 捕获SecurityException异常并打印异常信息
e.printStackTrace();
} catch (IOException e) {
// 捕获IOException异常并打印异常信息
e.printStackTrace();
}
// 返回null
return null;
}
}

@ -0,0 +1,417 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
// 批量删除笔记
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
// 如果ids为空则直接返回true
// 判断ids是否为空
if (ids == null) {
// 如果为空,打印日志
Log.d(TAG, "the ids is null");
// 返回true
return true;
}
// 判断ids的大小是否为0
if (ids.size() == 0) {
// 如果为0则输出日志信息
Log.d(TAG, "no id is in the hashset");
// 返回true
return true;
}
// 创建一个ContentProviderOperation的ArrayList
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历ids数组中的每一个id
for (long id : ids) {
// 如果id等于Notes.ID_ROOT_FOLDER则输出错误日志并跳过此次循环
if(id == Notes.ID_ROOT_FOLDER) {
// 打印错误日志提示不要删除系统文件夹root
Log.e(TAG, "Don't delete system folder root");
// 继续执行循环
continue;
}
// 创建一个ContentProviderOperation.Builder对象用于构建一个删除操作
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 将构建好的删除操作添加到操作列表中
operationList.add(builder.build());
}
try {
// 调用ContentResolver的applyBatch方法批量删除notes
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果返回的结果为空或者长度为0或者第一个结果为空则删除失败
// 判断results是否为空或者results的长度是否为0或者results的第一个元素是否为空
if (results == null || results.length == 0 || results[0] == null) {
// 打印删除笔记失败的日志并输出失败笔记的id
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
// 打印RemoteException异常信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
// 打印OperationApplicationException异常信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
// 返回false
return false;
}
// 移动笔记到文件夹
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
// 创建一个ContentValues对象用于存储要更新的数据
ContentValues values = new ContentValues();
// 将目标文件夹ID存储到ContentValues对象中
values.put(NoteColumns.PARENT_ID, desFolderId);
// 将原始文件夹ID存储到ContentValues对象中
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
// 将本地修改标志设置为1
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 使用ContentResolver对象更新指定ID的笔记将ContentValues对象中的数据应用到该笔记中
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
// 批量移动笔记到文件夹
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
// 检查ids是否为空
if (ids == null) {
// 如果为空,打印日志
Log.d(TAG, "the ids is null");
// 返回true
return true;
}
// 创建一个ContentProviderOperation的ArrayList
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历ids数组
for (long id : ids) {
// 创建一个ContentProviderOperation.Builder对象用于构建ContentProviderOperation
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 设置PARENT_ID字段的值为folderId
builder.withValue(NoteColumns.PARENT_ID, folderId);
// 设置LOCAL_MODIFIED字段的值为1
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
// 将构建好的ContentProviderOperation添加到operationList中
operationList.add(builder.build());
}
try {
// 调用ContentResolver的applyBatch方法批量执行操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果结果为空或者长度为0或者第一个结果为空则删除失败
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
// 否则删除成功
return true;
// 捕获RemoteException异常
} 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()));
}
// 返回false
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 查询Notes.CONTENT_NOTE_URI获取COUNT(*)条件为NoteColumns.TYPE为Notes.TYPE_FOLDER且NoteColumns.PARENT_ID不为Notes.ID_TRASH_FOLER
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
// 创建一个包含一个字符串的数组,字符串内容为 "COUNT(*)"
new String[] { "COUNT(*)" },
// 查询类型为指定类型且父ID不等于指定值的记录
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
// 创建一个包含两个元素的字符串数组第一个元素是Notes.TYPE_FOLDER的值第二个元素是Notes.ID_TRASH_FOLER的值
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
// 第二个参数为null
null);
// 定义一个整型变量count并初始化为0
int count = 0;
// 判断游标是否为空
if(cursor != null) {
// 将游标移动到第一行
if(cursor.moveToFirst()) {
try {
// 获取第一列的整数值
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
// 捕获IndexOutOfBoundsException异常并打印错误日志
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
// 关闭游标
cursor.close();
}
}
}
// 返回count
return count;
}
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 查询指定noteId的笔记类型为type且父文件夹不为垃圾箱
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
// 查询条件类型等于某个值并且父ID不等于垃圾文件夹的ID
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)}, // 将type转换为字符串并添加到字符串数组中
null); // 添加一个空值
boolean exist = false;
// 判断游标是否为空
if (cursor != null) {
// 判断游标中的数据是否大于0
if (cursor.getCount() > 0) {
// 如果大于0则存在数据
exist = true;
}
// 关闭游标
cursor.close();
}
// 返回exist变量
return exist;
}
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 查询指定noteId的笔记是否存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
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) {
// 查询指定dataId的数据是否存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
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,
// 添加注释
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,
// 查询便签的widget_id和widget_type字段
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
// 查询条件为便签的parent_id等于folderId
NoteColumns.PARENT_ID + "=?",
// 查询条件参数
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
// 如果c不为空
if (c != null) {
// 如果c可以移动到下一行
if (c.moveToFirst()) {
// 创建一个新的HashSet
set = new HashSet<AppWidgetAttribute>();
// 循环遍历c中的每一行
do {
try {
// 创建一个新的AppWidgetAttribute对象
AppWidgetAttribute widget = new AppWidgetAttribute();
// 获取widgetId的值
widget.widgetId = c.getInt(0);
// 获取widgetType的值
widget.widgetType = c.getInt(1);
// 将widget添加到set中
set.add(widget);
} catch (IndexOutOfBoundsException e) {
// 如果发生IndexOutOfBoundsException异常打印错误日志
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
// 关闭c
c.close();
}
// 返回set
return set;
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 根据noteId查询CallNote表中的PHONE_NUMBER字段
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
// 如果查询结果不为空且游标指向第一条数据
if (cursor != null && cursor.moveToFirst()) {
try {
// 返回PHONE_NUMBER字段的值
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
// 如果发生IndexOutOfBoundsException异常打印错误日志
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,
// 查询的列
new String [] { CallNote.NOTE_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);
// 判断cursor是否为空
if (cursor != null) {
// 将cursor移动到第一行
if (cursor.moveToFirst()) {
try {
// 返回cursor中第一列的值
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
// 如果发生IndexOutOfBoundsException异常打印错误日志
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
// 关闭cursor
cursor.close();
}
// 如果cursor为空返回0
return 0;
}
public static String getSnippetById(ContentResolver resolver, long noteId) {
// 通过ContentResolver查询指定id的note的snippet
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
// 创建一个包含NoteColumns.SNIPPET的字符串数组
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
// 创建一个包含noteId的字符串数组
new String [] { String.valueOf(noteId)},
null);
// 判断cursor是否为空
if (cursor != null) {
// 定义一个字符串变量snippet用于存储从cursor中获取的字符串
String snippet = "";
// 将cursor移动到第一行
if (cursor.moveToFirst()) {
// 从cursor中获取第一列的字符串并赋值给snippet
snippet = cursor.getString(0);
}
// 关闭游标
cursor.close();
// 返回片段
return snippet;
}
// 抛出异常提示找不到指定id的笔记
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
// 格式化snippet
public static String getFormattedSnippet(String snippet) {
// 如果snippet不为空
if (snippet != null) {
// 去除snippet两端的空格
snippet = snippet.trim();
// 获取snippet中第一个换行符的位置
int index = snippet.indexOf('\n');
// 如果存在换行符
if (index != -1) {
// 截取换行符之前的内容
snippet = snippet.substring(0, index);
}
}
// 返回格式化后的snippet
return snippet;
}
}

@ -0,0 +1,157 @@
/*
* 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 JSON中的action_id字段
public final static String GTASK_JSON_ACTION_ID = "action_id";
// 定义GTask JSON中的action_list字段
public final static String GTASK_JSON_ACTION_LIST = "action_list";
// 定义GTask JSON中的action_type字段
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// 定义GTask JSON中的action_type字段表示创建操作
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// 定义GTask JSON中的action_type字段表示获取所有操作
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// 定义任务JSON操作类型为移动
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// 定义任务JSON操作类型为更新
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// 定义任务JSON创建者ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 定义任务JSON子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// 定义任务JSON客户端版本
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// 定义任务JSON是否完成
public final static String GTASK_JSON_COMPLETED = "completed";
// 定义任务JSON当前列表ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
// 定义任务JSON默认列表ID
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// 定义任务JSON是否删除
public final static String GTASK_JSON_DELETED = "deleted";
// 定义任务JSON目标列表
public final static String GTASK_JSON_DEST_LIST = "dest_list";
// 定义任务JSON目标父级
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
// 实体增量
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// 实体类型
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// 获取已删除
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// ID
public final static String GTASK_JSON_ID = "id";
// 索引
public final static String GTASK_JSON_INDEX = "index";
// 最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// 最新同步点
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// 列表ID
public final static String GTASK_JSON_LIST_ID = "list_id";
// 列表
public final static String GTASK_JSON_LISTS = "lists";
// 名称
public final static String GTASK_JSON_NAME = "name";
// 新ID
public final static String GTASK_JSON_NEW_ID = "new_id";
// 备注
public final static String GTASK_JSON_NOTES = "notes";
// 父ID
public final static String GTASK_JSON_PARENT_ID = "parent_id";
// 前一个兄弟ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
// 结果
public final static String GTASK_JSON_RESULTS = "results";
// 源列表
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";
// 任务类型
public final static String GTASK_JSON_TYPE = "type";
// 任务组
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// 任务
public final static String GTASK_JSON_TYPE_TASK = "TASK";
// 用户
public final static String GTASK_JSON_USER = "user";
// MIUI文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
// 默认文件夹
public final static String FOLDER_DEFAULT = "Default";
// 呼叫记录文件夹
public final static String FOLDER_CALL_NOTE = "Call_Note";
// 元数据文件夹
public final static String FOLDER_META = "METADATA";
// 元数据任务ID
public final static String META_HEAD_GTASK_ID = "meta_gid";
// 元数据笔记
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,219 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
// 定义颜色常量
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
// 定义默认背景颜色
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义字体大小常量
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
// 定义默认字体大小
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
public static class NoteBgResources {
// 定义一个静态的整型数组用于存储背景图片的资源ID
private final static int [] BG_EDIT_RESOURCES = new int [] {
// 背景图片资源ID
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) {
// 根据id返回对应的背景资源
return BG_EDIT_RESOURCES[id];
}
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
public static int getDefaultBgId(Context context) {
// 获取默认的背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 如果设置了背景颜色,则返回一个随机的背景颜色
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
// 如果没有设置背景颜色,则返回默认的背景颜色
return BG_DEFAULT_COLOR;
}
}
public static class NoteItemBgResources {
// 定义一个静态的整型数组用于存储背景图片的资源ID
private final static int [] BG_FIRST_RESOURCES = new int [] {
// 黄色背景图片的资源ID
R.drawable.list_yellow_up,
// 蓝色背景图片的资源ID
R.drawable.list_blue_up,
// 白色背景图片的资源ID
R.drawable.list_white_up,
// 绿色背景图片的资源ID
R.drawable.list_green_up,
// 红色背景图片的资源ID
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];
}
// 获取笔记背景的最后一张图片
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
// 获取笔记背景的单张图片
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
// 获取笔记背景的正常图片
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
// 获取文件夹背景的图片
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {
// 定义一个静态的整数数组用于存储2x分辨率的背景资源
private final static int [] BG_2X_RESOURCES = new int [] {
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) {
// 根据id返回对应的2x背景资源
return BG_2X_RESOURCES[id];
}
// 定义4x背景资源数组
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
// 根据id返回对应的4x背景资源
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
// 定义文本样式资源类
public static class TextAppearanceResources {
// 定义文本样式资源数组
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 根据id获取文本外观资源
public static int getTexAppearanceResource(int id) {
/**
* 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}
*/
// 判断id是否大于等于TEXTAPPEARANCE_RESOURCES的长度
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
// 如果是,则返回默认字体大小
return BG_DEFAULT_FONT_SIZE;
}
// 否则返回TEXTAPPEARANCE_RESOURCES中对应id的值
return TEXTAPPEARANCE_RESOURCES[id];
}
// 获取TEXTAPPEARANCE_RESOURCES的长度
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -0,0 +1,220 @@
/*
* 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 {
// 声明一个长整型变量mNoteId用于存储闹钟的ID
private long mNoteId;
// 声明一个字符串变量mSnippet用于存储闹钟的提示信息
private String mSnippet;
// 声明一个常量SNIPPET_PREW_MAX_LEN用于存储提示信息的最大长度
private static final int SNIPPET_PREW_MAX_LEN = 60;
// 声明一个MediaPlayer对象mPlayer用于播放闹钟铃声
MediaPlayer mPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置窗口无标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 获取窗口对象
final Window win = getWindow();
// 设置窗口在锁屏状态下显示
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕未亮起
if (!isScreenOn()) {
// 设置窗口保持屏幕常亮
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
// 设置窗口在屏幕亮起时自动亮起
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
// 设置窗口在锁屏状态下允许解锁
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
// 设置窗口布局嵌入装饰
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取当前Activity的Intent
Intent intent = getIntent();
// 尝试获取Intent中的数据
try {
// 获取Intent中的数据路径段
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 根据NoteId获取Snippet
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 如果Snippet的长度大于SNIPPET_PREW_MAX_LEN则截取前SNIPPET_PREW_MAX_LEN个字符并在后面添加字符串
// 如果mSnippet的长度大于SNIPPET_PREW_MAX_LEN则截取前SNIPPET_PREW_MAX_LEN个字符并在后面加上字符串资源R.string.notelist_string_info
// 否则mSnippet保持不变
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) {
// 如果发生IllegalArgumentException异常则打印异常信息并返回
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()) {
// 设置对话框的取消按钮点击后执行this中的方法
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框并设置对话框消失时的监听器为this
dialog.show().setOnDismissListener(this);
}
// 处理对话框按钮点击事件
public void onClick(DialogInterface dialog, int which) {
// 点击对话框中的按钮时触发
switch (which) {
// 如果点击了对话框中的“取消”按钮
case DialogInterface.BUTTON_NEGATIVE:
// 如果点击了“进入”按钮,则跳转到笔记编辑活动
// 创建一个Intent对象指定目标Activity为NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置Intent的Action为ACTION_VIEW
intent.setAction(Intent.ACTION_VIEW);
// 将mNoteId作为附加数据传递给Intent
intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 启动目标Activity
startActivity(intent);
// 结束switch语句
break;
// 默认情况下,不执行任何操作
default:
break;
}
}
// 处理对话框消失事件
public void onDismiss(DialogInterface dialog) {
// 停止播放闹钟声音
stopAlarmSound();
// 结束当前活动
finish();
}
// 停止播放闹钟声音
// 停止闹钟声音
private void stopAlarmSound() {
// 如果mPlayer不为空
if (mPlayer != null) {
// 停止播放
mPlayer.stop();
// 释放资源
mPlayer.release();
// 将mPlayer置为空
mPlayer = null;
}
}
}

@ -0,0 +1,84 @@
/*
* 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 {
// 定义一个字符串数组用于存储NoteColumns中的ID和ALERTED_DATE
private static final String [] PROJECTION = new String [] {
// NoteColumns.ID 表示Note表的ID列
NoteColumns.ID,
// 获取提醒日期的列
NoteColumns.ALERTED_DATE
};
// 定义常量COLUMN_ID表示列的ID
private static final int COLUMN_ID = 0;
// 定义常量COLUMN_ALERTED_DATE表示列的报警日期
private static final int COLUMN_ALERTED_DATE = 1;
// 重写onReceive方法接收广播
@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);
// 判断变量c是否为空
if (c != null) {
// 如果游标移动到第一行
if (c.moveToFirst()) {
do {
// 获取提醒日期
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建一个Intent指向AlarmReceiver类
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置Intent的数据指向Notes表中的指定ID
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建一个PendingIntent用于在指定时间触发AlarmReceiver
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取AlarmManager服务
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟在指定时间触发AlarmReceiver
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭Cursor
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,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

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

Loading…
Cancel
Save