Showing preview only (872K chars total). Download the full file or copy to clipboard to get everything.
Repository: Shusshu/Android-RecurrencePicker
Branch: master
Commit: 4f0ae74482e4
Files: 135
Total size: 794.7 KB
Directory structure:
gitextract_fk9y8ksi/
├── .gitignore
├── LICENSE.txt
├── README.md
├── demo/
│ ├── pom.xml
│ ├── project.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── be/
│ │ └── billington/
│ │ └── calendar/
│ │ └── recurrencepicker/
│ │ └── demo/
│ │ └── activity/
│ │ └── DemoActivity.java
│ └── res/
│ ├── layout/
│ │ └── demo.xml
│ └── values/
│ ├── colors.xml
│ └── strings.xml
├── library/
│ ├── pom.xml
│ ├── project.properties
│ ├── release.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── be/
│ │ └── billington/
│ │ └── calendar/
│ │ └── recurrencepicker/
│ │ ├── EventRecurrence.java
│ │ ├── EventRecurrenceFormatter.java
│ │ ├── LinearLayoutWithMaxWidth.java
│ │ ├── RecurrencePickerDialog.java
│ │ ├── Utils.java
│ │ └── WeekButton.java
│ └── res/
│ ├── color/
│ │ ├── done_text_color.xml
│ │ ├── recurrence_bubble_text_color.xml
│ │ └── recurrence_spinner_text_color.xml
│ ├── drawable/
│ │ ├── recurrence_bubble_fill.xml
│ │ └── switch_thumb.xml
│ ├── layout/
│ │ ├── recurrencepicker.xml
│ │ ├── recurrencepicker_end_text.xml
│ │ └── recurrencepicker_freq_item.xml
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── values-af/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-am/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-ar/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-be/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-bg/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-ca/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-cs/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-da/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-de/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-el/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-en-rGB/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-es/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-es-rUS/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-et/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-fa/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-fi/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-fr/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-hi/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-hr/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-hu/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-in/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-it/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-iw/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-ja/
│ │ ├── arrays.xml
│ │ ├── colors.xml
│ │ └── strings.xml
│ ├── values-ko/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-land/
│ │ └── dimens.xml
│ ├── values-lt/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-lv/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-mcc262/
│ │ └── strings.xml
│ ├── values-ms/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-nb/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-nl/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-pl/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-pt/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-pt-rPT/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-ro/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-ru/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sk/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sl/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sr/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sv/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sw/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-sw600dp/
│ │ └── dimens.xml
│ ├── values-sw600dp-land/
│ │ └── dimens.xml
│ ├── values-th/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-tl/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-tr/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-uk/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-vi/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ ├── values-zh-rTW/
│ │ ├── arrays.xml
│ │ └── strings.xml
│ └── values-zu/
│ ├── arrays.xml
│ └── strings.xml
└── pom.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
out/
gen-external-apklibs/
# Local configuration file (sdk path, etc)
local.properties
# Eclipse project files
.classpath
.project
.settings
# Proguard folder generated by Eclipse
proguard/
# Intellij project files
*.iml
*.ipr
*.iws
.idea/
# Mac OS X files
*.DS_Store
# Maven
target
#Python
.pydevproject
#Crashlytics
com_crashlytics_export_strings.xml
#gradle
.gradle
build/
*.*~
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
================================================
FILE: README.md
================================================
Android Recurrence Picker
=========================
Google Calendar Recurrence picker
Screenshot
==========
![Example Image][1]
Usage
=====
Maven / Gradle
--------------

Maven:
<dependency>
<groupId>be.billington.calendar.recurrencepicker</groupId>
<artifactId>library</artifactId>
<version>1.1.1</version>
<type>aar</type>
</dependency>
Gradle:
compile 'be.billington.calendar.recurrencepicker:library:1.1.1'
Credits
=======
This library uses Google Calendar Date & Time pickers from [Laurent Flavien & Edison Wang's library][2]
The original source code of the recurrence picker can be found [here][3]
License
=======
[The Apache Software License, Version 2.0][4]
[1]: https://github.com/Shusshu/Android-RecurrencePicker/blob/master/screenshots/recurrence-picker.png
[2]: https://github.com/flavienlaurent/datetimepicker
[3]: https://github.com/android/platform_packages_apps_calendar/tree/master/src/com/android/calendar
[4]: http://www.apache.org/licenses/LICENSE-2.0.txt
================================================
FILE: demo/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>be.billington.calendar.recurrencepicker</groupId>
<artifactId>parent</artifactId>
<version>1.1.2-SNAPSHOT</version>
</parent>
<name>Android Date Time Picker - Demo</name>
<artifactId>demo</artifactId>
<packaging>apk</packaging>
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>4.1.1.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>be.billington.calendar.recurrencepicker</groupId>
<artifactId>library</artifactId>
<version>${project.parent.version}</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.simpligility.maven.plugins</groupId>
<artifactId>android-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<includeLibsJarsFromAar>true</includeLibsJarsFromAar>
<sdk>
<platform>${sdk.platform}</platform>
</sdk>
<sign>
<debug>true</debug>
</sign>
<undeployBeforeDeploy>true</undeployBeforeDeploy>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-plugin</artifactId>
<configuration>
<connectionType>developerConnection</connectionType>
</configuration>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: demo/project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
================================================
FILE: demo/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0.0-SNAPSHOT"
package="be.billington.calendar.recurrencepicker.demo">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light">
<activity android:name=".activity.DemoActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
================================================
FILE: demo/src/main/java/be/billington/calendar/recurrencepicker/demo/activity/DemoActivity.java
================================================
package be.billington.calendar.recurrencepicker.demo.activity;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.text.format.Time;
import android.view.View;
import android.widget.TextView;
import be.billington.calendar.recurrencepicker.EventRecurrence;
import be.billington.calendar.recurrencepicker.EventRecurrenceFormatter;
import be.billington.calendar.recurrencepicker.RecurrencePickerDialog;
import be.billington.calendar.recurrencepicker.demo.R;
import java.util.Date;
public class DemoActivity extends FragmentActivity {
private TextView recurrence;
private String recurrenceRule;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.demo);
recurrence = (TextView) findViewById(R.id.recurrence);
recurrence.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
RecurrencePickerDialog recurrencePickerDialog = new RecurrencePickerDialog();
if (recurrenceRule != null && recurrenceRule.length() > 0) {
Bundle bundle = new Bundle();
bundle.putString(RecurrencePickerDialog.BUNDLE_RRULE, recurrenceRule);
recurrencePickerDialog.setArguments(bundle);
}
recurrencePickerDialog.setOnRecurrenceSetListener(new RecurrencePickerDialog.OnRecurrenceSetListener() {
@Override
public void onRecurrenceSet(String rrule) {
recurrenceRule = rrule;
if (recurrenceRule != null && recurrenceRule.length() > 0) {
EventRecurrence recurrenceEvent = new EventRecurrence();
recurrenceEvent.setStartDate(new Time("" + new Date().getTime()));
recurrenceEvent.parse(rrule);
String srt = EventRecurrenceFormatter.getRepeatString(DemoActivity.this, getResources(), recurrenceEvent, true);
recurrence.setText(srt);
} else {
recurrence.setText("No recurrence");
}
}
});
recurrencePickerDialog.show(getSupportFragmentManager(), "recurrencePicker");
}
});
}
}
================================================
FILE: demo/src/main/res/layout/demo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linearlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/recurrence"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Recurrence picker"
android:textColor="@color/black"
android:textSize="18sp" />
</LinearLayout>
================================================
FILE: demo/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="green">#5bba1e</color>
<!--Color's with Alpha-->
<color name="black_a70">#B2000000</color>
<color name="black_a60">#99000000</color>
<color name="black_a50">#7F000000</color>
<color name="black_a45">#72000000</color>
<color name="black_a40">#66000000</color>
<color name="black_a30">#4C000000</color>
<color name="black_a20">#33000000</color>
<color name="black_a10">#19000000</color>
<color name="red_a60">#99ff0000</color>
<color name="transparent">#00000000</color>
<color name="white_a60">#99ffffff</color>
<color name="white_a50">#7FFFFFFF</color>
<color name="dark_turquoise_a50">#8000aeef</color>
<color name="gray_overlay">#CC292e2c</color>
<color name="green_a40">#665bba1e</color>
<color name="green_a10">#1969bd45</color>
</resources>
================================================
FILE: demo/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">Android Recurrence Picker - Demo</string>
</resources>
================================================
FILE: library/pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>be.billington.calendar.recurrencepicker</groupId>
<artifactId>parent</artifactId>
<version>1.1.2-SNAPSHOT</version>
</parent>
<name>Android Recurrence Picker - Library</name>
<artifactId>library</artifactId>
<packaging>aar</packaging>
<dependencies>
<dependency>
<groupId>android</groupId>
<artifactId>android</artifactId>
<version>${android.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.nineoldandroids</groupId>
<artifactId>library</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.android.support</groupId>
<artifactId>support-v4</artifactId>
<version>21.0.3</version>
<type>aar</type>
</dependency>
<dependency>
<groupId>com.github.flavienlaurent.datetimepicker</groupId>
<artifactId>library</artifactId>
<version>0.0.2</version>
<type>aar</type>
<exclusions>
<exclusion>
<groupId>com.android.support</groupId>
<artifactId>support-v4</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.simpligility.maven.plugins</groupId>
<artifactId>android-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<includeLibsJarsFromAar>true</includeLibsJarsFromAar>
<sdk>
<platform>${sdk.platform}</platform>
</sdk>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
================================================
FILE: library/project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19
android.library=true
================================================
FILE: library/release.properties
================================================
#release configuration
#Mon Jan 20 16:21:01 CET 2014
preparationGoals=clean verify
pushChanges=true
scm.commentPrefix=[maven-release-plugin]
remoteTagging=true
exec.additionalArguments=-Psonatype-oss-release
completedPhase=scm-check-modifications
scm.url=scm\:git\:git@github.com\:Shusshu/Android-RecurrencePicker.git/library
================================================
FILE: library/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0.0-SNAPSHOT"
package="be.billington.calendar.recurrencepicker">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.VIBRATE" />
</manifest>
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrence.java
================================================
package be.billington.calendar.recurrencepicker;
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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.
*/
import android.text.TextUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.TimeFormatException;
import java.util.Calendar;
import java.util.HashMap;
/**
* Event recurrence utility functions.
*/
public class EventRecurrence {
private static String TAG = "EventRecur";
public static final int SECONDLY = 1;
public static final int MINUTELY = 2;
public static final int HOURLY = 3;
public static final int DAILY = 4;
public static final int WEEKLY = 5;
public static final int MONTHLY = 6;
public static final int YEARLY = 7;
public static final int SU = 0x00010000;
public static final int MO = 0x00020000;
public static final int TU = 0x00040000;
public static final int WE = 0x00080000;
public static final int TH = 0x00100000;
public static final int FR = 0x00200000;
public static final int SA = 0x00400000;
public Time startDate; // set by setStartDate(), not parse()
public int freq; // SECONDLY, MINUTELY, etc.
public String until;
public int count;
public int interval;
public int wkst; // SU, MO, TU, etc.
/* lists with zero entries may be null references */
public int[] bysecond;
public int bysecondCount;
public int[] byminute;
public int byminuteCount;
public int[] byhour;
public int byhourCount;
public int[] byday;
public int[] bydayNum;
public int bydayCount;
public int[] bymonthday;
public int bymonthdayCount;
public int[] byyearday;
public int byyeardayCount;
public int[] byweekno;
public int byweeknoCount;
public int[] bymonth;
public int bymonthCount;
public int[] bysetpos;
public int bysetposCount;
/**
* maps a part string to a parser object
*/
private static HashMap<String, PartParser> sParsePartMap;
static {
sParsePartMap = new HashMap<String, PartParser>();
sParsePartMap.put("FREQ", new ParseFreq());
sParsePartMap.put("UNTIL", new ParseUntil());
sParsePartMap.put("COUNT", new ParseCount());
sParsePartMap.put("INTERVAL", new ParseInterval());
sParsePartMap.put("BYSECOND", new ParseBySecond());
sParsePartMap.put("BYMINUTE", new ParseByMinute());
sParsePartMap.put("BYHOUR", new ParseByHour());
sParsePartMap.put("BYDAY", new ParseByDay());
sParsePartMap.put("BYMONTHDAY", new ParseByMonthDay());
sParsePartMap.put("BYYEARDAY", new ParseByYearDay());
sParsePartMap.put("BYWEEKNO", new ParseByWeekNo());
sParsePartMap.put("BYMONTH", new ParseByMonth());
sParsePartMap.put("BYSETPOS", new ParseBySetPos());
sParsePartMap.put("WKST", new ParseWkst());
}
/* values for bit vector that keeps track of what we have already seen */
private static final int PARSED_FREQ = 1 << 0;
private static final int PARSED_UNTIL = 1 << 1;
private static final int PARSED_COUNT = 1 << 2;
private static final int PARSED_INTERVAL = 1 << 3;
private static final int PARSED_BYSECOND = 1 << 4;
private static final int PARSED_BYMINUTE = 1 << 5;
private static final int PARSED_BYHOUR = 1 << 6;
private static final int PARSED_BYDAY = 1 << 7;
private static final int PARSED_BYMONTHDAY = 1 << 8;
private static final int PARSED_BYYEARDAY = 1 << 9;
private static final int PARSED_BYWEEKNO = 1 << 10;
private static final int PARSED_BYMONTH = 1 << 11;
private static final int PARSED_BYSETPOS = 1 << 12;
private static final int PARSED_WKST = 1 << 13;
/**
* maps a FREQ value to an integer constant
*/
private static final HashMap<String, Integer> sParseFreqMap = new HashMap<String, Integer>();
static {
sParseFreqMap.put("SECONDLY", SECONDLY);
sParseFreqMap.put("MINUTELY", MINUTELY);
sParseFreqMap.put("HOURLY", HOURLY);
sParseFreqMap.put("DAILY", DAILY);
sParseFreqMap.put("WEEKLY", WEEKLY);
sParseFreqMap.put("MONTHLY", MONTHLY);
sParseFreqMap.put("YEARLY", YEARLY);
}
/**
* maps a two-character weekday string to an integer constant
*/
private static final HashMap<String, Integer> sParseWeekdayMap = new HashMap<String, Integer>();
static {
sParseWeekdayMap.put("SU", SU);
sParseWeekdayMap.put("MO", MO);
sParseWeekdayMap.put("TU", TU);
sParseWeekdayMap.put("WE", WE);
sParseWeekdayMap.put("TH", TH);
sParseWeekdayMap.put("FR", FR);
sParseWeekdayMap.put("SA", SA);
}
/**
* If set, allow lower-case recurrence rule strings. Minor performance impact.
*/
private static final boolean ALLOW_LOWER_CASE = true;
/**
* If set, validate the value of UNTIL parts. Minor performance impact.
*/
private static final boolean VALIDATE_UNTIL = false;
/**
* If set, require that only one of {UNTIL,COUNT} is present. Breaks compat w/ old parser.
*/
private static final boolean ONLY_ONE_UNTIL_COUNT = false;
/**
* Thrown when a recurrence string provided can not be parsed according
* to RFC2445.
*/
public static class InvalidFormatException extends RuntimeException {
InvalidFormatException(String s) {
super(s);
}
}
public void setStartDate(Time date) {
startDate = date;
}
/**
* Converts one of the Calendar.SUNDAY constants to the SU, MO, etc.
* constants. btw, I think we should switch to those here too, to
* get rid of this function, if possible.
*/
public static int calendarDay2Day(int day) {
switch (day) {
case Calendar.SUNDAY:
return SU;
case Calendar.MONDAY:
return MO;
case Calendar.TUESDAY:
return TU;
case Calendar.WEDNESDAY:
return WE;
case Calendar.THURSDAY:
return TH;
case Calendar.FRIDAY:
return FR;
case Calendar.SATURDAY:
return SA;
default:
throw new RuntimeException("bad day of week: " + day);
}
}
public static int timeDay2Day(int day) {
switch (day) {
case Time.SUNDAY:
return SU;
case Time.MONDAY:
return MO;
case Time.TUESDAY:
return TU;
case Time.WEDNESDAY:
return WE;
case Time.THURSDAY:
return TH;
case Time.FRIDAY:
return FR;
case Time.SATURDAY:
return SA;
default:
throw new RuntimeException("bad day of week: " + day);
}
}
public static int day2TimeDay(int day) {
switch (day) {
case SU:
return Time.SUNDAY;
case MO:
return Time.MONDAY;
case TU:
return Time.TUESDAY;
case WE:
return Time.WEDNESDAY;
case TH:
return Time.THURSDAY;
case FR:
return Time.FRIDAY;
case SA:
return Time.SATURDAY;
default:
throw new RuntimeException("bad day of week: " + day);
}
}
/**
* Converts one of the SU, MO, etc. constants to the Calendar.SUNDAY
* constants. btw, I think we should switch to those here too, to
* get rid of this function, if possible.
*/
public static int day2CalendarDay(int day) {
switch (day) {
case SU:
return Calendar.SUNDAY;
case MO:
return Calendar.MONDAY;
case TU:
return Calendar.TUESDAY;
case WE:
return Calendar.WEDNESDAY;
case TH:
return Calendar.THURSDAY;
case FR:
return Calendar.FRIDAY;
case SA:
return Calendar.SATURDAY;
default:
throw new RuntimeException("bad day of week: " + day);
}
}
/**
* Converts one of the internal day constants (SU, MO, etc.) to the
* two-letter string representing that constant.
*
* @param day one the internal constants SU, MO, etc.
* @return the two-letter string for the day ("SU", "MO", etc.)
* @throws IllegalArgumentException Thrown if the day argument is not one of
* the defined day constants.
*/
private static String day2String(int day) {
switch (day) {
case SU:
return "SU";
case MO:
return "MO";
case TU:
return "TU";
case WE:
return "WE";
case TH:
return "TH";
case FR:
return "FR";
case SA:
return "SA";
default:
throw new IllegalArgumentException("bad day argument: " + day);
}
}
private static void appendNumbers(StringBuilder s, String label,
int count, int[] values) {
if (count > 0) {
s.append(label);
count--;
for (int i = 0; i < count; i++) {
s.append(values[i]);
s.append(",");
}
s.append(values[count]);
}
}
private void appendByDay(StringBuilder s, int i) {
int n = this.bydayNum[i];
if (n != 0) {
s.append(n);
}
String str = day2String(this.byday[i]);
s.append(str);
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("FREQ=");
switch (this.freq) {
case SECONDLY:
s.append("SECONDLY");
break;
case MINUTELY:
s.append("MINUTELY");
break;
case HOURLY:
s.append("HOURLY");
break;
case DAILY:
s.append("DAILY");
break;
case WEEKLY:
s.append("WEEKLY");
break;
case MONTHLY:
s.append("MONTHLY");
break;
case YEARLY:
s.append("YEARLY");
break;
}
if (!TextUtils.isEmpty(this.until)) {
s.append(";UNTIL=");
s.append(until);
}
if (this.count != 0) {
s.append(";COUNT=");
s.append(this.count);
}
if (this.interval != 0) {
s.append(";INTERVAL=");
s.append(this.interval);
}
if (this.wkst != 0) {
s.append(";WKST=");
s.append(day2String(this.wkst));
}
appendNumbers(s, ";BYSECOND=", this.bysecondCount, this.bysecond);
appendNumbers(s, ";BYMINUTE=", this.byminuteCount, this.byminute);
appendNumbers(s, ";BYSECOND=", this.byhourCount, this.byhour);
// day
int count = this.bydayCount;
if (count > 0) {
s.append(";BYDAY=");
count--;
for (int i = 0; i < count; i++) {
appendByDay(s, i);
s.append(",");
}
appendByDay(s, count);
}
appendNumbers(s, ";BYMONTHDAY=", this.bymonthdayCount, this.bymonthday);
appendNumbers(s, ";BYYEARDAY=", this.byyeardayCount, this.byyearday);
appendNumbers(s, ";BYWEEKNO=", this.byweeknoCount, this.byweekno);
appendNumbers(s, ";BYMONTH=", this.bymonthCount, this.bymonth);
appendNumbers(s, ";BYSETPOS=", this.bysetposCount, this.bysetpos);
return s.toString();
}
public boolean repeatsOnEveryWeekDay() {
if (this.freq != WEEKLY) {
return false;
}
int count = this.bydayCount;
if (count != 5) {
return false;
}
for (int i = 0; i < count; i++) {
int day = byday[i];
if (day == SU || day == SA) {
return false;
}
}
return true;
}
/**
* Determines whether this rule specifies a simple monthly rule by weekday, such as
* "FREQ=MONTHLY;BYDAY=3TU" (the 3rd Tuesday of every month).
* <p/>
* Negative days, e.g. "FREQ=MONTHLY;BYDAY=-1TU" (the last Tuesday of every month),
* will cause "false" to be returned.
* <p/>
* Rules that fire every week, such as "FREQ=MONTHLY;BYDAY=TU" (every Tuesday of every
* month) will cause "false" to be returned. (Note these are usually expressed as
* WEEKLY rules, and hence are uncommon.)
*
* @return true if this rule is of the appropriate form
*/
public boolean repeatsMonthlyOnDayCount() {
if (this.freq != MONTHLY) {
return false;
}
if (bydayCount != 1 || bymonthdayCount != 0) {
return false;
}
if (bydayNum[0] <= 0) {
return false;
}
return true;
}
/**
* Determines whether two integer arrays contain identical elements.
* <p/>
* The native implementation over-allocated the arrays (and may have stuff left over from
* a previous run), so we can't just check the arrays -- the separately-maintained count
* field also matters. We assume that a null array will have a count of zero, and that the
* array can hold as many elements as the associated count indicates.
* <p/>
* TODO: replace this with Arrays.equals() when the old parser goes away.
*/
private static boolean arraysEqual(int[] array1, int count1, int[] array2, int count2) {
if (count1 != count2) {
return false;
}
for (int i = 0; i < count1; i++) {
if (array1[i] != array2[i])
return false;
}
return true;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof EventRecurrence)) {
return false;
}
EventRecurrence er = (EventRecurrence) obj;
return (startDate == null ?
er.startDate == null : Time.compare(startDate, er.startDate) == 0) &&
freq == er.freq &&
(until == null ? er.until == null : until.equals(er.until)) &&
count == er.count &&
interval == er.interval &&
wkst == er.wkst &&
arraysEqual(bysecond, bysecondCount, er.bysecond, er.bysecondCount) &&
arraysEqual(byminute, byminuteCount, er.byminute, er.byminuteCount) &&
arraysEqual(byhour, byhourCount, er.byhour, er.byhourCount) &&
arraysEqual(byday, bydayCount, er.byday, er.bydayCount) &&
arraysEqual(bydayNum, bydayCount, er.bydayNum, er.bydayCount) &&
arraysEqual(bymonthday, bymonthdayCount, er.bymonthday, er.bymonthdayCount) &&
arraysEqual(byyearday, byyeardayCount, er.byyearday, er.byyeardayCount) &&
arraysEqual(byweekno, byweeknoCount, er.byweekno, er.byweeknoCount) &&
arraysEqual(bymonth, bymonthCount, er.bymonth, er.bymonthCount) &&
arraysEqual(bysetpos, bysetposCount, er.bysetpos, er.bysetposCount);
}
@Override
public int hashCode() {
// We overrode equals, so we must override hashCode(). Nobody seems to need this though.
throw new UnsupportedOperationException();
}
/**
* Resets parser-modified fields to their initial state. Does not alter startDate.
* <p/>
* The original parser always set all of the "count" fields, "wkst", and "until",
* essentially allowing the same object to be used multiple times by calling parse().
* It's unclear whether this behavior was intentional. For now, be paranoid and
* preserve the existing behavior by resetting the fields.
* <p/>
* We don't need to touch the integer arrays; they will either be ignored or
* overwritten. The "startDate" field is not set by the parser, so we ignore it here.
*/
private void resetFields() {
until = null;
freq = count = interval = bysecondCount = byminuteCount = byhourCount =
bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount =
bysetposCount = 0;
}
/**
* Parses an rfc2445 recurrence rule string into its component pieces. Attempting to parse
* malformed input will result in an EventRecurrence.InvalidFormatException.
*
* @param recur The recurrence rule to parse (in un-folded form).
*/
public void parse(String recur) {
/*
* From RFC 2445 section 4.3.10:
*
* recur = "FREQ"=freq *(
* ; either UNTIL or COUNT may appear in a 'recur',
* ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
*
* ( ";" "UNTIL" "=" enddate ) /
* ( ";" "COUNT" "=" 1*DIGIT ) /
*
* ; the rest of these keywords are optional,
* ; but MUST NOT occur more than once
*
* ( ";" "INTERVAL" "=" 1*DIGIT ) /
* ( ";" "BYSECOND" "=" byseclist ) /
* ( ";" "BYMINUTE" "=" byminlist ) /
* ( ";" "BYHOUR" "=" byhrlist ) /
* ( ";" "BYDAY" "=" bywdaylist ) /
* ( ";" "BYMONTHDAY" "=" bymodaylist ) /
* ( ";" "BYYEARDAY" "=" byyrdaylist ) /
* ( ";" "BYWEEKNO" "=" bywknolist ) /
* ( ";" "BYMONTH" "=" bymolist ) /
* ( ";" "BYSETPOS" "=" bysplist ) /
* ( ";" "WKST" "=" weekday ) /
* ( ";" x-name "=" text )
* )
*
* The rule parts are not ordered in any particular sequence.
*
* Examples:
* FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
* FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
*
* Strategy:
* (1) Split the string at ';' boundaries to get an array of rule "parts".
* (2) For each part, find substrings for left/right sides of '=' (name/value).
* (3) Call a <name>-specific parsing function to parse the <value> into an
* output field.
*
* By keeping track of which names we've seen in a bit vector, we can verify the
* constraints indicated above (FREQ appears first, none of them appear more than once --
* though x-[name] would require special treatment), and we have either UNTIL or COUNT
* but not both.
*
* In general, RFC 2445 property names (e.g. "FREQ") and enumerations ("TU") must
* be handled in a case-insensitive fashion, but case may be significant for other
* properties. We don't have any case-sensitive values in RRULE, except possibly
* for the custom "X-" properties, but we ignore those anyway. Thus, we can trivially
* convert the entire string to upper case and then use simple comparisons.
*
* Differences from previous version:
* - allows lower-case property and enumeration values [optional]
* - enforces that FREQ appears first
* - enforces that only one of UNTIL and COUNT may be specified
* - allows (but ignores) X-* parts
* - improved validation on various values (e.g. UNTIL timestamps)
* - error messages are more specific
*
* TODO: enforce additional constraints listed in RFC 5545, notably the "N/A" entries
* in section 3.3.10. For example, if FREQ=WEEKLY, we should reject a rule that
* includes a BYMONTHDAY part.
*/
/* TODO: replace with "if (freq != 0) throw" if nothing requires this */
resetFields();
int parseFlags = 0;
String[] parts;
if (ALLOW_LOWER_CASE) {
parts = recur.toUpperCase().split(";");
} else {
parts = recur.split(";");
}
for (String part : parts) {
// allow empty part (e.g., double semicolon ";;")
if (TextUtils.isEmpty(part)) {
continue;
}
int equalIndex = part.indexOf('=');
if (equalIndex <= 0) {
/* no '=' or no LHS */
throw new InvalidFormatException("Missing LHS in " + part);
}
String lhs = part.substring(0, equalIndex);
String rhs = part.substring(equalIndex + 1);
if (rhs.length() == 0) {
throw new InvalidFormatException("Missing RHS in " + part);
}
/*
* In lieu of a "switch" statement that allows string arguments, we use a
* map from strings to parsing functions.
*/
PartParser parser = sParsePartMap.get(lhs);
if (parser == null) {
if (lhs.startsWith("X-")) {
//Log.d(TAG, "Ignoring custom part " + lhs);
continue;
}
throw new InvalidFormatException("Couldn't find parser for " + lhs);
} else {
int flag = parser.parsePart(rhs, this);
if ((parseFlags & flag) != 0) {
throw new InvalidFormatException("Part " + lhs + " was specified twice");
}
parseFlags |= flag;
}
}
// If not specified, week starts on Monday.
if ((parseFlags & PARSED_WKST) == 0) {
wkst = MO;
}
// FREQ is mandatory.
if ((parseFlags & PARSED_FREQ) == 0) {
throw new InvalidFormatException("Must specify a FREQ value");
}
// Can't have both UNTIL and COUNT.
if ((parseFlags & (PARSED_UNTIL | PARSED_COUNT)) == (PARSED_UNTIL | PARSED_COUNT)) {
if (ONLY_ONE_UNTIL_COUNT) {
throw new InvalidFormatException("Must not specify both UNTIL and COUNT: " + recur);
} else {
Log.w(TAG, "Warning: rrule has both UNTIL and COUNT: " + recur);
}
}
}
/**
* Base class for the RRULE part parsers.
*/
abstract static class PartParser {
/**
* Parses a single part.
*
* @param value The right-hand-side of the part.
* @param er The EventRecurrence into which the result is stored.
* @return A bit value indicating which part was parsed.
*/
public abstract int parsePart(String value, EventRecurrence er);
/**
* Parses an integer, with range-checking.
*
* @param str The string to parse.
* @param minVal Minimum allowed value.
* @param maxVal Maximum allowed value.
* @param allowZero Is 0 allowed?
* @return The parsed value.
*/
public static int parseIntRange(String str, int minVal, int maxVal, boolean allowZero) {
try {
if (str.charAt(0) == '+') {
// Integer.parseInt does not allow a leading '+', so skip it manually.
str = str.substring(1);
}
int val = Integer.parseInt(str);
if (val < minVal || val > maxVal || (val == 0 && !allowZero)) {
throw new InvalidFormatException("Integer value out of range: " + str);
}
return val;
} catch (NumberFormatException nfe) {
throw new InvalidFormatException("Invalid integer value: " + str);
}
}
/**
* Parses a comma-separated list of integers, with range-checking.
*
* @param listStr The string to parse.
* @param minVal Minimum allowed value.
* @param maxVal Maximum allowed value.
* @param allowZero Is 0 allowed?
* @return A new array with values, sized to hold the exact number of elements.
*/
public static int[] parseNumberList(String listStr, int minVal, int maxVal,
boolean allowZero) {
int[] values;
if (listStr.indexOf(",") < 0) {
// Common case: only one entry, skip split() overhead.
values = new int[1];
values[0] = parseIntRange(listStr, minVal, maxVal, allowZero);
} else {
String[] valueStrs = listStr.split(",");
int len = valueStrs.length;
values = new int[len];
for (int i = 0; i < len; i++) {
values[i] = parseIntRange(valueStrs[i], minVal, maxVal, allowZero);
}
}
return values;
}
}
/**
* parses FREQ={SECONDLY,MINUTELY,...}
*/
private static class ParseFreq extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
Integer freq = sParseFreqMap.get(value);
if (freq == null) {
throw new InvalidFormatException("Invalid FREQ value: " + value);
}
er.freq = freq;
return PARSED_FREQ;
}
}
/**
* parses UNTIL=enddate, e.g. "19970829T021400"
*/
private static class ParseUntil extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
if (VALIDATE_UNTIL) {
try {
// Parse the time to validate it. The result isn't retained.
Time until = new Time();
until.parse(value);
} catch (TimeFormatException tfe) {
throw new InvalidFormatException("Invalid UNTIL value: " + value);
}
}
er.until = value;
return PARSED_UNTIL;
}
}
/**
* parses COUNT=[non-negative-integer]
*/
private static class ParseCount extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
er.count = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
if (er.count < 0) {
Log.d(TAG, "Invalid Count. Forcing COUNT to 1 from " + value);
er.count = 1; // invalid count. assume one time recurrence.
}
return PARSED_COUNT;
}
}
/**
* parses INTERVAL=[non-negative-integer]
*/
private static class ParseInterval extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
er.interval = parseIntRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
if (er.interval < 1) {
Log.d(TAG, "Invalid Interval. Forcing INTERVAL to 1 from " + value);
er.interval = 1;
}
return PARSED_INTERVAL;
}
}
/**
* parses BYSECOND=byseclist
*/
private static class ParseBySecond extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] bysecond = parseNumberList(value, 0, 59, true);
er.bysecond = bysecond;
er.bysecondCount = bysecond.length;
return PARSED_BYSECOND;
}
}
/**
* parses BYMINUTE=byminlist
*/
private static class ParseByMinute extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] byminute = parseNumberList(value, 0, 59, true);
er.byminute = byminute;
er.byminuteCount = byminute.length;
return PARSED_BYMINUTE;
}
}
/**
* parses BYHOUR=byhrlist
*/
private static class ParseByHour extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] byhour = parseNumberList(value, 0, 23, true);
er.byhour = byhour;
er.byhourCount = byhour.length;
return PARSED_BYHOUR;
}
}
/**
* parses BYDAY=bywdaylist, e.g. "1SU,-1SU"
*/
private static class ParseByDay extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] byday;
int[] bydayNum;
int bydayCount;
if (value.indexOf(",") < 0) {
/* only one entry, skip split() overhead */
bydayCount = 1;
byday = new int[1];
bydayNum = new int[1];
parseWday(value, byday, bydayNum, 0);
} else {
String[] wdays = value.split(",");
int len = wdays.length;
bydayCount = len;
byday = new int[len];
bydayNum = new int[len];
for (int i = 0; i < len; i++) {
parseWday(wdays[i], byday, bydayNum, i);
}
}
er.byday = byday;
er.bydayNum = bydayNum;
er.bydayCount = bydayCount;
return PARSED_BYDAY;
}
/**
* parses [int]weekday, putting the pieces into parallel array entries
*/
private static void parseWday(String str, int[] byday, int[] bydayNum, int index) {
int wdayStrStart = str.length() - 2;
String wdayStr;
if (wdayStrStart > 0) {
/* number is included; parse it out and advance to weekday */
String numPart = str.substring(0, wdayStrStart);
int num = parseIntRange(numPart, -53, 53, false);
bydayNum[index] = num;
wdayStr = str.substring(wdayStrStart);
} else {
/* just the weekday string */
wdayStr = str;
}
Integer wday = sParseWeekdayMap.get(wdayStr);
if (wday == null) {
throw new InvalidFormatException("Invalid BYDAY value: " + str);
}
byday[index] = wday;
}
}
/**
* parses BYMONTHDAY=bymodaylist
*/
private static class ParseByMonthDay extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] bymonthday = parseNumberList(value, -31, 31, false);
er.bymonthday = bymonthday;
er.bymonthdayCount = bymonthday.length;
return PARSED_BYMONTHDAY;
}
}
/**
* parses BYYEARDAY=byyrdaylist
*/
private static class ParseByYearDay extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] byyearday = parseNumberList(value, -366, 366, false);
er.byyearday = byyearday;
er.byyeardayCount = byyearday.length;
return PARSED_BYYEARDAY;
}
}
/**
* parses BYWEEKNO=bywknolist
*/
private static class ParseByWeekNo extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] byweekno = parseNumberList(value, -53, 53, false);
er.byweekno = byweekno;
er.byweeknoCount = byweekno.length;
return PARSED_BYWEEKNO;
}
}
/**
* parses BYMONTH=bymolist
*/
private static class ParseByMonth extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] bymonth = parseNumberList(value, 1, 12, false);
er.bymonth = bymonth;
er.bymonthCount = bymonth.length;
return PARSED_BYMONTH;
}
}
/**
* parses BYSETPOS=bysplist
*/
private static class ParseBySetPos extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
int[] bysetpos = parseNumberList(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
er.bysetpos = bysetpos;
er.bysetposCount = bysetpos.length;
return PARSED_BYSETPOS;
}
}
/**
* parses WKST={SU,MO,...}
*/
private static class ParseWkst extends PartParser {
@Override
public int parsePart(String value, EventRecurrence er) {
Integer wkst = sParseWeekdayMap.get(value);
if (wkst == null) {
throw new InvalidFormatException("Invalid WKST value: " + value);
}
er.wkst = wkst;
return PARSED_WKST;
}
}
}
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrenceFormatter.java
================================================
package be.billington.calendar.recurrencepicker;
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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.
*/
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.TimeFormatException;
import java.util.Calendar;
public class EventRecurrenceFormatter
{
private static int[] mMonthRepeatByDayOfWeekIds;
private static String[][] mMonthRepeatByDayOfWeekStrs;
public static String getRepeatString(Context context, Resources r, EventRecurrence recurrence,
boolean includeEndString) {
String endString = "";
if (includeEndString) {
StringBuilder sb = new StringBuilder();
if (recurrence.until != null) {
try {
Time t = new Time();
t.parse(recurrence.until);
final String dateStr = DateUtils.formatDateTime(context,
t.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE);
sb.append(r.getString(R.string.endByDate, dateStr));
} catch (TimeFormatException e) {
}
}
if (recurrence.count > 0) {
sb.append(r.getQuantityString(R.plurals.endByCount, recurrence.count,
recurrence.count));
}
endString = sb.toString();
}
// TODO Implement "Until" portion of string, as well as custom settings
int interval = recurrence.interval <= 1 ? 1 : recurrence.interval;
switch (recurrence.freq) {
case EventRecurrence.DAILY:
return r.getQuantityString(R.plurals.daily, interval, interval) + endString;
case EventRecurrence.WEEKLY: {
if (recurrence.repeatsOnEveryWeekDay()) {
return r.getString(R.string.every_weekday) + endString;
} else {
String string;
int dayOfWeekLength = DateUtils.LENGTH_MEDIUM;
if (recurrence.bydayCount == 1) {
dayOfWeekLength = DateUtils.LENGTH_LONG;
}
StringBuilder days = new StringBuilder();
// Do one less iteration in the loop so the last element is added out of the
// loop. This is done so the comma is not placed after the last item.
if (recurrence.bydayCount > 0) {
int count = recurrence.bydayCount - 1;
for (int i = 0 ; i < count ; i++) {
days.append(dayToString(recurrence.byday[i], dayOfWeekLength));
days.append(", ");
}
days.append(dayToString(recurrence.byday[count], dayOfWeekLength));
string = days.toString();
} else {
// There is no "BYDAY" specifier, so use the day of the
// first event. For this to work, the setStartDate()
// method must have been used by the caller to set the
// date of the first event in the recurrence.
if (recurrence.startDate == null) {
return null;
}
int day = EventRecurrence.timeDay2Day(recurrence.startDate.weekDay);
string = dayToString(day, DateUtils.LENGTH_LONG);
}
return r.getQuantityString(R.plurals.weekly, interval, interval, string)
+ endString;
}
}
case EventRecurrence.MONTHLY: {
if (recurrence.bydayCount == 1) {
int weekday = recurrence.startDate.weekDay;
// Cache this stuff so we won't have to redo work again later.
cacheMonthRepeatStrings(r, weekday);
int dayNumber = (recurrence.startDate.monthDay - 1) / 7;
StringBuilder sb = new StringBuilder();
sb.append(r.getString(R.string.monthly));
sb.append(" (");
sb.append(mMonthRepeatByDayOfWeekStrs[weekday][dayNumber]);
sb.append(")");
sb.append(endString);
return sb.toString();
}
return r.getString(R.string.monthly) + endString;
}
case EventRecurrence.YEARLY:
return r.getString(R.string.yearly_plain) + endString;
}
return null;
}
private static void cacheMonthRepeatStrings(Resources r, int weekday) {
if (mMonthRepeatByDayOfWeekIds == null) {
mMonthRepeatByDayOfWeekIds = new int[7];
mMonthRepeatByDayOfWeekIds[0] = R.array.repeat_by_nth_sun;
mMonthRepeatByDayOfWeekIds[1] = R.array.repeat_by_nth_mon;
mMonthRepeatByDayOfWeekIds[2] = R.array.repeat_by_nth_tues;
mMonthRepeatByDayOfWeekIds[3] = R.array.repeat_by_nth_wed;
mMonthRepeatByDayOfWeekIds[4] = R.array.repeat_by_nth_thurs;
mMonthRepeatByDayOfWeekIds[5] = R.array.repeat_by_nth_fri;
mMonthRepeatByDayOfWeekIds[6] = R.array.repeat_by_nth_sat;
}
if (mMonthRepeatByDayOfWeekStrs == null) {
mMonthRepeatByDayOfWeekStrs = new String[7][];
}
if (mMonthRepeatByDayOfWeekStrs[weekday] == null) {
mMonthRepeatByDayOfWeekStrs[weekday] =
r.getStringArray(mMonthRepeatByDayOfWeekIds[weekday]);
}
}
/**
* Converts day of week to a String.
* @param day a EventRecurrence constant
* @return day of week as a string
*/
private static String dayToString(int day, int dayOfWeekLength) {
return DateUtils.getDayOfWeekString(dayToUtilDay(day), dayOfWeekLength);
}
/**
* Converts EventRecurrence's day of week to DateUtil's day of week.
* @param day of week as an EventRecurrence value
* @return day of week as a DateUtil value.
*/
private static int dayToUtilDay(int day) {
switch (day) {
case EventRecurrence.SU: return Calendar.SUNDAY;
case EventRecurrence.MO: return Calendar.MONDAY;
case EventRecurrence.TU: return Calendar.TUESDAY;
case EventRecurrence.WE: return Calendar.WEDNESDAY;
case EventRecurrence.TH: return Calendar.THURSDAY;
case EventRecurrence.FR: return Calendar.FRIDAY;
case EventRecurrence.SA: return Calendar.SATURDAY;
default: throw new IllegalArgumentException("bad day argument: " + day);
}
}
}
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java
================================================
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 be.billington.calendar.recurrencepicker;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
public class LinearLayoutWithMaxWidth extends LinearLayout {
public LinearLayoutWithMaxWidth(Context context) {
super(context);
}
public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
WeekButton.setSuggestedWidth((View.MeasureSpec.getSize(widthMeasureSpec)) / 7);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/RecurrencePickerDialog.java
================================================
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 be.billington.calendar.recurrencepicker;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.DialogFragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.TimeFormatException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TableLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import com.fourmob.datetimepicker.date.DatePickerDialog;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
public class RecurrencePickerDialog extends DialogFragment implements OnItemSelectedListener,
OnCheckedChangeListener, OnClickListener,
android.widget.RadioGroup.OnCheckedChangeListener, DatePickerDialog.OnDateSetListener {
private static final String TAG = "RecurrencePickerDialog";
// in dp's
private static final int MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK = 450;
// Update android:maxLength in EditText as needed
private static final int INTERVAL_MAX = 99;
private static final int INTERVAL_DEFAULT = 1;
// Update android:maxLength in EditText as needed
private static final int COUNT_MAX = 730;
private static final int COUNT_DEFAULT = 5;
// Special cases in monthlyByNthDayOfWeek
private static final int FIFTH_WEEK_IN_A_MONTH = 5;
private static final int LAST_NTH_DAY_OF_WEEK = -1;
private DatePickerDialog mDatePickerDialog;
private class RecurrenceModel implements Parcelable {
// Should match EventRecurrence.DAILY, etc
static final int FREQ_DAILY = 0;
static final int FREQ_WEEKLY = 1;
static final int FREQ_MONTHLY = 2;
static final int FREQ_YEARLY = 3;
static final int END_NEVER = 0;
static final int END_BY_DATE = 1;
static final int END_BY_COUNT = 2;
static final int MONTHLY_BY_DATE = 0;
static final int MONTHLY_BY_NTH_DAY_OF_WEEK = 1;
static final int STATE_NO_RECURRENCE = 0;
static final int STATE_RECURRENCE = 1;
int recurrenceState;
/**
* FREQ: Repeat pattern
*
* @see FREQ_DAILY
* @see FREQ_WEEKLY
* @see FREQ_MONTHLY
* @see FREQ_YEARLY
*/
int freq = FREQ_WEEKLY;
/**
* INTERVAL: Every n days/weeks/months/years. n >= 1
*/
int interval = INTERVAL_DEFAULT;
/**
* UNTIL and COUNT: How does the the event end?
*
* @see END_NEVER
* @see END_BY_DATE
* @see END_BY_COUNT
* @see untilDate
* @see untilCount
*/
int end;
/**
* UNTIL: Date of the last recurrence. Used when until == END_BY_DATE
*/
Time endDate;
/**
* COUNT: Times to repeat. Use when until == END_BY_COUNT
*/
int endCount = COUNT_DEFAULT;
/**
* BYDAY: Days of the week to be repeated. Sun = 0, Mon = 1, etc
*/
boolean[] weeklyByDayOfWeek = new boolean[7];
/**
* BYDAY AND BYMONTHDAY: How to repeat monthly events? Same date of the
* month or Same nth day of week.
*
* @see MONTHLY_BY_DATE
* @see MONTHLY_BY_NTH_DAY_OF_WEEK
*/
int monthlyRepeat;
/**
* Day of the month to repeat. Used when monthlyRepeat ==
* MONTHLY_BY_DATE
*/
int monthlyByMonthDay;
/**
* Day of the week to repeat. Used when monthlyRepeat ==
* MONTHLY_BY_NTH_DAY_OF_WEEK
*/
int monthlyByDayOfWeek;
/**
* Nth day of the week to repeat. Used when monthlyRepeat ==
* MONTHLY_BY_NTH_DAY_OF_WEEK 0=undefined, -1=Last, 1=1st, 2=2nd, ..., 5=5th
* <p/>
* We support 5th, just to handle backwards capabilities with old bug, but it
* gets converted to -1 once edited.
*/
int monthlyByNthDayOfWeek;
/*
* (generated method)
*/
@Override
public String toString() {
return "Model [freq=" + freq + ", interval=" + interval + ", end=" + end + ", endDate="
+ endDate + ", endCount=" + endCount + ", weeklyByDayOfWeek="
+ Arrays.toString(weeklyByDayOfWeek) + ", monthlyRepeat=" + monthlyRepeat
+ ", monthlyByMonthDay=" + monthlyByMonthDay + ", monthlyByDayOfWeek="
+ monthlyByDayOfWeek + ", monthlyByNthDayOfWeek=" + monthlyByNthDayOfWeek + "]";
}
@Override
public int describeContents() {
return 0;
}
public RecurrenceModel() {
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(freq);
dest.writeInt(interval);
dest.writeInt(end);
dest.writeInt(endDate.year);
dest.writeInt(endDate.month);
dest.writeInt(endDate.monthDay);
dest.writeInt(endCount);
dest.writeBooleanArray(weeklyByDayOfWeek);
dest.writeInt(monthlyRepeat);
dest.writeInt(monthlyByMonthDay);
dest.writeInt(monthlyByDayOfWeek);
dest.writeInt(monthlyByNthDayOfWeek);
dest.writeInt(recurrenceState);
}
}
class minMaxTextWatcher implements TextWatcher {
private int mMin;
private int mMax;
private int mDefault;
public minMaxTextWatcher(int min, int defaultInt, int max) {
mMin = min;
mMax = max;
mDefault = defaultInt;
}
@Override
public void afterTextChanged(Editable s) {
boolean updated = false;
int value;
try {
value = Integer.parseInt(s.toString());
} catch (NumberFormatException e) {
value = mDefault;
}
if (value < mMin) {
value = mMin;
updated = true;
} else if (value > mMax) {
updated = true;
value = mMax;
}
// Update UI
if (updated) {
s.clear();
s.append(Integer.toString(value));
}
updateDoneButtonState();
onChange(value);
}
/**
* Override to be called after each key stroke
*/
void onChange(int value) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
private Resources mResources;
private EventRecurrence mRecurrence = new EventRecurrence();
private Time mTime = new Time(); // TODO timezone?
private RecurrenceModel mModel = new RecurrenceModel();
private Toast mToast;
private final int[] TIME_DAY_TO_CALENDAR_DAY = new int[]{
Calendar.SUNDAY,
Calendar.MONDAY,
Calendar.TUESDAY,
Calendar.WEDNESDAY,
Calendar.THURSDAY,
Calendar.FRIDAY,
Calendar.SATURDAY,
};
// Call mStringBuilder.setLength(0) before formatting any string or else the
// formatted text will accumulate.
// private final StringBuilder mStringBuilder = new StringBuilder();
// private Formatter mFormatter = new Formatter(mStringBuilder);
private View mView;
private Spinner mFreqSpinner;
private static final int[] mFreqModelToEventRecurrence = {
EventRecurrence.DAILY,
EventRecurrence.WEEKLY,
EventRecurrence.MONTHLY,
EventRecurrence.YEARLY
};
public static final String BUNDLE_START_TIME_MILLIS = "bundle_event_start_time";
public static final String BUNDLE_TIME_ZONE = "bundle_event_time_zone";
public static final String BUNDLE_RRULE = "bundle_event_rrule";
private static final String BUNDLE_MODEL = "bundle_model";
private static final String BUNDLE_END_COUNT_HAS_FOCUS = "bundle_end_count_has_focus";
private static final String FRAG_TAG_DATE_PICKER = "tag_date_picker_frag";
private Switch mRepeatSwitch;
private EditText mInterval;
private TextView mIntervalPreText;
private TextView mIntervalPostText;
private int mIntervalResId = -1;
private Spinner mEndSpinner;
private TextView mEndDateTextView;
private EditText mEndCount;
private TextView mPostEndCount;
private boolean mHidePostEndCount;
private ArrayList<CharSequence> mEndSpinnerArray = new ArrayList<CharSequence>(3);
private EndSpinnerAdapter mEndSpinnerAdapter;
private String mEndNeverStr;
private String mEndDateLabel;
private String mEndCountLabel;
/**
* Hold toggle buttons in the order per user's first day of week preference
*/
private LinearLayout mWeekGroup;
private LinearLayout mWeekGroup2;
// Sun = 0
private ToggleButton[] mWeekByDayButtons = new ToggleButton[7];
/**
* A double array of Strings to hold the 7x5 list of possible strings of the form:
* "on every [Nth] [DAY_OF_WEEK]", e.g. "on every second Monday",
* where [Nth] can be [first, second, third, fourth, last]
*/
private String[][] mMonthRepeatByDayOfWeekStrs;
private LinearLayout mMonthGroup;
private RadioGroup mMonthRepeatByRadioGroup;
private RadioButton mRepeatMonthlyByNthDayOfWeek;
private RadioButton mRepeatMonthlyByNthDayOfMonth;
private String mMonthRepeatByDayOfWeekStr;
private Button mDone;
private OnRecurrenceSetListener mRecurrenceSetListener;
public RecurrencePickerDialog() {
}
static public boolean isSupportedMonthlyByNthDayOfWeek(int num) {
// We only support monthlyByNthDayOfWeek when it is greater then 0 but less then 5.
// Or if -1 when it is the last monthly day of the week.
return (num > 0 && num <= FIFTH_WEEK_IN_A_MONTH) || num == LAST_NTH_DAY_OF_WEEK;
}
static public boolean canHandleRecurrenceRule(EventRecurrence er) {
switch (er.freq) {
case EventRecurrence.DAILY:
case EventRecurrence.MONTHLY:
case EventRecurrence.YEARLY:
case EventRecurrence.WEEKLY:
break;
default:
return false;
}
if (er.count > 0 && !TextUtils.isEmpty(er.until)) {
return false;
}
// Weekly: For "repeat by day of week", the day of week to repeat is in
// er.byday[]
/*
* Monthly: For "repeat by nth day of week" the day of week to repeat is
* in er.byday[] and the "nth" is stored in er.bydayNum[]. Currently we
* can handle only one and only in monthly
*/
int numOfByDayNum = 0;
for (int i = 0; i < er.bydayCount; i++) {
if (isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) {
++numOfByDayNum;
}
}
if (numOfByDayNum > 1) {
return false;
}
if (numOfByDayNum > 0 && er.freq != EventRecurrence.MONTHLY) {
return false;
}
// The UI only handle repeat by one day of month i.e. not 9th and 10th
// of every month
if (er.bymonthdayCount > 1) {
return false;
}
if (er.freq == EventRecurrence.MONTHLY) {
if (er.bydayCount > 1) {
return false;
}
if (er.bydayCount > 0 && er.bymonthdayCount > 0) {
return false;
}
}
return true;
}
// TODO don't lose data when getting data that our UI can't handle
static private void copyEventRecurrenceToModel(final EventRecurrence er,
RecurrenceModel model) {
// Freq:
switch (er.freq) {
case EventRecurrence.DAILY:
model.freq = RecurrenceModel.FREQ_DAILY;
break;
case EventRecurrence.MONTHLY:
model.freq = RecurrenceModel.FREQ_MONTHLY;
break;
case EventRecurrence.YEARLY:
model.freq = RecurrenceModel.FREQ_YEARLY;
break;
case EventRecurrence.WEEKLY:
model.freq = RecurrenceModel.FREQ_WEEKLY;
break;
default:
throw new IllegalStateException("freq=" + er.freq);
}
// Interval:
if (er.interval > 0) {
model.interval = er.interval;
}
// End:
// End by count:
model.endCount = er.count;
if (model.endCount > 0) {
model.end = RecurrenceModel.END_BY_COUNT;
}
// End by date:
if (!TextUtils.isEmpty(er.until)) {
if (model.endDate == null) {
model.endDate = new Time();
}
try {
model.endDate.parse(er.until);
} catch (TimeFormatException e) {
model.endDate = null;
}
// LIMITATION: The UI can only handle END_BY_DATE or END_BY_COUNT
if (model.end == RecurrenceModel.END_BY_COUNT && model.endDate != null) {
throw new IllegalStateException("freq=" + er.freq);
}
model.end = RecurrenceModel.END_BY_DATE;
}
// Weekly: repeat by day of week or Monthly: repeat by nth day of week
// in the month
Arrays.fill(model.weeklyByDayOfWeek, false);
if (er.bydayCount > 0) {
int count = 0;
for (int i = 0; i < er.bydayCount; i++) {
int dayOfWeek = EventRecurrence.day2TimeDay(er.byday[i]);
model.weeklyByDayOfWeek[dayOfWeek] = true;
if (model.freq == RecurrenceModel.FREQ_MONTHLY &&
isSupportedMonthlyByNthDayOfWeek(er.bydayNum[i])) {
// LIMITATION: Can handle only (one) weekDayNum in nth or last and only
// when
// monthly
model.monthlyByDayOfWeek = dayOfWeek;
model.monthlyByNthDayOfWeek = er.bydayNum[i];
model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
count++;
}
}
if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
if (er.bydayCount != 1) {
// Can't handle 1st Monday and 2nd Wed
throw new IllegalStateException("Can handle only 1 byDayOfWeek in monthly");
}
if (count != 1) {
throw new IllegalStateException(
"Didn't specify which nth day of week to repeat for a monthly");
}
}
}
// Monthly by day of month
if (model.freq == RecurrenceModel.FREQ_MONTHLY) {
if (er.bymonthdayCount == 1) {
if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
throw new IllegalStateException(
"Can handle only by monthday or by nth day of week, not both");
}
model.monthlyByMonthDay = er.bymonthday[0];
model.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
} else if (er.bymonthCount > 1) {
// LIMITATION: Can handle only one month day
throw new IllegalStateException("Can handle only one bymonthday");
}
}
}
static private void copyModelToEventRecurrence(final RecurrenceModel model,
EventRecurrence er) {
if (model.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
throw new IllegalStateException("There's no recurrence");
}
// Freq
er.freq = mFreqModelToEventRecurrence[model.freq];
// Interval
if (model.interval <= 1) {
er.interval = 0;
} else {
er.interval = model.interval;
}
// End
switch (model.end) {
case RecurrenceModel.END_BY_DATE:
if (model.endDate != null) {
model.endDate.switchTimezone(Time.TIMEZONE_UTC);
model.endDate.normalize(false);
er.until = model.endDate.format2445();
er.count = 0;
} else {
throw new IllegalStateException("end = END_BY_DATE but endDate is null");
}
break;
case RecurrenceModel.END_BY_COUNT:
er.count = model.endCount;
er.until = null;
if (er.count <= 0) {
throw new IllegalStateException("count is " + er.count);
}
break;
default:
er.count = 0;
er.until = null;
break;
}
// Weekly && monthly repeat patterns
er.bydayCount = 0;
er.bymonthdayCount = 0;
switch (model.freq) {
case RecurrenceModel.FREQ_MONTHLY:
if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
if (model.monthlyByMonthDay > 0) {
if (er.bymonthday == null || er.bymonthdayCount < 1) {
er.bymonthday = new int[1];
}
er.bymonthday[0] = model.monthlyByMonthDay;
er.bymonthdayCount = 1;
}
} else if (model.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
if (!isSupportedMonthlyByNthDayOfWeek(model.monthlyByNthDayOfWeek)) {
throw new IllegalStateException("month repeat by nth week but n is "
+ model.monthlyByNthDayOfWeek);
}
int count = 1;
if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
er.byday = new int[count];
er.bydayNum = new int[count];
}
er.bydayCount = count;
er.byday[0] = EventRecurrence.timeDay2Day(model.monthlyByDayOfWeek);
er.bydayNum[0] = model.monthlyByNthDayOfWeek;
}
break;
case RecurrenceModel.FREQ_WEEKLY:
int count = 0;
for (int i = 0; i < 7; i++) {
if (model.weeklyByDayOfWeek[i]) {
count++;
}
}
if (er.bydayCount < count || er.byday == null || er.bydayNum == null) {
er.byday = new int[count];
er.bydayNum = new int[count];
}
er.bydayCount = count;
for (int i = 6; i >= 0; i--) {
if (model.weeklyByDayOfWeek[i]) {
er.bydayNum[--count] = 0;
er.byday[count] = EventRecurrence.timeDay2Day(i);
}
}
break;
}
if (!canHandleRecurrenceRule(er)) {
throw new IllegalStateException("UI generated recurrence that it can't handle. ER:"
+ er.toString() + " Model: " + model.toString());
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRecurrence.wkst = EventRecurrence.timeDay2Day(Utils.getFirstDayOfWeek(getActivity()));
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
boolean endCountHasFocus = false;
if (savedInstanceState != null) {
RecurrenceModel m = (RecurrenceModel) savedInstanceState.get(BUNDLE_MODEL);
if (m != null) {
mModel = m;
}
endCountHasFocus = savedInstanceState.getBoolean(BUNDLE_END_COUNT_HAS_FOCUS);
} else {
Bundle b = getArguments();
if (b != null) {
mTime.set(b.getLong(BUNDLE_START_TIME_MILLIS));
String tz = b.getString(BUNDLE_TIME_ZONE);
if (!TextUtils.isEmpty(tz)) {
mTime.timezone = tz;
}
mTime.normalize(false);
// Time days of week: Sun=0, Mon=1, etc
mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
String rrule = b.getString(BUNDLE_RRULE);
if (!TextUtils.isEmpty(rrule)) {
mModel.recurrenceState = RecurrenceModel.STATE_RECURRENCE;
mRecurrence.parse(rrule);
copyEventRecurrenceToModel(mRecurrence, mModel);
// Leave today's day of week as checked by default in weekly view.
if (mRecurrence.bydayCount == 0) {
mModel.weeklyByDayOfWeek[mTime.weekDay] = true;
}
}
} else {
mTime.setToNow();
}
}
mResources = getResources();
mView = inflater.inflate(R.layout.recurrencepicker, container, true);
final Activity activity = getActivity();
final Configuration config = activity.getResources().getConfiguration();
mRepeatSwitch = (Switch) mView.findViewById(R.id.repeat_switch);
mRepeatSwitch.setChecked(mModel.recurrenceState == RecurrenceModel.STATE_RECURRENCE);
mRepeatSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mModel.recurrenceState = isChecked ? RecurrenceModel.STATE_RECURRENCE
: RecurrenceModel.STATE_NO_RECURRENCE;
togglePickerOptions();
}
});
mFreqSpinner = (Spinner) mView.findViewById(R.id.freqSpinner);
mFreqSpinner.setOnItemSelectedListener(this);
ArrayAdapter<CharSequence> freqAdapter = ArrayAdapter.createFromResource(getActivity(),
R.array.recurrence_freq, R.layout.recurrencepicker_freq_item);
freqAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
mFreqSpinner.setAdapter(freqAdapter);
mInterval = (EditText) mView.findViewById(R.id.interval);
mInterval.addTextChangedListener(new minMaxTextWatcher(1, INTERVAL_DEFAULT, INTERVAL_MAX) {
@Override
void onChange(int v) {
if (mIntervalResId != -1 && mInterval.getText().toString().length() > 0) {
mModel.interval = v;
updateIntervalText();
mInterval.requestLayout();
}
}
});
mIntervalPreText = (TextView) mView.findViewById(R.id.intervalPreText);
mIntervalPostText = (TextView) mView.findViewById(R.id.intervalPostText);
mEndNeverStr = mResources.getString(R.string.recurrence_end_continously);
mEndDateLabel = mResources.getString(R.string.recurrence_end_date_label);
mEndCountLabel = mResources.getString(R.string.recurrence_end_count_label);
mEndSpinnerArray.add(mEndNeverStr);
mEndSpinnerArray.add(mEndDateLabel);
mEndSpinnerArray.add(mEndCountLabel);
mEndSpinner = (Spinner) mView.findViewById(R.id.endSpinner);
mEndSpinner.setOnItemSelectedListener(this);
mEndSpinnerAdapter = new EndSpinnerAdapter(getActivity(), mEndSpinnerArray,
R.layout.recurrencepicker_freq_item, R.layout.recurrencepicker_end_text);
mEndSpinnerAdapter.setDropDownViewResource(R.layout.recurrencepicker_freq_item);
mEndSpinner.setAdapter(mEndSpinnerAdapter);
mEndCount = (EditText) mView.findViewById(R.id.endCount);
mEndCount.addTextChangedListener(new minMaxTextWatcher(1, COUNT_DEFAULT, COUNT_MAX) {
@Override
void onChange(int v) {
if (mModel.endCount != v) {
mModel.endCount = v;
updateEndCountText();
mEndCount.requestLayout();
}
}
});
mPostEndCount = (TextView) mView.findViewById(R.id.postEndCount);
mEndDateTextView = (TextView) mView.findViewById(R.id.endDate);
mEndDateTextView.setOnClickListener(this);
if (mModel.endDate == null) {
mModel.endDate = new Time(mTime);
switch (mModel.freq) {
case RecurrenceModel.FREQ_DAILY:
case RecurrenceModel.FREQ_WEEKLY:
mModel.endDate.month += 1;
break;
case RecurrenceModel.FREQ_MONTHLY:
mModel.endDate.month += 3;
break;
case RecurrenceModel.FREQ_YEARLY:
mModel.endDate.year += 3;
break;
}
mModel.endDate.normalize(false);
}
mWeekGroup = (LinearLayout) mView.findViewById(R.id.weekGroup);
mWeekGroup2 = (LinearLayout) mView.findViewById(R.id.weekGroup2);
// In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
String[] dayOfWeekString = new DateFormatSymbols().getWeekdays();
mMonthRepeatByDayOfWeekStrs = new String[7][];
// from Time.SUNDAY as 0 through Time.SATURDAY as 6
mMonthRepeatByDayOfWeekStrs[0] = mResources.getStringArray(R.array.repeat_by_nth_sun);
mMonthRepeatByDayOfWeekStrs[1] = mResources.getStringArray(R.array.repeat_by_nth_mon);
mMonthRepeatByDayOfWeekStrs[2] = mResources.getStringArray(R.array.repeat_by_nth_tues);
mMonthRepeatByDayOfWeekStrs[3] = mResources.getStringArray(R.array.repeat_by_nth_wed);
mMonthRepeatByDayOfWeekStrs[4] = mResources.getStringArray(R.array.repeat_by_nth_thurs);
mMonthRepeatByDayOfWeekStrs[5] = mResources.getStringArray(R.array.repeat_by_nth_fri);
mMonthRepeatByDayOfWeekStrs[6] = mResources.getStringArray(R.array.repeat_by_nth_sat);
// In Time.java day of week order e.g. Sun = 0
int idx = Utils.getFirstDayOfWeek(getActivity());
// In Calendar.java day of week order e.g Sun = 1 ... Sat = 7
dayOfWeekString = new DateFormatSymbols().getShortWeekdays();
int numOfButtonsInRow1;
int numOfButtonsInRow2;
if (mResources.getConfiguration().screenWidthDp > MIN_SCREEN_WIDTH_FOR_SINGLE_ROW_WEEK) {
numOfButtonsInRow1 = 7;
numOfButtonsInRow2 = 0;
mWeekGroup2.setVisibility(View.GONE);
mWeekGroup2.getChildAt(3).setVisibility(View.GONE);
} else {
numOfButtonsInRow1 = 4;
numOfButtonsInRow2 = 3;
mWeekGroup2.setVisibility(View.VISIBLE);
// Set rightmost button on the second row invisible so it takes up
// space and everything centers properly
mWeekGroup2.getChildAt(3).setVisibility(View.INVISIBLE);
}
/* First row */
for (int i = 0; i < 7; i++) {
if (i >= numOfButtonsInRow1) {
mWeekGroup.getChildAt(i).setVisibility(View.GONE);
continue;
}
mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup.getChildAt(i);
mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
if (++idx >= 7) {
idx = 0;
}
}
/* 2nd Row */
for (int i = 0; i < 3; i++) {
if (i >= numOfButtonsInRow2) {
mWeekGroup2.getChildAt(i).setVisibility(View.GONE);
continue;
}
mWeekByDayButtons[idx] = (ToggleButton) mWeekGroup2.getChildAt(i);
mWeekByDayButtons[idx].setTextOff(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
mWeekByDayButtons[idx].setTextOn(dayOfWeekString[TIME_DAY_TO_CALENDAR_DAY[idx]]);
mWeekByDayButtons[idx].setOnCheckedChangeListener(this);
if (++idx >= 7) {
idx = 0;
}
}
mMonthGroup = (LinearLayout) mView.findViewById(R.id.monthGroup);
mMonthRepeatByRadioGroup = (RadioGroup) mView.findViewById(R.id.monthGroup);
mMonthRepeatByRadioGroup.setOnCheckedChangeListener(this);
mRepeatMonthlyByNthDayOfWeek = (RadioButton) mView
.findViewById(R.id.repeatMonthlyByNthDayOfTheWeek);
mRepeatMonthlyByNthDayOfMonth = (RadioButton) mView
.findViewById(R.id.repeatMonthlyByNthDayOfMonth);
mDone = (Button) mView.findViewById(R.id.done);
mDone.setOnClickListener(this);
togglePickerOptions();
updateDialog();
if (endCountHasFocus) {
mEndCount.requestFocus();
}
return mView;
}
private void togglePickerOptions() {
if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
mFreqSpinner.setEnabled(false);
mEndSpinner.setEnabled(false);
mIntervalPreText.setEnabled(false);
mInterval.setEnabled(false);
mIntervalPostText.setEnabled(false);
mMonthRepeatByRadioGroup.setEnabled(false);
mEndCount.setEnabled(false);
mPostEndCount.setEnabled(false);
mEndDateTextView.setEnabled(false);
mRepeatMonthlyByNthDayOfWeek.setEnabled(false);
mRepeatMonthlyByNthDayOfMonth.setEnabled(false);
for (Button button : mWeekByDayButtons) {
button.setEnabled(false);
}
} else {
mView.findViewById(R.id.options).setEnabled(true);
mFreqSpinner.setEnabled(true);
mEndSpinner.setEnabled(true);
mIntervalPreText.setEnabled(true);
mInterval.setEnabled(true);
mIntervalPostText.setEnabled(true);
mMonthRepeatByRadioGroup.setEnabled(true);
mEndCount.setEnabled(true);
mPostEndCount.setEnabled(true);
mEndDateTextView.setEnabled(true);
mRepeatMonthlyByNthDayOfWeek.setEnabled(true);
mRepeatMonthlyByNthDayOfMonth.setEnabled(true);
for (Button button : mWeekByDayButtons) {
button.setEnabled(true);
}
}
updateDoneButtonState();
}
private void updateDoneButtonState() {
if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
mDone.setEnabled(true);
return;
}
if (mInterval.getText().toString().length() == 0) {
mDone.setEnabled(false);
return;
}
if (mEndCount.getVisibility() == View.VISIBLE &&
mEndCount.getText().toString().length() == 0) {
mDone.setEnabled(false);
return;
}
if (mModel.freq == RecurrenceModel.FREQ_WEEKLY) {
for (CompoundButton b : mWeekByDayButtons) {
if (b.isChecked()) {
mDone.setEnabled(true);
return;
}
}
mDone.setEnabled(false);
return;
}
mDone.setEnabled(true);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BUNDLE_MODEL, mModel);
if (mEndCount.hasFocus()) {
outState.putBoolean(BUNDLE_END_COUNT_HAS_FOCUS, true);
}
}
public void updateDialog() {
// Interval
// Checking before setting because this causes infinite recursion
// in afterTextWatcher
final String intervalStr = Integer.toString(mModel.interval);
if (!intervalStr.equals(mInterval.getText().toString())) {
mInterval.setText(intervalStr);
}
mFreqSpinner.setSelection(mModel.freq);
mWeekGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
mWeekGroup2.setVisibility(mModel.freq == RecurrenceModel.FREQ_WEEKLY ? View.VISIBLE : View.GONE);
mMonthGroup.setVisibility(mModel.freq == RecurrenceModel.FREQ_MONTHLY ? View.VISIBLE : View.GONE);
switch (mModel.freq) {
case RecurrenceModel.FREQ_DAILY:
mIntervalResId = R.plurals.recurrence_interval_daily;
break;
case RecurrenceModel.FREQ_WEEKLY:
mIntervalResId = R.plurals.recurrence_interval_weekly;
for (int i = 0; i < 7; i++) {
mWeekByDayButtons[i].setChecked(mModel.weeklyByDayOfWeek[i]);
}
break;
case RecurrenceModel.FREQ_MONTHLY:
mIntervalResId = R.plurals.recurrence_interval_monthly;
if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_DATE) {
mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfMonth);
} else if (mModel.monthlyRepeat == RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK) {
mMonthRepeatByRadioGroup.check(R.id.repeatMonthlyByNthDayOfTheWeek);
}
if (mMonthRepeatByDayOfWeekStr == null) {
if (mModel.monthlyByNthDayOfWeek == 0) {
mModel.monthlyByNthDayOfWeek = (mTime.monthDay + 6) / 7;
// Since not all months have 5 weeks, we convert 5th NthDayOfWeek to
// -1 for last monthly day of the week
if (mModel.monthlyByNthDayOfWeek >= FIFTH_WEEK_IN_A_MONTH) {
mModel.monthlyByNthDayOfWeek = LAST_NTH_DAY_OF_WEEK;
}
mModel.monthlyByDayOfWeek = mTime.weekDay;
}
String[] monthlyByNthDayOfWeekStrs =
mMonthRepeatByDayOfWeekStrs[mModel.monthlyByDayOfWeek];
// TODO(psliwowski): Find a better way handle -1 indexes
int msgIndex = mModel.monthlyByNthDayOfWeek < 0 ? FIFTH_WEEK_IN_A_MONTH :
mModel.monthlyByNthDayOfWeek;
mMonthRepeatByDayOfWeekStr =
monthlyByNthDayOfWeekStrs[msgIndex - 1];
mRepeatMonthlyByNthDayOfWeek.setText(mMonthRepeatByDayOfWeekStr);
}
break;
case RecurrenceModel.FREQ_YEARLY:
mIntervalResId = R.plurals.recurrence_interval_yearly;
break;
}
updateIntervalText();
updateDoneButtonState();
mEndSpinner.setSelection(mModel.end);
if (mModel.end == RecurrenceModel.END_BY_DATE) {
final String dateStr = DateUtils.formatDateTime(getActivity(),
mModel.endDate.toMillis(false), DateUtils.FORMAT_NUMERIC_DATE);
mEndDateTextView.setText(dateStr);
} else {
if (mModel.end == RecurrenceModel.END_BY_COUNT) {
// Checking before setting because this causes infinite
// recursion
// in afterTextWatcher
final String countStr = Integer.toString(mModel.endCount);
if (!countStr.equals(mEndCount.getText().toString())) {
mEndCount.setText(countStr);
}
}
}
}
/**
* @param endDateString
*/
private void setEndSpinnerEndDateStr(final String endDateString) {
mEndSpinnerArray.set(1, endDateString);
mEndSpinnerAdapter.notifyDataSetChanged();
}
private void doToast() {
Log.e(TAG, "Model = " + mModel.toString());
String rrule;
if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
rrule = "Not repeating";
} else {
copyModelToEventRecurrence(mModel, mRecurrence);
rrule = mRecurrence.toString();
}
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(getActivity(), rrule,
Toast.LENGTH_LONG);
mToast.show();
}
// TODO Test and update for Right-to-Left
private void updateIntervalText() {
if (mIntervalResId == -1) {
return;
}
final String INTERVAL_COUNT_MARKER = "%d";
String intervalString = mResources.getQuantityString(mIntervalResId, mModel.interval);
int markerStart = intervalString.indexOf(INTERVAL_COUNT_MARKER);
if (markerStart != -1) {
int postTextStart = markerStart + INTERVAL_COUNT_MARKER.length();
mIntervalPostText.setText(intervalString.substring(postTextStart,
intervalString.length()).trim());
mIntervalPreText.setText(intervalString.substring(0, markerStart).trim());
}
}
/**
* Update the "Repeat for N events" end option with the proper string values
* based on the value that has been entered for N.
*/
private void updateEndCountText() {
final String END_COUNT_MARKER = "%d";
String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
mModel.endCount);
int markerStart = endString.indexOf(END_COUNT_MARKER);
if (markerStart != -1) {
if (markerStart == 0) {
Log.e(TAG, "No text to put in to recurrence's end spinner.");
} else {
int postTextStart = markerStart + END_COUNT_MARKER.length();
mPostEndCount.setText(endString.substring(postTextStart,
endString.length()).trim());
}
}
}
// Implements OnItemSelectedListener interface
// Freq spinner
// End spinner
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (parent == mFreqSpinner) {
mModel.freq = position;
} else if (parent == mEndSpinner) {
switch (position) {
case RecurrenceModel.END_NEVER:
mModel.end = RecurrenceModel.END_NEVER;
break;
case RecurrenceModel.END_BY_DATE:
mModel.end = RecurrenceModel.END_BY_DATE;
break;
case RecurrenceModel.END_BY_COUNT:
mModel.end = RecurrenceModel.END_BY_COUNT;
if (mModel.endCount <= 1) {
mModel.endCount = 1;
} else if (mModel.endCount > COUNT_MAX) {
mModel.endCount = COUNT_MAX;
}
updateEndCountText();
break;
}
mEndCount.setVisibility(mModel.end == RecurrenceModel.END_BY_COUNT ? View.VISIBLE
: View.GONE);
mEndDateTextView.setVisibility(mModel.end == RecurrenceModel.END_BY_DATE ? View.VISIBLE
: View.GONE);
mPostEndCount.setVisibility(
mModel.end == RecurrenceModel.END_BY_COUNT && !mHidePostEndCount ?
View.VISIBLE : View.GONE);
}
updateDialog();
}
// Implements OnItemSelectedListener interface
@Override
public void onNothingSelected(AdapterView<?> arg0) {
}
@Override
public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
if (mModel.endDate == null) {
mModel.endDate = new Time(mTime.timezone);
mModel.endDate.hour = mModel.endDate.minute = mModel.endDate.second = 0;
}
mModel.endDate.year = year;
mModel.endDate.month = monthOfYear;
mModel.endDate.monthDay = dayOfMonth;
mModel.endDate.normalize(false);
updateDialog();
}
// Implements OnCheckedChangeListener interface
// Week repeat by day of week
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
int itemIdx = -1;
for (int i = 0; i < 7; i++) {
if (itemIdx == -1 && buttonView == mWeekByDayButtons[i]) {
itemIdx = i;
mModel.weeklyByDayOfWeek[i] = isChecked;
}
}
updateDialog();
}
// Implements android.widget.RadioGroup.OnCheckedChangeListener interface
// Month repeat by radio buttons
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.repeatMonthlyByNthDayOfMonth) {
mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_DATE;
} else if (checkedId == R.id.repeatMonthlyByNthDayOfTheWeek) {
mModel.monthlyRepeat = RecurrenceModel.MONTHLY_BY_NTH_DAY_OF_WEEK;
}
updateDialog();
}
// Implements OnClickListener interface
// EndDate button
// Done button
@Override
public void onClick(View v) {
if (mEndDateTextView == v) {
if (mDatePickerDialog != null) {
mDatePickerDialog.dismiss();
}
mDatePickerDialog = DatePickerDialog.newInstance(this, mModel.endDate.year,
mModel.endDate.month, mModel.endDate.monthDay);
mDatePickerDialog.setFirstDayOfWeek(Utils.getFirstDayOfWeekAsCalendar(getActivity()));
mDatePickerDialog.setYearRange(Utils.YEAR_MIN, Utils.YEAR_MAX);
mDatePickerDialog.show(getFragmentManager(), FRAG_TAG_DATE_PICKER);
} else if (mDone == v) {
String rrule;
if (mModel.recurrenceState == RecurrenceModel.STATE_NO_RECURRENCE) {
rrule = null;
} else {
copyModelToEventRecurrence(mModel, mRecurrence);
rrule = mRecurrence.toString();
}
if (mRecurrenceSetListener != null) {
mRecurrenceSetListener.onRecurrenceSet(rrule);
}
dismiss();
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mDatePickerDialog = (DatePickerDialog) getFragmentManager()
.findFragmentByTag(FRAG_TAG_DATE_PICKER);
if (mDatePickerDialog != null) {
mDatePickerDialog.setOnDateSetListener(this);
}
}
public interface OnRecurrenceSetListener {
void onRecurrenceSet(String rrule);
}
public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) {
mRecurrenceSetListener = l;
}
private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> {
final String END_DATE_MARKER = "%s";
final String END_COUNT_MARKER = "%d";
private LayoutInflater mInflater;
private int mItemResourceId;
private int mTextResourceId;
private ArrayList<CharSequence> mStrings;
private String mEndDateString;
private boolean mUseFormStrings;
/**
* @param context
* @param textViewResourceId
* @param objects
*/
public EndSpinnerAdapter(Context context, ArrayList<CharSequence> strings,
int itemResourceId, int textResourceId) {
super(context, itemResourceId, strings);
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mItemResourceId = itemResourceId;
mTextResourceId = textResourceId;
mStrings = strings;
mEndDateString = getResources().getString(R.string.recurrence_end_date);
// If either date or count strings don't translate well, such that we aren't assured
// to have some text available to be placed in the spinner, then we'll have to use
// the more form-like versions of both strings instead.
int markerStart = mEndDateString.indexOf(END_DATE_MARKER);
if (markerStart <= 0) {
// The date string does not have any text before the "%s" so we'll have to use the
// more form-like strings instead.
mUseFormStrings = true;
} else {
String countEndStr = getResources().getQuantityString(
R.plurals.recurrence_end_count, 1);
markerStart = countEndStr.indexOf(END_COUNT_MARKER);
if (markerStart <= 0) {
// The count string does not have any text before the "%d" so we'll have to use
// the more form-like strings instead.
mUseFormStrings = true;
}
}
if (mUseFormStrings) {
// We'll have to set the layout for the spinner to be weight=0 so it doesn't
// take up too much space.
mEndSpinner.setLayoutParams(
new TableLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f));
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
// Check if we can recycle the view
if (convertView == null) {
v = mInflater.inflate(mTextResourceId, parent, false);
} else {
v = convertView;
}
TextView item = (TextView) v.findViewById(R.id.spinner_item);
int markerStart;
switch (position) {
case RecurrenceModel.END_NEVER:
item.setText(mStrings.get(RecurrenceModel.END_NEVER));
break;
case RecurrenceModel.END_BY_DATE:
markerStart = mEndDateString.indexOf(END_DATE_MARKER);
if (markerStart != -1) {
if (mUseFormStrings || markerStart == 0) {
// If we get here, the translation of "Until" doesn't work correctly,
// so we'll just set the whole "Until a date" string.
item.setText(mEndDateLabel);
} else {
item.setText(mEndDateString.substring(0, markerStart).trim());
}
}
break;
case RecurrenceModel.END_BY_COUNT:
String endString = mResources.getQuantityString(R.plurals.recurrence_end_count,
mModel.endCount);
markerStart = endString.indexOf(END_COUNT_MARKER);
if (markerStart != -1) {
if (mUseFormStrings || markerStart == 0) {
// If we get here, the translation of "For" doesn't work correctly,
// so we'll just set the whole "For a number of events" string.
item.setText(mEndCountLabel);
// Also, we'll hide the " events" that would have been at the end.
mPostEndCount.setVisibility(View.GONE);
// Use this flag so the onItemSelected knows whether to show it later.
mHidePostEndCount = true;
} else {
int postTextStart = markerStart + END_COUNT_MARKER.length();
mPostEndCount.setText(endString.substring(postTextStart,
endString.length()).trim());
// In case it's a recycled view that wasn't visible.
if (mModel.end == RecurrenceModel.END_BY_COUNT) {
mPostEndCount.setVisibility(View.VISIBLE);
}
if (endString.charAt(markerStart - 1) == ' ') {
markerStart--;
}
item.setText(endString.substring(0, markerStart).trim());
}
}
break;
default:
v = null;
break;
}
return v;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
View v;
// Check if we can recycle the view
if (convertView == null) {
v = mInflater.inflate(mItemResourceId, parent, false);
} else {
v = convertView;
}
TextView item = (TextView) v.findViewById(R.id.spinner_item);
item.setText(mStrings.get(position));
return v;
}
}
}
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/Utils.java
================================================
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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 be.billington.calendar.recurrencepicker;
import android.accounts.Account;
import android.app.Activity;
import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.CalendarContract.Calendars;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.format.Time;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.widget.SearchView;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME;
public class Utils {
private static final boolean DEBUG = false;
private static final String TAG = "CalUtils";
// Set to 0 until we have UI to perform undo
public static final long UNDO_DELAY = 0;
// For recurring events which instances of the series are being modified
public static final int MODIFY_UNINITIALIZED = 0;
public static final int MODIFY_SELECTED = 1;
public static final int MODIFY_ALL_FOLLOWING = 2;
public static final int MODIFY_ALL = 3;
// When the edit event view finishes it passes back the appropriate exit
// code.
public static final int DONE_REVERT = 1 << 0;
public static final int DONE_SAVE = 1 << 1;
public static final int DONE_DELETE = 1 << 2;
// And should re run with DONE_EXIT if it should also leave the view, just
// exiting is identical to reverting
public static final int DONE_EXIT = 1 << 0;
public static final String OPEN_EMAIL_MARKER = " <";
public static final String CLOSE_EMAIL_MARKER = ">";
public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW";
public static final String INTENT_KEY_VIEW_TYPE = "VIEW";
public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY";
public static final String INTENT_KEY_HOME = "KEY_HOME";
public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
public static final int DECLINED_EVENT_ALPHA = 0x66;
public static final int DECLINED_EVENT_TEXT_ALPHA = 0xC0;
private static final float SATURATION_ADJUST = 1.3f;
private static final float INTENSITY_ADJUST = 0.8f;
// Defines used by the DNA generation code
static final int DAY_IN_MINUTES = 60 * 24;
static final int WEEK_IN_MINUTES = DAY_IN_MINUTES * 7;
// The work day is being counted as 6am to 8pm
static int WORK_DAY_MINUTES = 14 * 60;
static int WORK_DAY_START_MINUTES = 6 * 60;
static int WORK_DAY_END_MINUTES = 20 * 60;
static int WORK_DAY_END_LENGTH = (24 * 60) - WORK_DAY_END_MINUTES;
static int CONFLICT_COLOR = 0xFF000000;
static boolean mMinutesLoaded = false;
public static final int YEAR_MIN = 1970;
public static final int YEAR_MAX = 2036;
// The name of the shared preferences file. This name must be maintained for
// historical
// reasons, as it's what PreferenceManager assigned the first time the file
// was created.
static final String SHARED_PREFS_NAME = "com.android.calendar_preferences";
public static final String KEY_QUICK_RESPONSES = "preferences_quick_responses";
public static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen";
public static final String APPWIDGET_DATA_TYPE = "vnd.android.data/update";
static final String MACHINE_GENERATED_ADDRESS = "calendar.google.com";
private static boolean mAllowWeekForDetailView = false;
private static long mTardis = 0;
private static String sVersion = null;
private static final Pattern mWildcardPattern = Pattern.compile("^.*$");
/**
* A coordinate must be of the following form for Google Maps to correctly use it:
* Latitude, Longitude
* <p/>
* This may be in decimal form:
* Latitude: {-90 to 90}
* Longitude: {-180 to 180}
* <p/>
* Or, in degrees, minutes, and seconds:
* Latitude: {-90 to 90}° {0 to 59}' {0 to 59}"
* Latitude: {-180 to 180}° {0 to 59}' {0 to 59}"
* + or - degrees may also be represented with N or n, S or s for latitude, and with
* E or e, W or w for longitude, where the direction may either precede or follow the value.
* <p/>
* Some examples of coordinates that will be accepted by the regex:
* 37.422081°, -122.084576°
* 37.422081,-122.084576
* +37°25'19.49", -122°5'4.47"
* 37°25'19.49"N, 122°5'4.47"W
* N 37° 25' 19.49", W 122° 5' 4.47"
*/
private static final String COORD_DEGREES_LATITUDE =
"([-+NnSs]" + "(\\s)*)?"
+ "[1-9]?[0-9](\u00B0)" + "(\\s)*"
+ "([1-5]?[0-9]\')?" + "(\\s)*"
+ "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
+ "((\\s)*" + "[NnSs])?";
private static final String COORD_DEGREES_LONGITUDE =
"([-+EeWw]" + "(\\s)*)?"
+ "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*"
+ "([1-5]?[0-9]\')?" + "(\\s)*"
+ "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?"
+ "((\\s)*" + "[EeWw])?";
private static final String COORD_DEGREES_PATTERN =
COORD_DEGREES_LATITUDE
+ "(\\s)*" + "," + "(\\s)*"
+ COORD_DEGREES_LONGITUDE;
private static final String COORD_DECIMAL_LATITUDE =
"[+-]?"
+ "[1-9]?[0-9]" + "(\\.[0-9]+)"
+ "(\u00B0)?";
private static final String COORD_DECIMAL_LONGITUDE =
"[+-]?"
+ "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)"
+ "(\u00B0)?";
private static final String COORD_DECIMAL_PATTERN =
COORD_DECIMAL_LATITUDE
+ "(\\s)*" + "," + "(\\s)*"
+ COORD_DECIMAL_LONGITUDE;
private static final Pattern COORD_PATTERN =
Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN);
private static final String NANP_ALLOWED_SYMBOLS = "()+-*#.";
private static final int NANP_MIN_DIGITS = 7;
private static final int NANP_MAX_DIGITS = 11;
/**
* Returns whether the SDK is the Jellybean release or later.
*/
public static boolean isJellybeanOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
}
/**
* Returns whether the SDK is the KeyLimePie release or later.
*/
public static boolean isKeyLimePieOrLater() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
/**
* Gets the intent action for telling the widget to update.
*/
public static String getWidgetUpdateAction(Context context) {
return context.getPackageName() + ".APPWIDGET_UPDATE";
}
/**
* Gets the intent action for telling the widget to update.
*/
public static String getWidgetScheduledUpdateAction(Context context) {
return context.getPackageName() + ".APPWIDGET_SCHEDULED_UPDATE";
}
/**
* Gets the intent action for telling the widget to update.
*/
public static String getSearchAuthority(Context context) {
return context.getPackageName() + ".CalendarRecentSuggestionsProvider";
}
protected static void tardis() {
mTardis = System.currentTimeMillis();
}
protected static long getTardis() {
return mTardis;
}
public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
if (cursor == null) {
return null;
}
String[] columnNames = cursor.getColumnNames();
if (columnNames == null) {
columnNames = new String[]{};
}
MatrixCursor newCursor = new MatrixCursor(columnNames);
int numColumns = cursor.getColumnCount();
String data[] = new String[numColumns];
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
for (int i = 0; i < numColumns; i++) {
data[i] = cursor.getString(i);
}
newCursor.addRow(data);
}
return newCursor;
}
/**
* Compares two cursors to see if they contain the same data.
*
* @return Returns true of the cursors contain the same data and are not
* null, false otherwise
*/
public static boolean compareCursors(Cursor c1, Cursor c2) {
if (c1 == null || c2 == null) {
return false;
}
int numColumns = c1.getColumnCount();
if (numColumns != c2.getColumnCount()) {
return false;
}
if (c1.getCount() != c2.getCount()) {
return false;
}
c1.moveToPosition(-1);
c2.moveToPosition(-1);
while (c1.moveToNext() && c2.moveToNext()) {
for (int i = 0; i < numColumns; i++) {
if (!TextUtils.equals(c1.getString(i), c2.getString(i))) {
return false;
}
}
}
return true;
}
/**
* If the given intent specifies a time (in milliseconds since the epoch),
* then that time is returned. Otherwise, the current time is returned.
*/
public static final long timeFromIntentInMillis(Intent intent) {
// If the time was specified, then use that. Otherwise, use the current
// time.
Uri data = intent.getData();
long millis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1);
if (millis == -1 && data != null && data.isHierarchical()) {
List<String> path = data.getPathSegments();
if (path.size() == 2 && path.get(0).equals("time")) {
try {
millis = Long.valueOf(data.getLastPathSegment());
} catch (NumberFormatException e) {
Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time "
+ "found. Using current time.");
}
}
}
if (millis <= 0) {
millis = System.currentTimeMillis();
}
return millis;
}
/**
* Returns a list joined together by the provided delimiter, for example,
* ["a", "b", "c"] could be joined into "a,b,c"
*
* @param things the things to join together
* @param delim the delimiter to use
* @return a string contained the things joined together
*/
public static String join(List<?> things, String delim) {
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Object thing : things) {
if (first) {
first = false;
} else {
builder.append(delim);
}
builder.append(thing.toString());
}
return builder.toString();
}
/**
* Returns the week since {@link android.text.format.Time#EPOCH_JULIAN_DAY} (Jan 1, 1970)
* adjusted for first day of week.
* <p/>
* This takes a julian day and the week start day and calculates which
* week since {@link android.text.format.Time#EPOCH_JULIAN_DAY} that day occurs in, starting
* at 0. *Do not* use this to compute the ISO week number for the year.
*
* @param julianDay The julian day to calculate the week number for
* @param firstDayOfWeek Which week day is the first day of the week,
* see {@link android.text.format.Time#SUNDAY}
* @return Weeks since the epoch
*/
public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) {
int diff = Time.THURSDAY - firstDayOfWeek;
if (diff < 0) {
diff += 7;
}
int refDay = Time.EPOCH_JULIAN_DAY - diff;
return (julianDay - refDay) / 7;
}
/**
* Takes a number of weeks since the epoch and calculates the Julian day of
* the Monday for that week.
* <p/>
* This assumes that the week containing the {@link android.text.format.Time#EPOCH_JULIAN_DAY}
* is considered week 0. It returns the Julian day for the Monday
* {@code week} weeks after the Monday of the week containing the epoch.
*
* @param week Number of weeks since the epoch
* @return The julian day for the Monday of the given week since the epoch
*/
public static int getJulianMondayFromWeeksSinceEpoch(int week) {
return MONDAY_BEFORE_JULIAN_EPOCH + week * 7;
}
/**
* Get first day of week as android.text.format.Time constant.
*
* @return the first day of week in android.text.format.Time
*/
public static int getFirstDayOfWeek(Context context) {
int startDay = Calendar.getInstance().getFirstDayOfWeek();
if (startDay == Calendar.SATURDAY) {
return Time.SATURDAY;
} else if (startDay == Calendar.MONDAY) {
return Time.MONDAY;
} else {
return Time.SUNDAY;
}
}
/**
* Get first day of week as java.util.Calendar constant.
*
* @return the first day of week as a java.util.Calendar constant
*/
public static int getFirstDayOfWeekAsCalendar(Context context) {
return convertDayOfWeekFromTimeToCalendar(getFirstDayOfWeek(context));
}
/**
* Converts the day of the week from android.text.format.Time to java.util.Calendar
*/
public static int convertDayOfWeekFromTimeToCalendar(int timeDayOfWeek) {
switch (timeDayOfWeek) {
case Time.MONDAY:
return Calendar.MONDAY;
case Time.TUESDAY:
return Calendar.TUESDAY;
case Time.WEDNESDAY:
return Calendar.WEDNESDAY;
case Time.THURSDAY:
return Calendar.THURSDAY;
case Time.FRIDAY:
return Calendar.FRIDAY;
case Time.SATURDAY:
return Calendar.SATURDAY;
case Time.SUNDAY:
return Calendar.SUNDAY;
default:
throw new IllegalArgumentException("Argument must be between Time.SUNDAY and " +
"Time.SATURDAY");
}
}
/**
* Determine whether the column position is Saturday or not.
*
* @param column the column position
* @param firstDayOfWeek the first day of week in android.text.format.Time
* @return true if the column is Saturday position
*/
public static boolean isSaturday(int column, int firstDayOfWeek) {
return (firstDayOfWeek == Time.SUNDAY && column == 6)
|| (firstDayOfWeek == Time.MONDAY && column == 5)
|| (firstDayOfWeek == Time.SATURDAY && column == 0);
}
/**
* Determine whether the column position is Sunday or not.
*
* @param column the column position
* @param firstDayOfWeek the first day of week in android.text.format.Time
* @return true if the column is Sunday position
*/
public static boolean isSunday(int column, int firstDayOfWeek) {
return (firstDayOfWeek == Time.SUNDAY && column == 0)
|| (firstDayOfWeek == Time.MONDAY && column == 6)
|| (firstDayOfWeek == Time.SATURDAY && column == 1);
}
/**
* Convert given UTC time into current local time. This assumes it is for an
* allday event and will adjust the time to be on a midnight boundary.
*
* @param recycle Time object to recycle, otherwise null.
* @param utcTime Time to convert, in UTC.
* @param tz The time zone to convert this time to.
*/
public static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz) {
if (recycle == null) {
recycle = new Time();
}
recycle.timezone = Time.TIMEZONE_UTC;
recycle.set(utcTime);
recycle.timezone = tz;
return recycle.normalize(true);
}
public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) {
if (recycle == null) {
recycle = new Time();
}
recycle.timezone = tz;
recycle.set(localTime);
recycle.timezone = Time.TIMEZONE_UTC;
return recycle.normalize(true);
}
/**
* Finds and returns the next midnight after "theTime" in milliseconds UTC
*
* @param recycle - Time object to recycle, otherwise null.
* @param theTime - Time used for calculations (in UTC)
* @param tz The time zone to convert this time to.
*/
public static long getNextMidnight(Time recycle, long theTime, String tz) {
if (recycle == null) {
recycle = new Time();
}
recycle.timezone = tz;
recycle.set(theTime);
recycle.monthDay++;
recycle.hour = 0;
recycle.minute = 0;
recycle.second = 0;
return recycle.normalize(true);
}
/**
* Scan through a cursor of calendars and check if names are duplicated.
* This travels a cursor containing calendar display names and fills in the
* provided map with whether or not each name is repeated.
*
* @param isDuplicateName The map to put the duplicate check results in.
* @param cursor The query of calendars to check
* @param nameIndex The column of the query that contains the display name
*/
public static void checkForDuplicateNames(
Map<String, Boolean> isDuplicateName, Cursor cursor, int nameIndex) {
isDuplicateName.clear();
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
String displayName = cursor.getString(nameIndex);
// Set it to true if we've seen this name before, false otherwise
if (displayName != null) {
isDuplicateName.put(displayName, isDuplicateName.containsKey(displayName));
}
}
}
/**
* Null-safe object comparison
*
* @param s1
* @param s2
* @return
*/
public static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
public static void setAllowWeekForDetailView(boolean allowWeekView) {
mAllowWeekForDetailView = allowWeekView;
}
public static boolean getAllowWeekForDetailView() {
return mAllowWeekForDetailView;
}
public static boolean getConfigBool(Context c, int key) {
return c.getResources().getBoolean(key);
}
/**
* For devices with Jellybean or later, darkens the given color to ensure that white text is
* clearly visible on top of it. For devices prior to Jellybean, does nothing, as the
* sync adapter handles the color change.
*
* @param color
*/
public static int getDisplayColorFromColor(int color) {
if (!isJellybeanOrLater()) {
return color;
}
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f);
hsv[2] = hsv[2] * INTENSITY_ADJUST;
return Color.HSVToColor(hsv);
}
// This takes a color and computes what it would look like blended with
// white. The result is the color that should be used for declined events.
public static int getDeclinedColorFromColor(int color) {
int bg = 0xffffffff;
int a = DECLINED_EVENT_ALPHA;
int r = (((color & 0x00ff0000) * a) + ((bg & 0x00ff0000) * (0xff - a))) & 0xff000000;
int g = (((color & 0x0000ff00) * a) + ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000;
int b = (((color & 0x000000ff) * a) + ((bg & 0x000000ff) * (0xff - a))) & 0x0000ff00;
return (0xff000000) | ((r | g | b) >> 8);
}
// A single strand represents one color of events. Events are divided up by
// color to make them convenient to draw. The black strand is special in
// that it holds conflicting events as well as color settings for allday on
// each day.
public static class DNAStrand {
public float[] points;
public int[] allDays; // color for the allday, 0 means no event
int position;
public int color;
int count;
}
// A segment is a single continuous length of time occupied by a single
// color. Segments should never span multiple days.
private static class DNASegment {
int startMinute; // in minutes since the start of the week
int endMinute;
int color; // Calendar color or black for conflicts
int day; // quick reference to the day this segment is on
}
// This processes all the segments, sorts them by color, and generates a
// list of points to draw
private static void weaveDNAStrands(LinkedList<DNASegment> segments, int firstJulianDay,
HashMap<Integer, DNAStrand> strands, int top, int bottom, int[] dayXs) {
// First, get rid of any colors that ended up with no segments
Iterator<DNAStrand> strandIterator = strands.values().iterator();
while (strandIterator.hasNext()) {
DNAStrand strand = strandIterator.next();
if (strand.count < 1 && strand.allDays == null) {
strandIterator.remove();
continue;
}
strand.points = new float[strand.count * 4];
strand.position = 0;
}
// Go through each segment and compute its points
for (DNASegment segment : segments) {
// Add the points to the strand of that color
DNAStrand strand = strands.get(segment.color);
int dayIndex = segment.day - firstJulianDay;
int dayStartMinute = segment.startMinute % DAY_IN_MINUTES;
int dayEndMinute = segment.endMinute % DAY_IN_MINUTES;
int height = bottom - top;
int workDayHeight = height * 3 / 4;
int remainderHeight = (height - workDayHeight) / 2;
int x = dayXs[dayIndex];
int y0 = 0;
int y1 = 0;
y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight);
y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight);
if (DEBUG) {
Log.d(TAG, "Adding " + Integer.toHexString(segment.color) + " at x,y0,y1: " + x
+ " " + y0 + " " + y1 + " for " + dayStartMinute + " " + dayEndMinute);
}
strand.points[strand.position++] = x;
strand.points[strand.position++] = y0;
strand.points[strand.position++] = x;
strand.points[strand.position++] = y1;
}
}
/**
* Compute a pixel offset from the top for a given minute from the work day
* height and the height of the top area.
*/
private static int getPixelOffsetFromMinutes(int minute, int workDayHeight,
int remainderHeight) {
int y;
if (minute < WORK_DAY_START_MINUTES) {
y = minute * remainderHeight / WORK_DAY_START_MINUTES;
} else if (minute < WORK_DAY_END_MINUTES) {
y = remainderHeight + (minute - WORK_DAY_START_MINUTES) * workDayHeight
/ WORK_DAY_MINUTES;
} else {
y = remainderHeight + workDayHeight + (minute - WORK_DAY_END_MINUTES) * remainderHeight
/ WORK_DAY_END_LENGTH;
}
return y;
}
/**
* Try to get a strand of the given color. Create it if it doesn't exist.
*/
private static DNAStrand getOrCreateStrand(HashMap<Integer, DNAStrand> strands, int color) {
DNAStrand strand = strands.get(color);
if (strand == null) {
strand = new DNAStrand();
strand.color = color;
strand.count = 0;
strands.put(strand.color, strand);
}
return strand;
}
/**
* This sets up a search view to use Calendar's search suggestions provider
* and to allow refining the search.
*
* @param view The {@link android.widget.SearchView} to set up
* @param act The activity using the view
*/
public static void setUpSearchView(SearchView view, Activity act) {
SearchManager searchManager = (SearchManager) act.getSystemService(Context.SEARCH_SERVICE);
view.setSearchableInfo(searchManager.getSearchableInfo(act.getComponentName()));
view.setQueryRefinementEnabled(true);
}
// Calculate the time until midnight + 1 second and set the handler to
// do run the runnable
public static void setMidnightUpdater(Handler h, Runnable r, String timezone) {
if (h == null || r == null || timezone == null) {
return;
}
long now = System.currentTimeMillis();
Time time = new Time(timezone);
time.set(now);
long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 -
time.second + 1) * 1000;
h.removeCallbacks(r);
h.postDelayed(r, runInMillis);
}
// Stop the midnight update thread
public static void resetMidnightUpdater(Handler h, Runnable r) {
if (h == null || r == null) {
return;
}
h.removeCallbacks(r);
}
/**
* Returns the timezone to display in the event info, if the local timezone is different
* from the event timezone. Otherwise returns null.
*/
public static String getDisplayedTimezone(long startMillis, String localTimezone,
String eventTimezone) {
String tzDisplay = null;
if (!TextUtils.equals(localTimezone, eventTimezone)) {
// Figure out if this is in DST
TimeZone tz = TimeZone.getTimeZone(localTimezone);
if (tz == null || tz.getID().equals("GMT")) {
tzDisplay = localTimezone;
} else {
Time startTime = new Time(localTimezone);
startTime.set(startMillis);
tzDisplay = tz.getDisplayName(startTime.isDst != 0, TimeZone.SHORT);
}
}
return tzDisplay;
}
/**
* Returns whether the specified time interval is in a single day.
*/
private static boolean singleDayEvent(long startMillis, long endMillis, long localGmtOffset) {
if (startMillis == endMillis) {
return true;
}
// An event ending at midnight should still be a single-day event, so check
// time end-1.
int startDay = Time.getJulianDay(startMillis, localGmtOffset);
int endDay = Time.getJulianDay(endMillis - 1, localGmtOffset);
return startDay == endDay;
}
// Using int constants as a return value instead of an enum to minimize resources.
private static final int TODAY = 1;
private static final int TOMORROW = 2;
private static final int NONE = 0;
/**
* Returns TODAY or TOMORROW if applicable. Otherwise returns NONE.
*/
private static int isTodayOrTomorrow(Resources r, long dayMillis,
long currentMillis, long localGmtOffset) {
int startDay = Time.getJulianDay(dayMillis, localGmtOffset);
int currentDay = Time.getJulianDay(currentMillis, localGmtOffset);
int days = startDay - currentDay;
if (days == 1) {
return TOMORROW;
} else if (days == 0) {
return TODAY;
} else {
return NONE;
}
}
/**
* Create an intent for emailing attendees of an event.
*
* @param resources The resources for translating strings.
* @param eventTitle The title of the event to use as the email subject.
* @param body The default text for the email body.
* @param toEmails The list of emails for the 'to' line.
* @param ccEmails The list of emails for the 'cc' line.
* @param ownerAccount The owner account to use as the email sender.
*/
public static Intent createEmailAttendeesIntent(Resources resources, String eventTitle,
String body, List<String> toEmails, List<String> ccEmails, String ownerAccount) {
List<String> toList = toEmails;
List<String> ccList = ccEmails;
if (toEmails.size() <= 0) {
if (ccEmails.size() <= 0) {
// TODO: Return a SEND intent if no one to email to, to at least populate
// a draft email with the subject (and no recipients).
throw new IllegalArgumentException("Both toEmails and ccEmails are empty.");
}
// Email app does not work with no "to" recipient. Move all 'cc' to 'to'
// in this case.
toList = ccEmails;
ccList = null;
}
// Use the event title as the email subject (prepended with 'Re: ').
String subject = null;
if (eventTitle != null) {
subject = resources.getString(R.string.email_subject_prefix) + eventTitle;
}
// Use the SENDTO intent with a 'mailto' URI, because using SEND will cause
// the picker to show apps like text messaging, which does not make sense
// for email addresses. We put all data in the URI instead of using the extra
// Intent fields (ie. EXTRA_CC, etc) because some email apps might not handle
// those (though gmail does).
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme("mailto");
// We will append the first email to the 'mailto' field later (because the
// current state of the Email app requires it). Add the remaining 'to' values
// here. When the email codebase is updated, we can simplify this.
if (toList.size() > 1) {
for (int i = 1; i < toList.size(); i++) {
// The Email app requires repeated parameter settings instead of
// a single comma-separated list.
uriBuilder.appendQueryParameter("to", toList.get(i));
}
}
// Add the subject parameter.
if (subject != null) {
uriBuilder.appendQueryParameter("subject", subject);
}
// Add the subject parameter.
if (body != null) {
uriBuilder.appendQueryParameter("body", body);
}
// Add the cc parameters.
if (ccList != null && ccList.size() > 0) {
for (String email : ccList) {
uriBuilder.appendQueryParameter("cc", email);
}
}
// Insert the first email after 'mailto:' in the URI manually since Uri.Builder
// doesn't seem to have a way to do this.
String uri = uriBuilder.toString();
if (uri.startsWith("mailto:")) {
StringBuilder builder = new StringBuilder(uri);
builder.insert(7, Uri.encode(toList.get(0)));
uri = builder.toString();
}
// Start the email intent. Email from the account of the calendar owner in case there
// are multiple email accounts.
Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri));
emailIntent.putExtra("fromAccountString", ownerAccount);
// Workaround a Email bug that overwrites the body with this intent extra. If not
// set, it clears the body.
if (body != null) {
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
}
return Intent.createChooser(emailIntent, resources.getString(R.string.email_picker_label));
}
/**
* Example fake email addresses used as attendee emails are resources like conference rooms,
* or another calendar, etc. These all end in "calendar.google.com".
*/
public static boolean isValidEmail(String email) {
return email != null && !email.endsWith(MACHINE_GENERATED_ADDRESS);
}
/**
* Returns true if:
* (1) the email is not a resource like a conference room or another calendar.
* Catch most of these by filtering out suffix calendar.google.com.
* (2) the email is not equal to the sync account to prevent mailing himself.
*/
public static boolean isEmailableFrom(String email, String syncAccountName) {
return Utils.isValidEmail(email) && !email.equals(syncAccountName);
}
private static class CalendarBroadcastReceiver extends BroadcastReceiver {
Runnable mCallBack;
public CalendarBroadcastReceiver(Runnable callback) {
super();
mCallBack = callback;
}
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED) ||
intent.getAction().equals(Intent.ACTION_TIME_CHANGED) ||
intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED) ||
intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {
if (mCallBack != null) {
mCallBack.run();
}
}
}
}
public static BroadcastReceiver setTimeChangesReceiver(Context c, Runnable callback) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
CalendarBroadcastReceiver r = new CalendarBroadcastReceiver(callback);
c.registerReceiver(r, filter);
return r;
}
public static void clearTimeChangesReceiver(Context c, BroadcastReceiver r) {
c.unregisterReceiver(r);
}
/**
* Return the app version code.
*/
public static String getVersionCode(Context context) {
if (sVersion == null) {
try {
sVersion = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
// Can't find version; just leave it blank.
Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName);
}
}
return sVersion;
}
/**
* Checks the server for an updated list of Calendars (in the background).
* <p/>
* If a Calendar is added on the web (and it is selected and not
* hidden) then it will be added to the list of calendars on the phone
* (when this finishes). When a new calendar from the
* web is added to the phone, then the events for that calendar are also
* downloaded from the web.
* <p/>
* This sync is done automatically in the background when the
* SelectCalendars activity and fragment are started.
*
* @param account - The account to sync. May be null to sync all accounts.
*/
public static void startCalendarMetafeedSync(Account account) {
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
extras.putBoolean("metafeedonly", true);
ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras);
}
/**
* Replaces stretches of text that look like addresses and phone numbers with clickable
* links. If lastDitchGeo is true, then if no links are found in the textview, the entire
* string will be converted to a single geo link. Any spans that may have previously been
* in the text will be cleared out.
* <p/>
* This is really just an enhanced version of Linkify.addLinks().
*
* @param text - The string to search for links.
* @param lastDitchGeo - If no links are found, turn the entire string into one geo link.
* @return Spannable object containing the list of URL spans found.
*/
public static Spannable extendedLinkify(String text, boolean lastDitchGeo) {
// We use a copy of the string argument so it's available for later if necessary.
Spannable spanText = SpannableString.valueOf(text);
/*
* If the text includes a street address like "1600 Amphitheater Parkway, 94043",
* the current Linkify code will identify "94043" as a phone number and invite
* you to dial it (and not provide a map link for the address). For outside US,
* use Linkify result iff it spans the entire text. Otherwise send the user to maps.
*/
String defaultPhoneRegion = System.getProperty("user.region", "US");
if (!defaultPhoneRegion.equals("US")) {
Linkify.addLinks(spanText, Linkify.ALL);
// If Linkify links the entire text, use that result.
URLSpan[] spans = spanText.getSpans(0, spanText.length(), URLSpan.class);
if (spans.length == 1) {
int linkStart = spanText.getSpanStart(spans[0]);
int linkEnd = spanText.getSpanEnd(spans[0]);
if (linkStart <= indexFirstNonWhitespaceChar(spanText) &&
linkEnd >= indexLastNonWhitespaceChar(spanText) + 1) {
return spanText;
}
}
// Otherwise, to be cautious and to try to prevent false positives, reset the spannable.
spanText = SpannableString.valueOf(text);
// If lastDitchGeo is true, default the entire string to geo.
if (lastDitchGeo && !text.isEmpty()) {
Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q=");
}
return spanText;
}
/*
* For within US, we want to have better recognition of phone numbers without losing
* any of the existing annotations. Ideally this would be addressed by improving Linkify.
* For now we manage it as a second pass over the text.
*
* URIs and e-mail addresses are pretty easy to pick out of text. Phone numbers
* are a bit tricky because they have radically different formats in different
* countries, in terms of both the digits and the way in which they are commonly
* written or presented (e.g. the punctuation and spaces in "(650) 555-1212").
* The expected format of a street address is defined in WebView.findAddress(). It's
* pretty narrowly defined, so it won't often match.
*
* The RFC 3966 specification defines the format of a "tel:" URI.
*
* Start by letting Linkify find anything that isn't a phone number. We have to let it
* run first because every invocation removes all previous URLSpan annotations.
*
* Ideally we'd use the external/libphonenumber routines, but those aren't available
* to unbundled applications.
*/
boolean linkifyFoundLinks = Linkify.addLinks(spanText,
Linkify.ALL & ~(Linkify.PHONE_NUMBERS));
/*
* Get a list of any spans created by Linkify, for the coordinate overlapping span check.
*/
URLSpan[] existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class);
/*
* Check for coordinates.
* This must be done before phone numbers because longitude may look like a phone number.
*/
Matcher coordMatcher = COORD_PATTERN.matcher(spanText);
int coordCount = 0;
while (coordMatcher.find()) {
int start = coordMatcher.start();
int end = coordMatcher.end();
if (spanWillOverlap(spanText, existingSpans, start, end)) {
continue;
}
URLSpan span = new URLSpan("geo:0,0?q=" + coordMatcher.group());
spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
coordCount++;
}
/*
* Update the list of existing spans, for the phone number overlapping span check.
*/
existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class);
/*
* Search for phone numbers.
*
* Some URIs contain strings of digits that look like phone numbers. If both the URI
* scanner and the phone number scanner find them, we want the URI link to win. Since
* the URI scanner runs first, we just need to avoid creating overlapping spans.
*/
int[] phoneSequences = findNanpPhoneNumbers(text);
/*
* Insert spans for the numbers we found. We generate "tel:" URIs.
*/
int phoneCount = 0;
for (int match = 0; match < phoneSequences.length / 2; match++) {
int start = phoneSequences[match * 2];
int end = phoneSequences[match * 2 + 1];
if (spanWillOverlap(spanText, existingSpans, start, end)) {
continue;
}
/*
* The Linkify code takes the matching span and strips out everything that isn't a
* digit or '+' sign. We do the same here. Extension numbers will get appended
* without a separator, but the dialer wasn't doing anything useful with ";ext="
* anyway.
*/
//String dialStr = phoneUtil.format(match.number(),
// PhoneNumberUtil.PhoneNumberFormat.RFC3966);
StringBuilder dialBuilder = new StringBuilder();
for (int i = start; i < end; i++) {
char ch = spanText.charAt(i);
if (ch == '+' || Character.isDigit(ch)) {
dialBuilder.append(ch);
}
}
URLSpan span = new URLSpan("tel:" + dialBuilder.toString());
spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
phoneCount++;
}
/*
* If lastDitchGeo, and no other links have been found, set the entire string as a geo link.
*/
if (lastDitchGeo && !text.isEmpty() &&
!linkifyFoundLinks && phoneCount == 0 && coordCount == 0) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "No linkification matches, using geo default");
}
Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q=");
}
return spanText;
}
private static int indexFirstNonWhitespaceChar(CharSequence str) {
for (int i = 0; i < str.length(); i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return i;
}
}
return -1;
}
private static int indexLastNonWhitespaceChar(CharSequence str) {
for (int i = str.length() - 1; i >= 0; i--) {
if (!Character.isWhitespace(str.charAt(i))) {
return i;
}
}
return -1;
}
/**
* Finds North American Numbering Plan (NANP) phone numbers in the input text.
*
* @param text The text to scan.
* @return A list of [start, end) pairs indicating the positions of phone numbers in the input.
*/
// @VisibleForTesting
static int[] findNanpPhoneNumbers(CharSequence text) {
ArrayList<Integer> list = new ArrayList<Integer>();
int startPos = 0;
int endPos = text.length() - NANP_MIN_DIGITS + 1;
if (endPos < 0) {
return new int[]{};
}
/*
* We can't just strip the whitespace out and crunch it down, because the whitespace
* is significant. March through, trying to figure out where numbers start and end.
*/
while (startPos < endPos) {
// skip whitespace
while (Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) {
startPos++;
}
if (startPos == endPos) {
break;
}
// check for a match at this position
int matchEnd = findNanpMatchEnd(text, startPos);
if (matchEnd > startPos) {
list.add(startPos);
list.add(matchEnd);
startPos = matchEnd; // skip past match
} else {
// skip to next whitespace char
while (!Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) {
startPos++;
}
}
}
int[] result = new int[list.size()];
for (int i = list.size() - 1; i >= 0; i--) {
result[i] = list.get(i);
}
return result;
}
/**
* Checks to see if there is a valid phone number in the input, starting at the specified
* offset. If so, the index of the last character + 1 is returned. The input is assumed
* to begin with a non-whitespace character.
*
* @return Exclusive end position, or -1 if not a match.
*/
private static int findNanpMatchEnd(CharSequence text, int startPos) {
/*
* A few interesting cases:
* 94043 # too short, ignore
* 123456789012 # too long, ignore
* +1 (650) 555-1212 # 11 digits, spaces
* (650) 555 5555 # Second space, only when first is present.
* (650) 555-1212, (650) 555-1213 # two numbers, return first
* 1-650-555-1212 # 11 digits with leading '1'
* *#650.555.1212#*! # 10 digits, include #*, ignore trailing '!'
* 555.1212 # 7 digits
*
* For the most part we want to break on whitespace, but it's common to leave a space
* between the initial '1' and/or after the area code.
*/
// Check for "tel:" URI prefix.
if (text.length() > startPos + 4
&& text.subSequence(startPos, startPos + 4).toString().equalsIgnoreCase("tel:")) {
startPos += 4;
}
int endPos = text.length();
int curPos = startPos;
int foundDigits = 0;
char firstDigit = 'x';
boolean foundWhiteSpaceAfterAreaCode = false;
while (curPos <= endPos) {
char ch;
if (curPos < endPos) {
ch = text.charAt(curPos);
} else {
ch = 27; // fake invalid symbol at end to trigger loop break
}
if (Character.isDigit(ch)) {
if (foundDigits == 0) {
firstDigit = ch;
}
foundDigits++;
if (foundDigits > NANP_MAX_DIGITS) {
// too many digits, stop early
return -1;
}
} else if (Character.isWhitespace(ch)) {
if ((firstDigit == '1' && foundDigits == 4) ||
(foundDigits == 3)) {
foundWhiteSpaceAfterAreaCode = true;
} else if (firstDigit == '1' && foundDigits == 1) {
} else if (foundWhiteSpaceAfterAreaCode
&& ((firstDigit == '1' && (foundDigits == 7)) || (foundDigits == 6))) {
} else {
break;
}
} else if (NANP_ALLOWED_SYMBOLS.indexOf(ch) == -1) {
break;
}
// else it's an allowed symbol
curPos++;
}
if ((firstDigit != '1' && (foundDigits == 7 || foundDigits == 10)) ||
(firstDigit == '1' && foundDigits == 11)) {
// match
return curPos;
}
return -1;
}
/**
* Determines whether a new span at [start,end) will overlap with any existing span.
*/
private static boolean spanWillOverlap(Spannable spanText, URLSpan[] spanList, int start,
int end) {
if (start == end) {
// empty span, ignore
return false;
}
for (URLSpan span : spanList) {
int existingStart = spanText.getSpanStart(span);
int existingEnd = spanText.getSpanEnd(span);
if ((start >= existingStart && start < existingEnd) ||
end > existingStart && end <= existingEnd) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
CharSequence seq = spanText.subSequence(start, end);
Log.v(TAG, "Not linkifying " + seq + " as phone number due to overlap");
}
return true;
}
}
return false;
}
}
================================================
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/WeekButton.java
================================================
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 be.billington.calendar.recurrencepicker;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
public class WeekButton extends android.widget.ToggleButton {
private static int mWidth;
public WeekButton(Context context) {
super(context);
}
public WeekButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WeekButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public static void setSuggestedWidth(int w) {
mWidth = w;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int h = getMeasuredHeight();
int w = getMeasuredWidth();
if (h > 0 && w > 0) {
if (w < h) {
if (View.MeasureSpec.getMode(getMeasuredHeightAndState()) != MeasureSpec.EXACTLY) {
h = w;
}
} else if (h < w) {
if (View.MeasureSpec.getMode(getMeasuredWidthAndState()) != MeasureSpec.EXACTLY) {
w = h;
}
}
}
setMeasuredDimension(w, h);
}
}
================================================
FILE: library/src/main/res/color/done_text_color.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/done_text_color_disabled" />
<item android:state_enabled="true" android:color="@color/done_text_color_normal" />
</selector><!-- From: file:/E:/dev/src/github/datetimepicker/datetimepicker-library/res/color/done_text_color.xml -->
================================================
FILE: library/src/main/res/color/recurrence_bubble_text_color.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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_checked="true" android:color="@android:color/white" />
<item android:state_enabled="false" android:color="@color/done_text_color_disabled" />
<item android:color="@color/recurrence_bubble_text_normal" />
</selector>
================================================
FILE: library/src/main/res/color/recurrence_spinner_text_color.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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_enabled="false" android:color="@color/done_text_color_disabled" />
<item android:color="@android:color/black" />
</selector>
================================================
FILE: library/src/main/res/drawable/recurrence_bubble_fill.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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_enabled="false" android:state_checked="false" android:drawable="@drawable/ic_recurrence_bubble_outline_disabled" />
<item android:state_enabled="false" android:state_checked="true" android:drawable="@drawable/ic_recurrence_bubble_disabled" />
<item android:state_pressed="true" android:state_window_focused="true" android:drawable="@drawable/ic_recurrence_bubble_fill" />
<item android:state_checked="true" android:drawable="@drawable/ic_recurrence_bubble_fill" />
<item android:drawable="@drawable/ic_recurrence_bubble_outline" />
</selector>
================================================
FILE: library/src/main/res/drawable/switch_thumb.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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:drawable="@drawable/switch_thumb_disabled_holo_light" android:state_enabled="false" />
<item android:drawable="@drawable/switch_thumb_pressed_holo_light" android:state_pressed="true" />
<item android:drawable="@drawable/switch_thumb_activated_holo_light" android:state_checked="true" />
<item android:drawable="@drawable/switch_thumb_holo_light_v2" />
</selector>
================================================
FILE: library/src/main/res/layout/recurrencepicker.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2013 The Android Open Source Project
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- This outer linear layout protects the fixed dimensions of the dialog.
The dimensions are not respected if this outer layout is not present. -->
<LinearLayout
android:layout_width="@dimen/recurrence_picker_width"
android:layout_height="@dimen/recurrence_picker_height"
android:background="@color/recurrence_picker_background"
android:orientation="vertical"
android:padding="0dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingLeft="12dip"
android:paddingTop="12dip"
android:paddingRight="12dip"
android:paddingBottom="4dip">
<Spinner
android:id="@+id/freqSpinner"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:entries="@array/recurrence_freq"
android:gravity="left"
android:padding="0dp" />
<Switch
android:id="@+id/repeat_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:thumb="@drawable/switch_thumb"
android:layout_marginLeft="4dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:measureAllChildren="true"
android:orientation="vertical"
android:paddingBottom="4dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="4dp">
<LinearLayout
android:id="@+id/intervalGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingLeft="4dp">
<TextView
android:id="@+id/intervalPreText"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/interval"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="2"
android:gravity="center_horizontal"
android:inputType="number"
android:maxLength="2"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:selectAllOnFocus="true"
android:textSize="15sp"
android:singleLine="true"></EditText>
<TextView
android:id="@+id/intervalPostText"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<be.billington.calendar.recurrencepicker.LinearLayoutWithMaxWidth
android:id="@+id/weekGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="8dp"
android:gravity="center_horizontal"
android:orientation="horizontal">
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
</be.billington.calendar.recurrencepicker.LinearLayoutWithMaxWidth>
<be.billington.calendar.recurrencepicker.LinearLayoutWithMaxWidth
android:id="@+id/weekGroup2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:visibility="gone">
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton style="@style/RecurrenceDayOfWeekStyle" />
<be.billington.calendar.recurrencepicker.WeekButton
style="@style/RecurrenceDayOfWeekStyle"
android:visibility="invisible" />
</be.billington.calendar.recurrencepicker.LinearLayoutWithMaxWidth>
<RadioGroup
android:id="@+id/monthGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/repeatMonthlyByNthDayOfMonth"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recurrence_month_pattern_by_day" />
<RadioButton
android:id="@+id/repeatMonthlyByNthDayOfTheWeek"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
<LinearLayout
android:id="@+id/endGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="horizontal"
android:paddingLeft="0dp">
<Spinner
android:id="@+id/endSpinner"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="-4dp"
android:layout_marginRight="8dp"
android:entries="@array/recurrence_freq"
android:gravity="left|center_vertical"
android:padding="0dp" />
<EditText
android:id="@+id/endCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="-3dp"
android:ems="3"
android:gravity="center_horizontal"
android:inputType="number"
android:maxLength="3"
android:selectAllOnFocus="true"
android:textSize="15sp"
android:singleLine="true" />
<TextView
android:id="@+id/postEndCount"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="bottom"
android:singleLine="true"
android:layout_marginBottom="-3dp"
android:visibility="gone" />
<TextView
android:id="@+id/endDate"
style="@style/TextAppearance.EditEvent_SpinnerButton"
android:textSize="15sp"
android:textColor="@color/recurrence_spinner_text_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:singleLine="true"
android:minWidth="80dp"
android:contentDescription="@string/acessibility_recurrence_choose_end_date_description"
android:gravity="center_horizontal|bottom"
android:layout_marginTop="3dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:paddingLeft="12dp"
android:paddingRight="16dp"
android:paddingBottom="8dp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider"
tools:ignore="PxUsage" />
<LinearLayout
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/done"
style="?android:attr/buttonBarButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save_label"
android:textColor="@color/done_text_color" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
================================================
FILE: library/src/main/res/layout/recurrencepicker_end_text.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/spinner_item"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:paddingTop="12dp"
android:paddingBottom="8dip"
android:singleLine="true"
android:ellipsize="end" />
================================================
FILE: library/src/main/res/layout/recurrencepicker_freq_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2013 The Android Open Source Project
~
~ 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
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/spinner_item"
style="@style/TextAppearance.RecurrencePickerStyle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:ellipsize="end"
android:singleLine="true" />
================================================
FILE: library/src/main/res/values/arrays.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
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.
-->
<resources>
<!-- Choices for the "Reminder method" spinner.
These must be kept in sync with the reminder_methods_values array.
[CHAR LIMIT=10]
-->
<string-array name="reminder_methods_labels">
<item>Notification</item>
<item>Email</item>
<item>SMS</item>
<item>Alarm</item>
</string-array>
<!-- This maps reminder_methods_labels to internal constants. -->
<integer-array name="reminder_methods_values" translatable="false">
<item>1</item> <!-- METHOD_ALERT -->
<item>2</item> <!-- METHOD_EMAIL -->
<item>3</item> <!-- METHOD_SMS -->
<item>4</item> <!-- METHOD_ALARM -->
</integer-array>
<!-- Choices for the "Reminder minutes" spinner.
These must be kept in sync with the reminder_minutes_values array.
For consistency, the format should match what
EventViewUtils.constructReminderLabel() generates. (TODO: eliminate
this and just generate the list from reminder_minutes_values?)
[CHAR LIMIT=15]
-->
<string-array name="reminder_minutes_labels">
<item>0 minutes</item>
<item>1 minute</item>
<item>5 minutes</item>
<item>10 minutes</item>
<item>15 minutes</item>
<item>20 minutes</item>
<item>25 minutes</item>
<item>30 minutes</item>
<item>45 minutes</item>
<item>1 hour</item>
<item>2 hours</item>
<item>3 hours</item>
<item>12 hours</item>
<item>24 hours</item>
<item>2 days</item>
<item>1 week</item>
</string-array>
<integer-array name="reminder_minutes_values" translatable="false">
<item>0</item>
<item>1</item>
<item>5</item>
<item>10</item>
<item>15</item>
<item>20</item>
<item>25</item>
<item>30</item>
<item>45</item>
<item>60</item>
<item>120</item>
<item>180</item>
<item>720</item>
<item>1440</item>
<item>2880</item>
<item>10080</item>
</integer-array>
<string-array name="availability">
<item>Busy</item>
<item>Available</item>
<item>Tentative</item>
</string-array>
<!-- This maps reminder_methods_labels to internal constants. -->
<integer-array name="availability_values" translatable="false">
<item>0</item> <!-- Busy -->
<item>1</item> <!-- Available -->
<item>2</item> <!-- Tentative -->
</integer-array>
<string-array name="visibility">
<item>Default</item>
<item>Private</item>
<item>Public</item>
</string-array>
<string-array name="ordinal_labels">
<item>first</item>
<item>second</item>
<item>third</item>
<item>fourth</item>
<item>last</item>
</string-array>
<!-- Invitation responses -->
<string-array name="response_labels1">
<item>No response</item>
<item>Yes</item>
<item>Maybe</item>
gitextract_fk9y8ksi/ ├── .gitignore ├── LICENSE.txt ├── README.md ├── demo/ │ ├── pom.xml │ ├── project.properties │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── be/ │ │ └── billington/ │ │ └── calendar/ │ │ └── recurrencepicker/ │ │ └── demo/ │ │ └── activity/ │ │ └── DemoActivity.java │ └── res/ │ ├── layout/ │ │ └── demo.xml │ └── values/ │ ├── colors.xml │ └── strings.xml ├── library/ │ ├── pom.xml │ ├── project.properties │ ├── release.properties │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── be/ │ │ └── billington/ │ │ └── calendar/ │ │ └── recurrencepicker/ │ │ ├── EventRecurrence.java │ │ ├── EventRecurrenceFormatter.java │ │ ├── LinearLayoutWithMaxWidth.java │ │ ├── RecurrencePickerDialog.java │ │ ├── Utils.java │ │ └── WeekButton.java │ └── res/ │ ├── color/ │ │ ├── done_text_color.xml │ │ ├── recurrence_bubble_text_color.xml │ │ └── recurrence_spinner_text_color.xml │ ├── drawable/ │ │ ├── recurrence_bubble_fill.xml │ │ └── switch_thumb.xml │ ├── layout/ │ │ ├── recurrencepicker.xml │ │ ├── recurrencepicker_end_text.xml │ │ └── recurrencepicker_freq_item.xml │ ├── values/ │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-af/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-am/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ar/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-be/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-bg/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ca/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-cs/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-da/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-de/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-el/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-en-rGB/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-es/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-es-rUS/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-et/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-fa/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-fi/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-fr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-hi/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-hr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-hu/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-in/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-it/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-iw/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ja/ │ │ ├── arrays.xml │ │ ├── colors.xml │ │ └── strings.xml │ ├── values-ko/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-land/ │ │ └── dimens.xml │ ├── values-lt/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-lv/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-mcc262/ │ │ └── strings.xml │ ├── values-ms/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-nb/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-nl/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pl/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pt/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-pt-rPT/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ro/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-ru/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sk/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sl/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sv/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sw/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-sw600dp/ │ │ └── dimens.xml │ ├── values-sw600dp-land/ │ │ └── dimens.xml │ ├── values-th/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-tl/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-tr/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-uk/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-vi/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-zh-rCN/ │ │ ├── arrays.xml │ │ └── strings.xml │ ├── values-zh-rTW/ │ │ ├── arrays.xml │ │ └── strings.xml │ └── values-zu/ │ ├── arrays.xml │ └── strings.xml └── pom.xml
SYMBOL INDEX (166 symbols across 7 files)
FILE: demo/src/main/java/be/billington/calendar/recurrencepicker/demo/activity/DemoActivity.java
class DemoActivity (line 16) | public class DemoActivity extends FragmentActivity {
method onCreate (line 21) | public void onCreate(Bundle savedInstanceState) {
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrence.java
class EventRecurrence (line 30) | public class EventRecurrence {
class InvalidFormatException (line 167) | public static class InvalidFormatException extends RuntimeException {
method InvalidFormatException (line 168) | InvalidFormatException(String s) {
method setStartDate (line 174) | public void setStartDate(Time date) {
method calendarDay2Day (line 183) | public static int calendarDay2Day(int day) {
method timeDay2Day (line 204) | public static int timeDay2Day(int day) {
method day2TimeDay (line 225) | public static int day2TimeDay(int day) {
method day2CalendarDay (line 251) | public static int day2CalendarDay(int day) {
method day2String (line 281) | private static String day2String(int day) {
method appendNumbers (line 302) | private static void appendNumbers(StringBuilder s, String label,
method appendByDay (line 315) | private void appendByDay(StringBuilder s, int i) {
method toString (line 325) | @Override
method repeatsOnEveryWeekDay (line 399) | public boolean repeatsOnEveryWeekDay() {
method repeatsMonthlyOnDayCount (line 432) | public boolean repeatsMonthlyOnDayCount() {
method arraysEqual (line 458) | private static boolean arraysEqual(int[] array1, int count1, int[] arr...
method equals (line 471) | @Override
method hashCode (line 500) | @Override
method resetFields (line 517) | private void resetFields() {
method parse (line 530) | public void parse(String recur) {
class PartParser (line 664) | abstract static class PartParser {
method parsePart (line 672) | public abstract int parsePart(String value, EventRecurrence er);
method parseIntRange (line 683) | public static int parseIntRange(String str, int minVal, int maxVal, ...
method parseNumberList (line 708) | public static int[] parseNumberList(String listStr, int minVal, int ...
class ParseFreq (line 731) | private static class ParseFreq extends PartParser {
method parsePart (line 732) | @Override
class ParseUntil (line 746) | private static class ParseUntil extends PartParser {
method parsePart (line 747) | @Override
class ParseCount (line 766) | private static class ParseCount extends PartParser {
method parsePart (line 767) | @Override
class ParseInterval (line 781) | private static class ParseInterval extends PartParser {
method parsePart (line 782) | @Override
class ParseBySecond (line 796) | private static class ParseBySecond extends PartParser {
method parsePart (line 797) | @Override
class ParseByMinute (line 809) | private static class ParseByMinute extends PartParser {
method parsePart (line 810) | @Override
class ParseByHour (line 822) | private static class ParseByHour extends PartParser {
method parsePart (line 823) | @Override
class ParseByDay (line 835) | private static class ParseByDay extends PartParser {
method parsePart (line 836) | @Override
method parseWday (line 867) | private static void parseWday(String str, int[] byday, int[] bydayNu...
class ParseByMonthDay (line 892) | private static class ParseByMonthDay extends PartParser {
method parsePart (line 893) | @Override
class ParseByYearDay (line 905) | private static class ParseByYearDay extends PartParser {
method parsePart (line 906) | @Override
class ParseByWeekNo (line 918) | private static class ParseByWeekNo extends PartParser {
method parsePart (line 919) | @Override
class ParseByMonth (line 931) | private static class ParseByMonth extends PartParser {
method parsePart (line 932) | @Override
class ParseBySetPos (line 944) | private static class ParseBySetPos extends PartParser {
method parsePart (line 945) | @Override
class ParseWkst (line 957) | private static class ParseWkst extends PartParser {
method parsePart (line 958) | @Override
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrenceFormatter.java
class EventRecurrenceFormatter (line 28) | public class EventRecurrenceFormatter
method getRepeatString (line 34) | public static String getRepeatString(Context context, Resources r, Eve...
method cacheMonthRepeatStrings (line 126) | private static void cacheMonthRepeatStrings(Resources r, int weekday) {
method dayToString (line 151) | private static String dayToString(int day, int dayOfWeekLength) {
method dayToUtilDay (line 160) | private static int dayToUtilDay(int day) {
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java
class LinearLayoutWithMaxWidth (line 24) | public class LinearLayoutWithMaxWidth extends LinearLayout {
method LinearLayoutWithMaxWidth (line 26) | public LinearLayoutWithMaxWidth(Context context) {
method LinearLayoutWithMaxWidth (line 30) | public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs) {
method LinearLayoutWithMaxWidth (line 34) | public LinearLayoutWithMaxWidth(Context context, AttributeSet attrs, i...
method onMeasure (line 38) | @Override
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/RecurrencePickerDialog.java
class RecurrencePickerDialog (line 64) | public class RecurrencePickerDialog extends DialogFragment implements On...
class RecurrenceModel (line 86) | private class RecurrenceModel implements Parcelable {
method toString (line 180) | @Override
method describeContents (line 189) | @Override
method RecurrenceModel (line 194) | public RecurrenceModel() {
method writeToParcel (line 197) | @Override
class minMaxTextWatcher (line 215) | class minMaxTextWatcher implements TextWatcher {
method minMaxTextWatcher (line 220) | public minMaxTextWatcher(int min, int defaultInt, int max) {
method afterTextChanged (line 226) | @Override
method onChange (line 258) | void onChange(int value) {
method beforeTextChanged (line 261) | @Override
method onTextChanged (line 265) | @Override
method RecurrencePickerDialog (line 354) | public RecurrencePickerDialog() {
method isSupportedMonthlyByNthDayOfWeek (line 357) | static public boolean isSupportedMonthlyByNthDayOfWeek(int num) {
method canHandleRecurrenceRule (line 363) | static public boolean canHandleRecurrenceRule(EventRecurrence er) {
method copyEventRecurrenceToModel (line 420) | static private void copyEventRecurrenceToModel(final EventRecurrence er,
method copyModelToEventRecurrence (line 521) | static private void copyModelToEventRecurrence(final RecurrenceModel m...
method onCreateView (line 620) | @Override
method togglePickerOptions (line 840) | private void togglePickerOptions() {
method updateDoneButtonState (line 876) | private void updateDoneButtonState() {
method onSaveInstanceState (line 907) | @Override
method updateDialog (line 916) | public void updateDialog() {
method setEndSpinnerEndDateStr (line 1002) | private void setEndSpinnerEndDateStr(final String endDateString) {
method doToast (line 1007) | private void doToast() {
method updateIntervalText (line 1026) | private void updateIntervalText() {
method updateEndCountText (line 1047) | private void updateEndCountText() {
method onItemSelected (line 1067) | @Override
method onNothingSelected (line 1103) | @Override
method onDateSet (line 1107) | @Override
method onCheckedChanged (line 1122) | @Override
method onCheckedChanged (line 1136) | @Override
method onClick (line 1149) | @Override
method onActivityCreated (line 1175) | @Override
type OnRecurrenceSetListener (line 1185) | public interface OnRecurrenceSetListener {
method onRecurrenceSet (line 1186) | void onRecurrenceSet(String rrule);
method setOnRecurrenceSetListener (line 1189) | public void setOnRecurrenceSetListener(OnRecurrenceSetListener l) {
class EndSpinnerAdapter (line 1193) | private class EndSpinnerAdapter extends ArrayAdapter<CharSequence> {
method EndSpinnerAdapter (line 1209) | public EndSpinnerAdapter(Context context, ArrayList<CharSequence> st...
method getView (line 1245) | @Override
method getDropDownView (line 1311) | @Override
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/Utils.java
class Utils (line 60) | public class Utils {
method isJellybeanOrLater (line 191) | public static boolean isJellybeanOrLater() {
method isKeyLimePieOrLater (line 198) | public static boolean isKeyLimePieOrLater() {
method getWidgetUpdateAction (line 206) | public static String getWidgetUpdateAction(Context context) {
method getWidgetScheduledUpdateAction (line 213) | public static String getWidgetScheduledUpdateAction(Context context) {
method getSearchAuthority (line 220) | public static String getSearchAuthority(Context context) {
method tardis (line 225) | protected static void tardis() {
method getTardis (line 229) | protected static long getTardis() {
method matrixCursorFromCursor (line 234) | public static MatrixCursor matrixCursorFromCursor(Cursor cursor) {
method compareCursors (line 262) | public static boolean compareCursors(Cursor c1, Cursor c2) {
method timeFromIntentInMillis (line 293) | public static final long timeFromIntentInMillis(Intent intent) {
method join (line 324) | public static String join(List<?> things, String delim) {
method getWeeksSinceEpochFromJulianDay (line 351) | public static int getWeeksSinceEpochFromJulianDay(int julianDay, int f...
method getJulianMondayFromWeeksSinceEpoch (line 371) | public static int getJulianMondayFromWeeksSinceEpoch(int week) {
method getFirstDayOfWeek (line 380) | public static int getFirstDayOfWeek(Context context) {
method getFirstDayOfWeekAsCalendar (line 397) | public static int getFirstDayOfWeekAsCalendar(Context context) {
method convertDayOfWeekFromTimeToCalendar (line 404) | public static int convertDayOfWeekFromTimeToCalendar(int timeDayOfWeek) {
method isSaturday (line 434) | public static boolean isSaturday(int column, int firstDayOfWeek) {
method isSunday (line 447) | public static boolean isSunday(int column, int firstDayOfWeek) {
method convertAlldayUtcToLocal (line 461) | public static long convertAlldayUtcToLocal(Time recycle, long utcTime,...
method convertAlldayLocalToUTC (line 471) | public static long convertAlldayLocalToUTC(Time recycle, long localTim...
method getNextMidnight (line 488) | public static long getNextMidnight(Time recycle, long theTime, String ...
method checkForDuplicateNames (line 510) | public static void checkForDuplicateNames(
method equals (line 530) | public static boolean equals(Object o1, Object o2) {
method setAllowWeekForDetailView (line 534) | public static void setAllowWeekForDetailView(boolean allowWeekView) {
method getAllowWeekForDetailView (line 538) | public static boolean getAllowWeekForDetailView() {
method getConfigBool (line 542) | public static boolean getConfigBool(Context c, int key) {
method getDisplayColorFromColor (line 553) | public static int getDisplayColorFromColor(int color) {
method getDeclinedColorFromColor (line 567) | public static int getDeclinedColorFromColor(int color) {
class DNAStrand (line 580) | public static class DNAStrand {
class DNASegment (line 590) | private static class DNASegment {
method weaveDNAStrands (line 600) | private static void weaveDNAStrands(LinkedList<DNASegment> segments, i...
method getPixelOffsetFromMinutes (line 645) | private static int getPixelOffsetFromMinutes(int minute, int workDayHe...
method getOrCreateStrand (line 663) | private static DNAStrand getOrCreateStrand(HashMap<Integer, DNAStrand>...
method setUpSearchView (line 682) | public static void setUpSearchView(SearchView view, Activity act) {
method setMidnightUpdater (line 690) | public static void setMidnightUpdater(Handler h, Runnable r, String ti...
method resetMidnightUpdater (line 704) | public static void resetMidnightUpdater(Handler h, Runnable r) {
method getDisplayedTimezone (line 715) | public static String getDisplayedTimezone(long startMillis, String loc...
method singleDayEvent (line 735) | private static boolean singleDayEvent(long startMillis, long endMillis...
method isTodayOrTomorrow (line 755) | private static int isTodayOrTomorrow(Resources r, long dayMillis,
method createEmailAttendeesIntent (line 780) | public static Intent createEmailAttendeesIntent(Resources resources, S...
method isValidEmail (line 866) | public static boolean isValidEmail(String email) {
method isEmailableFrom (line 876) | public static boolean isEmailableFrom(String email, String syncAccount...
class CalendarBroadcastReceiver (line 880) | private static class CalendarBroadcastReceiver extends BroadcastReceiv...
method CalendarBroadcastReceiver (line 884) | public CalendarBroadcastReceiver(Runnable callback) {
method onReceive (line 889) | @Override
method setTimeChangesReceiver (line 902) | public static BroadcastReceiver setTimeChangesReceiver(Context c, Runn...
method clearTimeChangesReceiver (line 914) | public static void clearTimeChangesReceiver(Context c, BroadcastReceiv...
method getVersionCode (line 921) | public static String getVersionCode(Context context) {
method startCalendarMetafeedSync (line 948) | public static void startCalendarMetafeedSync(Account account) {
method extendedLinkify (line 967) | public static Spannable extendedLinkify(String text, boolean lastDitch...
method indexFirstNonWhitespaceChar (line 1109) | private static int indexFirstNonWhitespaceChar(CharSequence str) {
method indexLastNonWhitespaceChar (line 1118) | private static int indexLastNonWhitespaceChar(CharSequence str) {
method findNanpPhoneNumbers (line 1134) | static int[] findNanpPhoneNumbers(CharSequence text) {
method findNanpMatchEnd (line 1184) | private static int findNanpMatchEnd(CharSequence text, int startPos) {
method spanWillOverlap (line 1259) | private static boolean spanWillOverlap(Spannable spanText, URLSpan[] s...
FILE: library/src/main/java/be/billington/calendar/recurrencepicker/WeekButton.java
class WeekButton (line 23) | public class WeekButton extends android.widget.ToggleButton {
method WeekButton (line 27) | public WeekButton(Context context) {
method WeekButton (line 31) | public WeekButton(Context context, AttributeSet attrs) {
method WeekButton (line 35) | public WeekButton(Context context, AttributeSet attrs, int defStyle) {
method setSuggestedWidth (line 39) | public static void setSuggestedWidth(int w) {
method onMeasure (line 43) | @Override
Condensed preview — 135 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (902K chars).
[
{
"path": ".gitignore",
"chars": 514,
"preview": "# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\n"
},
{
"path": "LICENSE.txt",
"chars": 11358,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 1173,
"preview": "Android Recurrence Picker\n=========================\n\nGoogle Calendar Recurrence picker\n\nScreenshot\n==========\n\n![Example"
},
{
"path": "demo/pom.xml",
"chars": 3084,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "demo/project.properties",
"chars": 563,
"preview": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# T"
},
{
"path": "demo/src/main/AndroidManifest.xml",
"chars": 825,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:"
},
{
"path": "demo/src/main/java/be/billington/calendar/recurrencepicker/demo/activity/DemoActivity.java",
"chars": 2448,
"preview": "package be.billington.calendar.recurrencepicker.demo.activity;\n\nimport android.os.Bundle;\nimport android.support.v4.app."
},
{
"path": "demo/src/main/res/layout/demo.xml",
"chars": 575,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andr"
},
{
"path": "demo/src/main/res/values/colors.xml",
"chars": 965,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"white\">#FFFFFF</color>\n <color name=\"black\">#0000"
},
{
"path": "demo/src/main/res/values/strings.xml",
"chars": 96,
"preview": "<resources>\n\n <string name=\"app_name\">Android Recurrence Picker - Demo</string>\n\n</resources>"
},
{
"path": "library/pom.xml",
"chars": 2713,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
},
{
"path": "library/project.properties",
"chars": 584,
"preview": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# T"
},
{
"path": "library/release.properties",
"chars": 327,
"preview": "#release configuration\n#Mon Jan 20 16:21:01 CET 2014\npreparationGoals=clean verify\npushChanges=true\nscm.commentPrefix=[m"
},
{
"path": "library/src/main/AndroidManifest.xml",
"chars": 401,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrence.java",
"chars": 33899,
"preview": "package be.billington.calendar.recurrencepicker;\n/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/EventRecurrenceFormatter.java",
"chars": 7436,
"preview": "package be.billington.calendar.recurrencepicker;\n\n/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * License"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/LinearLayoutWithMaxWidth.java",
"chars": 1446,
"preview": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/RecurrencePickerDialog.java",
"chars": 51012,
"preview": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/Utils.java",
"chars": 50019,
"preview": "/*\n * Copyright (C) 2006 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "library/src/main/java/be/billington/calendar/recurrencepicker/WeekButton.java",
"chars": 1899,
"preview": "/*\n * Copyright (C) 2013 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "library/src/main/res/color/done_text_color.xml",
"chars": 407,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector\n xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item"
},
{
"path": "library/src/main/res/color/recurrence_bubble_text_color.xml",
"chars": 995,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed und"
},
{
"path": "library/src/main/res/color/recurrence_spinner_text_color.xml",
"chars": 900,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed und"
},
{
"path": "library/src/main/res/drawable/recurrence_bubble_fill.xml",
"chars": 1331,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed und"
},
{
"path": "library/src/main/res/drawable/switch_thumb.xml",
"chars": 1138,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed under"
},
{
"path": "library/src/main/res/layout/recurrencepicker.xml",
"chars": 13600,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Copyright (C) 2013 The Android Open Source Project\n\n Licensed under "
},
{
"path": "library/src/main/res/layout/recurrencepicker_end_text.xml",
"chars": 1101,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed under"
},
{
"path": "library/src/main/res/layout/recurrencepicker_freq_item.xml",
"chars": 1002,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n ~ Copyright (C) 2013 The Android Open Source Project\n ~\n ~ Licensed under"
},
{
"path": "library/src/main/res/values/arrays.xml",
"chars": 9678,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the "
},
{
"path": "library/src/main/res/values/colors.xml",
"chars": 8220,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/* //device/apps/Calendar/assets/res/any/colors.xml\n**\n** Copyright 2006, The"
},
{
"path": "library/src/main/res/values/dimens.xml",
"chars": 808,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2011 The Android Open Source Project\n\n Licensed under the A"
},
{
"path": "library/src/main/res/values/strings.xml",
"chars": 7968,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the "
},
{
"path": "library/src/main/res/values/styles.xml",
"chars": 3597,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/* //device/apps/Calendar/assets/res/any/styles.xml\n**\n** Copyright 2006, The"
},
{
"path": "library/src/main/res/values-af/arrays.xml",
"chars": 8795,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-af/strings.xml",
"chars": 3213,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-am/arrays.xml",
"chars": 8297,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-am/strings.xml",
"chars": 3130,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ar/arrays.xml",
"chars": 8913,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ar/strings.xml",
"chars": 3166,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-be/arrays.xml",
"chars": 8800,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-be/strings.xml",
"chars": 4997,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-bg/arrays.xml",
"chars": 8710,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-bg/strings.xml",
"chars": 3211,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ca/arrays.xml",
"chars": 9011,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ca/strings.xml",
"chars": 3252,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-cs/arrays.xml",
"chars": 8684,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-cs/strings.xml",
"chars": 3253,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-da/arrays.xml",
"chars": 8813,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-da/strings.xml",
"chars": 3221,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-de/arrays.xml",
"chars": 8891,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-de/strings.xml",
"chars": 3202,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-el/arrays.xml",
"chars": 8766,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-el/strings.xml",
"chars": 3271,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-en-rGB/arrays.xml",
"chars": 8748,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-en-rGB/strings.xml",
"chars": 4928,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-es/arrays.xml",
"chars": 9002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-es/strings.xml",
"chars": 3227,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-es-rUS/arrays.xml",
"chars": 9012,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-es-rUS/strings.xml",
"chars": 3200,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-et/arrays.xml",
"chars": 8985,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-et/strings.xml",
"chars": 3261,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fa/arrays.xml",
"chars": 8603,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fa/strings.xml",
"chars": 3173,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fi/arrays.xml",
"chars": 8926,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fi/strings.xml",
"chars": 3240,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fr/arrays.xml",
"chars": 9002,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-fr/strings.xml",
"chars": 5053,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hi/arrays.xml",
"chars": 8761,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hi/strings.xml",
"chars": 3249,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hr/arrays.xml",
"chars": 8723,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hr/strings.xml",
"chars": 3261,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hu/arrays.xml",
"chars": 8788,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-hu/strings.xml",
"chars": 3214,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-in/arrays.xml",
"chars": 8684,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-in/strings.xml",
"chars": 3226,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-it/arrays.xml",
"chars": 8685,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-it/strings.xml",
"chars": 3230,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-iw/arrays.xml",
"chars": 8895,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-iw/strings.xml",
"chars": 3174,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ja/arrays.xml",
"chars": 7850,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ja/colors.xml",
"chars": 1006,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n/* //device/apps/Calendar/assets/res/any/colors.xml\n**\n** Copyright 2006, The"
},
{
"path": "library/src/main/res/values-ja/strings.xml",
"chars": 3095,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ko/arrays.xml",
"chars": 7952,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ko/strings.xml",
"chars": 3063,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-land/dimens.xml",
"chars": 808,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Copyright (C) 2009 The Android Open Source Project\n\n Licensed under the A"
},
{
"path": "library/src/main/res/values-lt/arrays.xml",
"chars": 8939,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-lt/strings.xml",
"chars": 3203,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-lv/arrays.xml",
"chars": 9118,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-lv/strings.xml",
"chars": 3279,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-mcc262/strings.xml",
"chars": 893,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Copyright (C) 2011 The Android Open Source Project\n\n Licensed under the "
},
{
"path": "library/src/main/res/values-ms/arrays.xml",
"chars": 8833,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ms/strings.xml",
"chars": 3217,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-nb/arrays.xml",
"chars": 9004,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-nb/strings.xml",
"chars": 3202,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-nl/arrays.xml",
"chars": 8827,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-nl/strings.xml",
"chars": 4979,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pl/arrays.xml",
"chars": 8821,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pl/strings.xml",
"chars": 3201,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pt/arrays.xml",
"chars": 8996,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pt/strings.xml",
"chars": 3212,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pt-rPT/arrays.xml",
"chars": 8974,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-pt-rPT/strings.xml",
"chars": 3229,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ro/arrays.xml",
"chars": 8958,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ro/strings.xml",
"chars": 3262,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ru/arrays.xml",
"chars": 8791,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-ru/strings.xml",
"chars": 3206,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sk/arrays.xml",
"chars": 8693,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sk/strings.xml",
"chars": 3209,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sl/arrays.xml",
"chars": 8907,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sl/strings.xml",
"chars": 3301,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sr/arrays.xml",
"chars": 8708,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sr/strings.xml",
"chars": 3211,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sv/arrays.xml",
"chars": 9083,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sv/strings.xml",
"chars": 3288,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sw/arrays.xml",
"chars": 8733,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sw/strings.xml",
"chars": 3279,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-sw600dp/dimens.xml",
"chars": 813,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Copyright (C) 2011 The Android Open Source Project\n\n Licensed under "
},
{
"path": "library/src/main/res/values-sw600dp-land/dimens.xml",
"chars": 813,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n Copyright (C) 2011 The Android Open Source Project\n\n Licensed under "
},
{
"path": "library/src/main/res/values-th/arrays.xml",
"chars": 8623,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-th/strings.xml",
"chars": 3207,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-tl/arrays.xml",
"chars": 8858,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-tl/strings.xml",
"chars": 3278,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-tr/arrays.xml",
"chars": 8954,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-tr/strings.xml",
"chars": 3262,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-uk/arrays.xml",
"chars": 8857,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-uk/strings.xml",
"chars": 3228,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-vi/arrays.xml",
"chars": 8820,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-vi/strings.xml",
"chars": 3280,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zh-rCN/arrays.xml",
"chars": 7819,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zh-rCN/strings.xml",
"chars": 3030,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zh-rTW/arrays.xml",
"chars": 7841,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zh-rTW/strings.xml",
"chars": 3064,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zu/arrays.xml",
"chars": 9174,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2007 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "library/src/main/res/values-zu/strings.xml",
"chars": 3351,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Copyright (C) 2006 The Android Open Source Project\n\n Licensed under the"
},
{
"path": "pom.xml",
"chars": 5241,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2"
}
]
About this extraction
This page contains the full source code of the Shusshu/Android-RecurrencePicker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 135 files (794.7 KB), approximately 249.4k tokens, and a symbol index with 166 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.