Refactored so that the widget is now with the app, not the provider

- CalendarAppWidgetReceiver now functions as the broadcast receiver for:
     * android.intent.action.TIMEZONE_CHANGED
     * android.intent.action.TIME_SET
     * android.intent.action.PROVIDER_CHANGED (content://com.android.calendar)
 - CalendarAppWidgetService has been refactored to use IntentService for
   serializing widget updates
 - AppWidgetShared has been preserved for managing wake locks
 - CalendarAppWidgetModel was pulled out into its own class

Change-Id: If7641aba278acc8ad00f14acd289ddbcbb1d5bcf
This commit is contained in:
Mason Tang 2010-06-22 17:03:54 -07:00
parent 8d69cd014d
commit bb3f08abac
29 changed files with 2092 additions and 3 deletions

View File

@ -9,7 +9,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under,src)
# library.
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_STATIC_JAVA_LIBRARIES += android-common
LOCAL_STATIC_JAVA_LIBRARIES += android-common guava
LOCAL_PACKAGE_NAME := Calendar

View File

@ -124,6 +124,29 @@
<service android:name="DismissAllAlarmsService" />
<!-- Declarations for the widget -->
<receiver android:name="CalendarAppWidgetReceiver" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.TIME_SET" />
<action android:name="android.intent.action.DATE_CHANGED" />
</intent-filter>
<intent-filter>
<data android:scheme="content" android:host="com.android.calendar"/>
<action android:name="android.intent.action.PROVIDER_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name="CalendarAppWidgetProvider" android:label="@string/gadget_title">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.android.calendar.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_info" />
</receiver>
<service android:name="CalendarAppWidgetService" />
<activity android:name="CalendarTests" android:label="Calendar Tests">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:interpolator="@android:anim/decelerate_interpolator"
android:fromYDelta="100%p" android:toYDelta="0"
android:duration="@integer/slide_transition_duration"/>
<alpha
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@integer/slide_transition_duration" />
</set>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:interpolator="@android:anim/decelerate_interpolator"
android:fromYDelta="0" android:toYDelta="-100%p"
android:duration="@integer/slide_transition_duration"/>
<alpha
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1.0" android:toAlpha="0"
android:duration="@integer/slide_transition_duration" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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_window_focused="false" android:drawable="@drawable/appwidget_bg" />
<item android:state_pressed="true" android:drawable="@drawable/appwidget_bg_press" />
<item android:state_focused="true" android:drawable="@drawable/appwidget_bg_focus" />
<item android:drawable="@drawable/appwidget_bg" />
</selector>

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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"
android:id="@+id/appwidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/appwidget_background"
android:focusable="true"
android:clickable="true">
<!-- Header -->
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="40dip"
android:orientation="horizontal"
android:background="@drawable/appwidget_calendar_bgtop_blue">
<TextView
android:id="@+id/day_of_week"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginLeft="7dip"
android:layout_marginRight="7dip"
android:layout_marginBottom="5dip"
android:textColor="@color/appwidget_date"
android:textSize="18sp"
android:gravity="left|bottom"
android:singleLine="true" />
<TextView
android:id="@+id/day_of_month"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="7dip"
android:layout_marginRight="7dip"
android:layout_marginBottom="5dip"
android:gravity="right|bottom"
android:textColor="@color/appwidget_date"
android:textSize="20sp"
android:textStyle="bold"
android:singleLine="true" />
</LinearLayout>
<!-- No Event -->
<TextView
android:id="@+id/no_events"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dip"
android:padding="7dip"
android:gravity="center"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/appwidget_no_events"
android:text="@string/gadget_no_events" />
<!-- Event #1 -->
<TextView
android:id="@+id/when1"
style="@style/TextAppearance.WidgetWhen" />
<TextView
android:id="@+id/where1"
style="@style/TextAppearance.WidgetWhere" />
<TextView
android:id="@+id/title1"
android:layout_marginBottom="-3dip"
style="@style/TextAppearance.WidgetTitle" />
<!-- Conflict banner -->
<TextView
android:id="@+id/conflict_landscape"
style="@style/TextAppearance.WidgetConflict" />
<!-- These fields are not visible in landscape mode but required to avoid exceptions -->
<TextView
android:id="@+id/when2"
android:visibility="gone"
android:layout_width="0dp"
android:layout_height="0dp" />
<TextView
android:id="@+id/where2"
android:visibility="gone"
android:layout_width="0dp"
android:layout_height="0dp" />
<TextView
android:id="@+id/title2"
android:visibility="gone"
android:layout_width="0dp"
android:layout_height="0dp" />
<TextView
android:id="@+id/conflict_portrait"
android:visibility="gone"
android:layout_width="0dp"
android:layout_height="0dp" />
</LinearLayout>

96
res/layout/appwidget.xml Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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"
android:id="@+id/appwidget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/appwidget_background"
android:focusable="true"
android:clickable="true">
<!-- Header -->
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="40dip"
android:orientation="horizontal"
android:background="@drawable/appwidget_calendar_bgtop_blue">
<TextView
android:id="@+id/day_of_week"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginLeft="7dip"
android:layout_marginRight="7dip"
android:layout_marginBottom="5dip"
android:textColor="@color/appwidget_date"
android:textSize="18sp"
android:gravity="left|bottom"
android:singleLine="true" />
<TextView
android:id="@+id/day_of_month"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginLeft="7dip"
android:layout_marginRight="7dip"
android:layout_marginBottom="5dip"
android:gravity="right|bottom"
android:textColor="@color/appwidget_date"
android:textSize="20sp"
android:textStyle="bold"
android:singleLine="true" />
</LinearLayout>
<!-- Container to show only a single page -->
<FrameLayout
android:id="@+id/single_page"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<!-- Flipper for event pages -->
<ViewFlipper
android:id="@+id/page_flipper"
android:autoStart="true"
android:flipInterval="@integer/flip_interval"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inAnimation="@anim/slide_in_fade"
android:outAnimation="@anim/slide_out_fade"
android:animateFirstView="false">
</ViewFlipper>
<!-- No Event -->
<TextView
android:id="@+id/no_events"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dip"
android:padding="7dip"
android:gravity="center"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/appwidget_no_events"
android:text="@string/gadget_no_events" />
</LinearLayout>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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"
android:id="@+id/appwidget_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:addStatesFromChildren="true">
<!-- Event #1 -->
<TextView
android:id="@+id/when1"
style="@style/TextAppearance.WidgetWhen" />
<TextView
android:id="@+id/where1"
style="@style/TextAppearance.WidgetWhere" />
<TextView
android:id="@+id/title1"
android:layout_marginBottom="6dip"
style="@style/TextAppearance.WidgetTitle" />
<!-- Event #2 -->
<TextView
android:id="@+id/when2"
style="@style/TextAppearance.WidgetWhen" />
<TextView
android:id="@+id/where2"
style="@style/TextAppearance.WidgetWhere" />
<TextView
android:id="@+id/title2"
android:layout_marginBottom="6dip"
style="@style/TextAppearance.WidgetTitle" />
<!-- Page count -->
<TextView
android:id="@+id/page_count"
android:gravity="right|bottom"
android:layout_gravity="bottom"
android:layout_weight="1"
style="@style/TextAppearance.WidgetPageCount">
</TextView>
</LinearLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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>
<dimen name="appwidget_width">206dip</dimen>
<dimen name="appwidget_height">143dip</dimen>
</resources>

View File

@ -65,5 +65,13 @@
<color name="agenda_day_bar_color">#c8c8c8</color>
<drawable name="agenda_item_declined">#88ffffff</drawable>
<color name="appwidget_date">#ff000000</color>
<color name="appwidget_when">#ff666666</color>
<color name="appwidget_title">#ff000000</color>
<color name="appwidget_where">#ff666666</color>
<color name="appwidget_conflict">#ff000000</color>
<color name="appwidget_no_events">#bb000000</color>
<color name="appwidget_page_count">#ff666666</color>
</resources>

View File

@ -4,9 +4,9 @@
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.
@ -16,4 +16,6 @@
<resources>
<integer name="number_of_hours">10</integer>
<integer name="flip_interval">5000</integer>
<integer name="slide_transition_duration">600</integer>
</resources>

View File

@ -403,4 +403,30 @@
<!-- Dialog title for the Vibrate dialog -->
<string name="prefDialogTitle_vibrateWhen">Vibrate</string>
<!-- Widget -->
<skip />
<!-- Title for calendar gadget when displayed in list of all other gadgets -->
<string name="gadget_title">Calendar</string>
<!-- Shown in gadget when additional events are available for display, but no room remaining -->
<plurals name="gadget_more_events">
<!-- additional events message for 1 event -->
<item quantity="one">1 more event</item>
<!-- additional events message for multiple events -->
<item quantity="other"><xliff:g id="number">%d</xliff:g> more events</item>
</plurals>
<!-- Caption to show on gadget when there are no upcoming calendar events -->
<string name="gadget_no_events">No upcoming calendar events</string>
<!-- Text to show on gadget when an event starts on the next day -->
<string name="tomorrow">Tomorrow</string>
<!-- Text to show on gadget when an event is currently in progress -->
<string name="in_progress">in progress</string>
<!-- Text to show on gadget when an all-day event is in progress -->
<string name="today">Today</string>
</resources>

View File

@ -105,4 +105,72 @@
<item name="android:gravity">center_vertical|left</item>
</style>
<style name="TextAppearance.WidgetWhen">
<item name="android:visibility">gone</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">7dip</item>
<item name="android:layout_marginRight">7dip</item>
<item name="android:layout_marginTop">3dip</item>
<item name="android:layout_marginBottom">-3dip</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/appwidget_when</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.WidgetWhere">
<item name="android:visibility">gone</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">7dip</item>
<item name="android:layout_marginRight">7dip</item>
<item name="android:layout_marginBottom">-3dip</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/appwidget_where</item>
<item name="android:singleLine">true</item>
</style>
<style name="TextAppearance.WidgetTitle">
<item name="android:visibility">gone</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">7dip</item>
<item name="android:layout_marginRight">7dip</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/appwidget_title</item>
<item name="android:maxLines">2</item>
</style>
<style name="TextAppearance.WidgetConflict">
<item name="android:visibility">gone</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:paddingLeft">7dip</item>
<item name="android:paddingRight">7dip</item>
<item name="android:layout_marginBottom">4dip</item>
<item name="android:textSize">14sp</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">@color/appwidget_conflict</item>
</style>
<style name="TextAppearance.WidgetPageCount">
<item name="android:visibility">gone</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">5dip</item>
<item name="android:layout_marginRight">5dip</item>
<item name="android:layout_marginTop">3dip</item>
<item name="android:layout_marginBottom">7dip</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/appwidget_when</item>
<item name="android:singleLine">true</item>
</style>
</resources>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 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.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="146dip"
android:minHeight="146dip"
android:updatePeriodMillis="0"
android:initialLayout="@layout/appwidget"
>
</appwidget-provider>

View File

@ -0,0 +1,158 @@
/*
* Copyright (C) 2010 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 com.android.calendar;
import android.view.View;
import java.util.Arrays;
class CalendarAppWidgetModel {
String dayOfWeek;
String dayOfMonth;
/*
* TODO Refactor this so this class is used in the case of "no event"
* So for now, this field is always View.GONE
*/
int visibNoEvents;
EventInfo[] eventInfos;
public CalendarAppWidgetModel() {
this(2);
}
public CalendarAppWidgetModel(int size) {
// we round up to the nearest even integer
eventInfos = new EventInfo[2 * ((size + 1) / 2)];
for (int i = 0; i < eventInfos.length; i++) {
eventInfos[i] = new EventInfo();
}
visibNoEvents = View.GONE;
}
class EventInfo {
int visibWhen; // Visibility value for When textview (View.GONE or View.VISIBLE)
String when;
int visibWhere; // Visibility value for Where textview (View.GONE or View.VISIBLE)
String where;
int visibTitle; // Visibility value for Title textview (View.GONE or View.VISIBLE)
String title;
public EventInfo() {
visibWhen = View.GONE;
visibWhere = View.GONE;
visibTitle = View.GONE;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("EventInfo [visibTitle=");
builder.append(visibTitle);
builder.append(", title=");
builder.append(title);
builder.append(", visibWhen=");
builder.append(visibWhen);
builder.append(", when=");
builder.append(when);
builder.append(", visibWhere=");
builder.append(visibWhere);
builder.append(", where=");
builder.append(where);
builder.append("]");
return builder.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((title == null) ? 0 : title.hashCode());
result = prime * result + visibTitle;
result = prime * result + visibWhen;
result = prime * result + visibWhere;
result = prime * result + ((when == null) ? 0 : when.hashCode());
result = prime * result + ((where == null) ? 0 : where.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
EventInfo other = (EventInfo) obj;
if (title == null) {
if (other.title != null) {
return false;
}
} else if (!title.equals(other.title)) {
return false;
}
if (visibTitle != other.visibTitle) {
return false;
}
if (visibWhen != other.visibWhen) {
return false;
}
if (visibWhere != other.visibWhere) {
return false;
}
if (when == null) {
if (other.when != null) {
return false;
}
} else if (!when.equals(other.when)) {
return false;
}
if (where == null) {
if (other.where != null) {
return false;
}
} else if (!where.equals(other.where)) {
return false;
}
return true;
}
private CalendarAppWidgetModel getOuterType() {
return CalendarAppWidgetModel.this;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("\nCalendarAppWidgetModel [eventInfos=");
builder.append(Arrays.toString(eventInfos));
builder.append(", visibNoEvents=");
builder.append(visibNoEvents);
builder.append(", dayOfMonth=");
builder.append(dayOfMonth);
builder.append(", dayOfWeek=");
builder.append(dayOfWeek);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2009 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 com.android.calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.text.format.DateUtils;
/**
* Simple widget to show next upcoming calendar event.
*/
public class CalendarAppWidgetProvider extends AppWidgetProvider {
static final String TAG = "CalendarAppWidgetProvider";
static final boolean LOGD = false;
static final String ACTION_CALENDAR_APPWIDGET_UPDATE =
"com.android.calendar.APPWIDGET_UPDATE";
// TODO Move these to Calendar.java
static final String EXTRA_WIDGET_IDS = "com.android.calendar.EXTRA_WIDGET_IDS";
static final String EXTRA_EVENT_IDS = "com.android.calendar.EXTRA_EVENT_IDS";
/**
* {@inheritDoc}
*/
@Override
public void onReceive(Context context, Intent intent) {
// Handle calendar-specific updates ourselves because they might be
// coming in without extras, which AppWidgetProvider then blocks.
final String action = intent.getAction();
if (ACTION_CALENDAR_APPWIDGET_UPDATE.equals(action)) {
performUpdate(context, null /* all widgets */,
null /* no eventIds */);
} else {
super.onReceive(context, intent);
}
}
/**
* {@inheritDoc}
*/
@Override
public void onEnabled(Context context) {
// Enable updates for timezone, date, and provider changes
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName(context, CalendarAppWidgetReceiver.class),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}
/**
* {@inheritDoc}
*/
@Override
public void onDisabled(Context context) {
// Unsubscribe from all AlarmManager updates
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingUpdate = getUpdateIntent(context);
am.cancel(pendingUpdate);
// Disable updates for timezone, date, and provider changes
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(
new ComponentName(context, CalendarAppWidgetReceiver.class),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
/**
* {@inheritDoc}
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
performUpdate(context, appWidgetIds, null /* no eventIds */);
}
/**
* Build {@link ComponentName} describing this specific
* {@link AppWidgetProvider}
*/
static ComponentName getComponentName(Context context) {
return new ComponentName(context, CalendarAppWidgetProvider.class);
}
/**
* Process and push out an update for the given appWidgetIds. This call
* actually fires an intent to start {@link CalendarAppWidgetService} as a
* background service which handles the actual update, to prevent ANR'ing
* during database queries.
*
* @param context Context to use when starting {@link CalendarAppWidgetService}.
* @param appWidgetIds List of specific appWidgetIds to update, or null for
* all.
* @param changedEventIds Specific events known to be changed. If present,
* we use it to decide if an update is necessary.
*/
private void performUpdate(Context context, int[] appWidgetIds,
long[] changedEventIds) {
// Launch over to service so it can perform update
final Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
if (appWidgetIds != null) {
updateIntent.putExtra(EXTRA_WIDGET_IDS, appWidgetIds);
}
if (changedEventIds != null) {
updateIntent.putExtra(EXTRA_EVENT_IDS, changedEventIds);
}
context.startService(updateIntent);
}
/**
* Build the {@link PendingIntent} used to trigger an update of all calendar
* widgets. Uses {@link #ACTION_CALENDAR_APPWIDGET_UPDATE} to directly target
* all widgets instead of using {@link AppWidgetManager#EXTRA_APPWIDGET_IDS}.
*
* @param context Context to use when building broadcast.
*/
static PendingIntent getUpdateIntent(Context context) {
Intent updateIntent = new Intent(ACTION_CALENDAR_APPWIDGET_UPDATE);
updateIntent.setComponent(new ComponentName(context, CalendarAppWidgetProvider.class));
return PendingIntent.getBroadcast(context, 0 /* no requestCode */,
updateIntent, 0 /* no flags */);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2010 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 com.android.calendar;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class CalendarAppWidgetReceiver extends BroadcastReceiver {
private static final String TAG = "CalendarAppWidgetReceiver";
private static final boolean LOGD = false;
@Override
public void onReceive(Context context, Intent intent) {
// Launch over to service so it can perform update
final Intent updateIntent = new Intent(context, CalendarAppWidgetService.class);
// Copy over the relevant extra fields if they exist
if (intent.hasExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS)) {
int[] data = intent.getIntArrayExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS);
updateIntent.putExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS, data);
}
if (intent.hasExtra(CalendarAppWidgetProvider.EXTRA_WIDGET_IDS)) {
int[] data = intent.getIntArrayExtra(CalendarAppWidgetProvider.EXTRA_WIDGET_IDS);
updateIntent.putExtra(CalendarAppWidgetProvider.EXTRA_WIDGET_IDS, data);
}
if (LOGD) Log.d(TAG, "Something changed, updating widget");
context.startService(updateIntent);
}
}

View File

@ -0,0 +1,710 @@
/*
* Copyright (C) 2009 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 com.android.calendar;
import com.google.common.annotations.VisibleForTesting;
import com.android.calendar.CalendarAppWidgetModel.EventInfo;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Calendar.Attendees;
import android.provider.Calendar.Calendars;
import android.provider.Calendar.Instances;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
public class CalendarAppWidgetService extends IntentService {
private static final String TAG = "CalendarAppWidgetService";
private static final boolean LOGD = false;
/* TODO query doesn't handle all-day events properly, we should fix this in
* the provider in a manner similar to how it is handled in Event.loadEvents
* in the Calendar application.
*/
private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, "
+ Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, "
+ Instances.END_MINUTE + " ASC LIMIT 10";
// TODO can't use parameter here because provider is dropping them
private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND "
+ Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED;
static final String[] EVENT_PROJECTION = new String[] {
Instances.ALL_DAY,
Instances.BEGIN,
Instances.END,
Instances.TITLE,
Instances.EVENT_LOCATION,
Instances.EVENT_ID,
};
static final int INDEX_ALL_DAY = 0;
static final int INDEX_BEGIN = 1;
static final int INDEX_END = 2;
static final int INDEX_TITLE = 3;
static final int INDEX_EVENT_LOCATION = 4;
static final int INDEX_EVENT_ID = 5;
private static final long SEARCH_DURATION = DateUtils.WEEK_IN_MILLIS;
// If no next-update calculated, or bad trigger time in past, schedule
// update about six hours from now.
private static final long UPDATE_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6;
private static final String KEY_DETAIL_VIEW = "DETAIL_VIEW";
public CalendarAppWidgetService() {
super(TAG);
}
@Override
protected void onHandleIntent(Intent intent) {
// These will be null if the extra data doesn't exist
int[] widgetIds = intent.getIntArrayExtra(CalendarAppWidgetProvider.EXTRA_WIDGET_IDS);
long[] eventIds = null;
HashSet<Long> eventIdsSet = null;
if (intent.hasExtra(CalendarAppWidgetProvider.EXTRA_EVENT_IDS)) {
eventIds = intent.getExtras().getLongArray(CalendarAppWidgetProvider.EXTRA_EVENT_IDS);
eventIdsSet = new HashSet<Long>(eventIds.length);
for (int i = 0; i < eventIds.length; i++) {
eventIdsSet.add(eventIds[i]);
}
}
long now = System.currentTimeMillis();
performUpdate(this, widgetIds, eventIdsSet, now);
}
/**
* Process and push out an update for the given appWidgetIds.
*
* @param context Context to use when updating widget.
* @param appWidgetIds List of appWidgetIds to update, or null for all.
* @param changedEventIds Specific events known to be changed, otherwise
* null. If present, we use to decide if an update is necessary.
* @param now System clock time to use during this update.
*/
private void performUpdate(Context context, int[] appWidgetIds,
Set<Long> changedEventIds, long now) {
ContentResolver resolver = context.getContentResolver();
Cursor cursor = null;
RemoteViews views = null;
long triggerTime = -1;
try {
cursor = getUpcomingInstancesCursor(resolver, SEARCH_DURATION, now);
if (cursor != null) {
MarkedEvents events = buildMarkedEvents(cursor, changedEventIds, now);
boolean shouldUpdate = true;
if (changedEventIds != null && changedEventIds.size() > 0) {
shouldUpdate = events.watchFound;
}
if (events.markedIds.isEmpty()) {
views = getAppWidgetNoEvents(context);
} else if (shouldUpdate) {
views = getAppWidgetUpdate(context, cursor, events);
triggerTime = calculateUpdateTime(cursor, events);
}
} else {
views = getAppWidgetNoEvents(context);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
// Bail out early if no update built
if (views == null) {
if (LOGD) Log.d(TAG, "Didn't build update, possibly because changedEventIds=" +
changedEventIds.toString());
return;
}
AppWidgetManager gm = AppWidgetManager.getInstance(context);
if (appWidgetIds != null && appWidgetIds.length > 0) {
gm.updateAppWidget(appWidgetIds, views);
} else {
ComponentName thisWidget = CalendarAppWidgetProvider.getComponentName(context);
gm.updateAppWidget(thisWidget, views);
}
// Schedule an alarm to wake ourselves up for the next update. We also cancel
// all existing wake-ups because PendingIntents don't match against extras.
// If no next-update calculated, or bad trigger time in past, schedule
// update about six hours from now.
if (triggerTime == -1 || triggerTime < now) {
if (LOGD) Log.w(TAG, "Encountered bad trigger time " +
formatDebugTime(triggerTime, now));
triggerTime = now + UPDATE_NO_EVENTS;
}
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingUpdate = CalendarAppWidgetProvider.getUpdateIntent(context);
am.cancel(pendingUpdate);
am.set(AlarmManager.RTC, triggerTime, pendingUpdate);
if (LOGD) Log.d(TAG, "Scheduled next update at " + formatDebugTime(triggerTime, now));
}
/**
* Format given time for debugging output.
*
* @param unixTime Target time to report.
* @param now Current system time from {@link System#currentTimeMillis()}
* for calculating time difference.
*/
static private String formatDebugTime(long unixTime, long now) {
Time time = new Time();
time.set(unixTime);
long delta = unixTime - now;
if (delta > DateUtils.MINUTE_IN_MILLIS) {
delta /= DateUtils.MINUTE_IN_MILLIS;
return String.format("[%d] %s (%+d mins)", unixTime, time.format("%H:%M:%S"), delta);
} else {
delta /= DateUtils.SECOND_IN_MILLIS;
return String.format("[%d] %s (%+d secs)", unixTime, time.format("%H:%M:%S"), delta);
}
}
/**
* Convert given UTC time into current local time.
*
* @param recycle Time object to recycle, otherwise null.
* @param utcTime Time to convert, in UTC.
*/
static private long convertUtcToLocal(Time recycle, long utcTime) {
if (recycle == null) {
recycle = new Time();
}
recycle.timezone = Time.TIMEZONE_UTC;
recycle.set(utcTime);
recycle.timezone = TimeZone.getDefault().getID();
return recycle.normalize(true);
}
/**
* Figure out the next time we should push widget updates, usually the time
* calculated by {@link #getEventFlip(Cursor, long, long, boolean)}.
*
* @param cursor Valid cursor on {@link Instances#CONTENT_URI}
* @param events {@link MarkedEvents} parsed from the cursor
*/
private long calculateUpdateTime(Cursor cursor, MarkedEvents events) {
long result = -1;
if (!events.markedIds.isEmpty()) {
cursor.moveToPosition(events.markedIds.get(0));
long start = cursor.getLong(INDEX_BEGIN);
long end = cursor.getLong(INDEX_END);
boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
// Adjust all-day times into local timezone
if (allDay) {
final Time recycle = new Time();
start = convertUtcToLocal(recycle, start);
end = convertUtcToLocal(recycle, end);
}
result = getEventFlip(cursor, start, end, allDay);
// Make sure an update happens at midnight or earlier
long midnight = getNextMidnightTimeMillis();
result = Math.min(midnight, result);
}
return result;
}
private long getNextMidnightTimeMillis() {
Time time = new Time();
time.setToNow();
time.monthDay++;
time.hour = 0;
time.minute = 0;
time.second = 0;
long midnight = time.normalize(true);
return midnight;
}
/**
* Calculate flipping point for the given event; when we should hide this
* event and show the next one. This is defined as the end time of the
* event.
*
* @param start Event start time in local timezone.
* @param end Event end time in local timezone.
*/
static private long getEventFlip(Cursor cursor, long start, long end, boolean allDay) {
return end;
}
/**
* Set visibility of various widget components if there are events, or if no
* events were found.
*
* @param views Set of {@link RemoteViews} to apply visibility.
* @param noEvents True if no events found, otherwise false.
*/
private void setNoEventsVisible(RemoteViews views, boolean noEvents) {
views.setViewVisibility(R.id.no_events, noEvents ? View.VISIBLE : View.GONE);
views.setViewVisibility(R.id.page_flipper, View.GONE);
views.setViewVisibility(R.id.single_page, View.GONE);
}
/**
* Build a set of {@link RemoteViews} that describes how to update any
* widget for a specific event instance.
*
* @param cursor Valid cursor on {@link Instances#CONTENT_URI}
* @param events {@link MarkedEvents} parsed from the cursor
*/
private RemoteViews getAppWidgetUpdate(Context context, Cursor cursor, MarkedEvents events) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
setNoEventsVisible(views, false);
long currentTime = System.currentTimeMillis();
CalendarAppWidgetModel model = buildAppWidgetModel(context, cursor, events, currentTime);
applyModelToView(context, model, views);
// Clicking on the widget launches Calendar
long startTime = Math.max(currentTime, events.firstTime);
PendingIntent pendingIntent = getLaunchPendingIntent(context, startTime);
views.setOnClickPendingIntent(R.id.appwidget, pendingIntent);
return views;
}
private void applyModelToView(Context context, CalendarAppWidgetModel model,
RemoteViews views) {
views.setTextViewText(R.id.day_of_week, model.dayOfWeek);
views.setTextViewText(R.id.day_of_month, model.dayOfMonth);
views.setViewVisibility(R.id.no_events, model.visibNoEvents);
// Make sure we have a clean slate first
views.removeAllViews(R.id.page_flipper);
views.removeAllViews(R.id.single_page);
// If we don't have any events, just hide the relevant views and return
if (model.visibNoEvents != View.GONE) {
views.setViewVisibility(R.id.page_flipper, View.GONE);
views.setViewVisibility(R.id.single_page, View.GONE);
return;
}
// Luckily, length of this array is guaranteed to be even
int pages = model.eventInfos.length / 2;
// We use a separate container for the case of only one page to prevent
// a ViewFlipper from repeatedly animating one view
if (pages > 1) {
views.setViewVisibility(R.id.page_flipper, View.VISIBLE);
views.setViewVisibility(R.id.single_page, View.GONE);
} else {
views.setViewVisibility(R.id.single_page, View.VISIBLE);
views.setViewVisibility(R.id.page_flipper, View.GONE);
}
// Iterate two at a time through the events and populate the views
for (int i = 0; i < model.eventInfos.length; i += 2) {
RemoteViews pageViews = new RemoteViews(context.getPackageName(),
R.layout.appwidget_page);
EventInfo e1 = model.eventInfos[i];
EventInfo e2 = model.eventInfos[i + 1];
updateTextView(pageViews, R.id.when1, e1.visibWhen, e1.when);
updateTextView(pageViews, R.id.where1, e1.visibWhere, e1.where);
updateTextView(pageViews, R.id.title1, e1.visibTitle, e1.title);
updateTextView(pageViews, R.id.when2, e2.visibWhen, e2.when);
updateTextView(pageViews, R.id.where2, e2.visibWhere, e2.where);
updateTextView(pageViews, R.id.title2, e2.visibTitle, e2.title);
if (pages > 1) {
views.addView(R.id.page_flipper, pageViews);
updateTextView(pageViews, R.id.page_count, View.VISIBLE,
makePageCount((i / 2) + 1, pages));
} else {
views.addView(R.id.single_page, pageViews);
}
}
}
static String makePageCount(int current, int total) {
return Integer.toString(current) + " / " + Integer.toString(total);
}
static void updateTextView(RemoteViews views, int id, int visibility, String string) {
views.setViewVisibility(id, visibility);
if (visibility == View.VISIBLE) {
views.setTextViewText(id, string);
}
}
static CalendarAppWidgetModel buildAppWidgetModel(Context context, Cursor cursor,
MarkedEvents events, long currentTime) {
int eventCount = events.markedIds.size();
CalendarAppWidgetModel model = new CalendarAppWidgetModel(eventCount);
Time time = new Time();
time.set(currentTime);
time.monthDay++;
time.hour = 0;
time.minute = 0;
time.second = 0;
long startOfNextDay = time.normalize(true);
time.set(currentTime);
// Calendar header
String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1, DateUtils.LENGTH_MEDIUM)
.toUpperCase();
model.dayOfWeek = dayOfWeek;
model.dayOfMonth = Integer.toString(time.monthDay);
int i = 0;
for (Integer id : events.markedIds) {
populateEvent(context, cursor, id, model, time, i, true, startOfNextDay, currentTime);
i++;
}
return model;
}
/**
* Pulls the information for a single event from the cursor and populates
* the corresponding model object with the data.
*
* @param context a Context to use for accessing resources
* @param cursor the cursor to retrieve the data from
* @param rowId the ID of the row to retrieve
* @param model the model object to populate
* @param recycle a Time instance to recycle
* @param eventIndex which event index in the model to populate
* @param showTitleLocation whether or not to show the title and location
* @param startOfNextDay the beginning of the next day
* @param currentTime the current time
*/
static private void populateEvent(Context context, Cursor cursor, int rowId,
CalendarAppWidgetModel model, Time recycle, int eventIndex,
boolean showTitleLocation, long startOfNextDay, long currentTime) {
cursor.moveToPosition(rowId);
// When
boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
long start = cursor.getLong(INDEX_BEGIN);
long end = cursor.getLong(INDEX_END);
if (allDay) {
start = convertUtcToLocal(recycle, start);
end = convertUtcToLocal(recycle, end);
}
boolean eventIsInProgress = start <= currentTime && end > currentTime;
boolean eventIsToday = start < startOfNextDay;
boolean eventIsTomorrow = !eventIsToday && !eventIsInProgress
&& (start < (startOfNextDay + DateUtils.DAY_IN_MILLIS));
// Compute a human-readable string for the start time of the event
String whenString;
if (eventIsInProgress && allDay) {
// All day events for the current day display as just "Today"
whenString = context.getString(R.string.today);
} else if (eventIsTomorrow && allDay) {
// All day events for the next day display as just "Tomorrow"
whenString = context.getString(R.string.tomorrow);
} else {
int flags = DateUtils.FORMAT_ABBREV_ALL;
if (allDay) {
flags |= DateUtils.FORMAT_UTC;
} else {
flags |= DateUtils.FORMAT_SHOW_TIME;
if (DateFormat.is24HourFormat(context)) {
flags |= DateUtils.FORMAT_24HOUR;
}
}
// Show day of the week if not today or tomorrow
if (!eventIsTomorrow && !eventIsToday) {
flags |= DateUtils.FORMAT_SHOW_WEEKDAY;
}
whenString = DateUtils.formatDateRange(context, start, start, flags);
if (eventIsTomorrow) {
whenString += (", ");
whenString += context.getString(R.string.tomorrow);
} else if (eventIsInProgress) {
whenString += " (";
whenString += context.getString(R.string.in_progress);
whenString += ")";
}
}
model.eventInfos[eventIndex].when = whenString;
model.eventInfos[eventIndex].visibWhen = View.VISIBLE;
if (showTitleLocation) {
// What
String titleString = cursor.getString(INDEX_TITLE);
if (TextUtils.isEmpty(titleString)) {
titleString = context.getString(R.string.no_title_label);
}
model.eventInfos[eventIndex].title = titleString;
model.eventInfos[eventIndex].visibTitle = View.VISIBLE;
// Where
String whereString = cursor.getString(INDEX_EVENT_LOCATION);
if (!TextUtils.isEmpty(whereString)) {
model.eventInfos[eventIndex].visibWhere = View.VISIBLE;
model.eventInfos[eventIndex].where = whereString;
} else {
model.eventInfos[eventIndex].visibWhere = View.GONE;
}
if (LOGD) Log.d(TAG, " Title:" + titleString + " Where:" + whereString);
}
}
/**
* Build a set of {@link RemoteViews} that describes an error state.
*/
private RemoteViews getAppWidgetNoEvents(Context context) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget);
setNoEventsVisible(views, true);
// Calendar header
Time time = new Time();
time.setToNow();
String dayOfWeek = DateUtils.getDayOfWeekString(time.weekDay + 1, DateUtils.LENGTH_MEDIUM)
.toUpperCase();
views.setTextViewText(R.id.day_of_week, dayOfWeek);
views.setTextViewText(R.id.day_of_month, Integer.toString(time.monthDay));
// Clicking on widget launches the agenda view in Calendar
PendingIntent pendingIntent = getLaunchPendingIntent(context, 0);
views.setOnClickPendingIntent(R.id.appwidget, pendingIntent);
return views;
}
/**
* Build a {@link PendingIntent} to launch the Calendar app. This correctly
* sets action, category, and flags so that we don't duplicate tasks when
* Calendar was also launched from a normal desktop icon.
* @param goToTime time that calendar should take the user to
*/
private PendingIntent getLaunchPendingIntent(Context context, long goToTime) {
Intent launchIntent = new Intent();
String dataString = "content://com.android.calendar/time";
launchIntent.setAction(Intent.ACTION_VIEW);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (goToTime != 0) {
launchIntent.putExtra(KEY_DETAIL_VIEW, true);
dataString += "/" + goToTime;
}
Uri data = Uri.parse(dataString);
launchIntent.setData(data);
return PendingIntent.getActivity(context, 0 /* no requestCode */,
launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
static class MarkedEvents {
/**
* The row IDs of all events marked for display
*/
List<Integer> markedIds = new ArrayList<Integer>(10);
/**
* The start time of the first marked event
*/
long firstTime = -1;
/** The number of events currently in progress */
int inProgressCount = 0; // Number of events with same start time as the primary evt.
/** The start time of the next upcoming event */
long primaryTime = -1;
/**
* The number of events that share the same start time as the next
* upcoming event
*/
int primaryCount = 0; // Number of events with same start time as the secondary evt.
/** The start time of the next next upcoming event */
long secondaryTime = 1;
/**
* The number of events that share the same start time as the next next
* upcoming event.
*/
int secondaryCount = 0;
boolean watchFound = false;
}
/**
* Walk the given instances cursor and build a list of marked events to be
* used when updating the widget. This structure is also used to check if
* updates are needed.
*
* @param cursor Valid cursor across {@link Instances#CONTENT_URI}.
* @param watchEventIds Specific events to watch for, setting
* {@link MarkedEvents#watchFound} if found during marking.
* @param now Current system time to use for this update, possibly from
* {@link System#currentTimeMillis()}
*/
@VisibleForTesting
static MarkedEvents buildMarkedEvents(Cursor cursor, Set<Long> watchEventIds, long now) {
MarkedEvents events = new MarkedEvents();
final Time recycle = new Time();
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
int row = cursor.getPosition();
long eventId = cursor.getLong(INDEX_EVENT_ID);
long start = cursor.getLong(INDEX_BEGIN);
long end = cursor.getLong(INDEX_END);
boolean allDay = cursor.getInt(INDEX_ALL_DAY) != 0;
if (LOGD) {
Log.d(TAG, "Row #" + row + " allDay:" + allDay + " start:" + start + " end:" + end
+ " eventId:" + eventId);
}
// Adjust all-day times into local timezone
if (allDay) {
start = convertUtcToLocal(recycle, start);
end = convertUtcToLocal(recycle, end);
}
boolean inProgress = now < end && now > start;
// Skip events that have already passed their flip times
long eventFlip = getEventFlip(cursor, start, end, allDay);
if (LOGD) Log.d(TAG, "Calculated flip time " + formatDebugTime(eventFlip, now));
if (eventFlip < now) {
continue;
}
// Mark if we've encountered the watched event
if (watchEventIds != null && watchEventIds.contains(eventId)) {
events.watchFound = true;
}
/* Scan through the events with the following logic:
* Rule #1 Show A) all the events that are in progress including
* all day events and B) the next upcoming event and any events
* with the same start time.
*
* Rule #2 If there are no events in progress, show A) the next
* upcoming event and B) any events with the same start time.
*
* Rule #3 If no events start at the same time at A in rule 2,
* show A) the next upcoming event and B) the following upcoming
* event + any events with the same start time.
*/
if (inProgress) {
// events for part A of Rule #1
events.markedIds.add(row);
events.inProgressCount++;
if (events.firstTime == -1) {
events.firstTime = start;
}
} else {
if (events.primaryCount == 0) {
// first upcoming event
events.markedIds.add(row);
events.primaryTime = start;
events.primaryCount++;
if (events.firstTime == -1) {
events.firstTime = start;
}
} else if (events.primaryTime == start) {
// any events with same start time as first upcoming event
events.markedIds.add(row);
events.primaryCount++;
} else if (events.markedIds.size() == 1) {
// only one upcoming event, so we take the next upcoming
events.markedIds.add(row);
events.secondaryTime = start;
events.secondaryCount++;
} else if (events.secondaryCount > 0
&& events.secondaryTime == start) {
// any events with same start time as next upcoming
events.markedIds.add(row);
events.secondaryCount++;
} else {
// looks like we're done
break;
}
}
}
return events;
}
/**
* Query across all calendars for upcoming event instances from now until
* some time in the future.
*
* @param resolver {@link ContentResolver} to use when querying
* {@link Instances#CONTENT_URI}.
* @param searchDuration Distance into the future to look for event
* instances, in milliseconds.
* @param now Current system time to use for this update, possibly from
* {@link System#currentTimeMillis()}.
*/
private Cursor getUpcomingInstancesCursor(ContentResolver resolver,
long searchDuration, long now) {
// Search for events from now until some time in the future
long end = now + searchDuration;
Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI,
String.format("%d/%d", now, end));
return resolver.query(uri, EVENT_PROJECTION, EVENT_SELECTION, null,
EVENT_SORT_ORDER);
}
}

View File

@ -0,0 +1,505 @@
/*
**
** Copyright 2010, 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 com.android.calendar;
import com.android.calendar.CalendarAppWidgetService.MarkedEvents;
import android.database.MatrixCursor;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.format.DateUtils;
import android.view.View;
// adb shell am instrument -w -e class com.android.providers.calendar.CalendarAppWidgetServiceTest
// com.android.providers.calendar.tests/android.test.InstrumentationTestRunner
public class CalendarAppWidgetServiceTest extends AndroidTestCase {
private static final String TAG = "CalendarAppWidgetService";
final long now = 1262340000000L; // Fri Jan 01 2010 02:00:00 GMT-0800 (PST)
final long ONE_MINUTE = 60000;
final long ONE_HOUR = 60 * ONE_MINUTE;
final long HALF_HOUR = ONE_HOUR / 2;
final long TWO_HOURS = ONE_HOUR * 2;
final String title = "Title";
final String location = "Location";
// TODO Disabled test since this CalendarAppWidgetModel is not used for the no event case
//
// @SmallTest
// public void testGetAppWidgetModel_noEvents() throws Exception {
// // Input
// MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
//
// // Expected Output
// CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
// expected.visibNoEvents = View.VISIBLE;
//
// // Test
// long now = 1270000000000L;
// MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
// CalendarAppWidgetModel actual = CalendarAppWidgetService.getAppWidgetModel(
// getTestContext(), cursor, events, now);
//
// assertEquals(expected.toString(), actual.toString());
// }
@SmallTest
public void testGetAppWidgetModel_1Event() throws Exception {
CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
// Input
// allDay, begin, end, title, location, eventId
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title, location, 0));
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[0].visibWhen = View.VISIBLE;
expected.eventInfos[0].visibWhere = View.VISIBLE;
expected.eventInfos[0].visibTitle = View.VISIBLE;
expected.eventInfos[0].when = "3am";
expected.eventInfos[0].where = location;
expected.eventInfos[0].title = title;
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_2StaggeredEvents() throws Exception {
CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
long tomorrow = now + DateUtils.DAY_IN_MILLIS;
long sunday = tomorrow + DateUtils.DAY_IN_MILLIS;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[0].visibWhen = View.VISIBLE;
expected.eventInfos[0].visibWhere = View.VISIBLE;
expected.eventInfos[0].visibTitle = View.VISIBLE;
expected.eventInfos[0].when = "2am, Tomorrow";
expected.eventInfos[0].where = location + i;
expected.eventInfos[0].title = title + i;
++i;
expected.eventInfos[1].visibWhen = View.VISIBLE;
expected.eventInfos[1].visibWhere = View.VISIBLE;
expected.eventInfos[1].visibTitle = View.VISIBLE;
expected.eventInfos[1].when = "2am, Sun";
expected.eventInfos[1].where = location + i;
expected.eventInfos[1].title = title + i;
// Input
// allDay, begin, end, title, location, eventId
i = 0;
cursor.addRow(getRow(0, tomorrow, tomorrow + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, sunday, sunday + TWO_HOURS, title + i, location + i, 0));
++i;
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
// Secondary test - Add two more afterwards
cursor.addRow(getRow(0, sunday + ONE_HOUR, sunday + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, sunday + ONE_HOUR, sunday + TWO_HOURS, title + i, location + i, 0));
// Test again
events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_2SameStartTimeEvents() throws Exception {
CalendarAppWidgetModel expected = new CalendarAppWidgetModel();
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[0].visibWhen = View.VISIBLE;
expected.eventInfos[0].visibWhere = View.VISIBLE;
expected.eventInfos[0].visibTitle = View.VISIBLE;
expected.eventInfos[0].when = "3am";
expected.eventInfos[0].where = location + i;
expected.eventInfos[0].title = title + i;
++i;
expected.eventInfos[1].visibWhen = View.VISIBLE;
expected.eventInfos[1].visibWhere = View.VISIBLE;
expected.eventInfos[1].visibTitle = View.VISIBLE;
expected.eventInfos[1].when = "3am";
expected.eventInfos[1].where = location + i;
expected.eventInfos[1].title = title + i;
// Input
// allDay, begin, end, title, location, eventId
i = 0;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
// Secondary test - Add two more afterwards
cursor.addRow(getRow(0, now + TWO_HOURS, now + TWO_HOURS + 1, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + TWO_HOURS, now + TWO_HOURS + 1, title + i, location + i, 0));
// Test again
events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_1EventThen2SameStartTimeEvents() throws Exception {
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(3);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
// Input
int i = 0;
// allDay, begin, end, title, location, eventId
cursor.addRow(getRow(0, now, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
i = 0;
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "2am (in progress)";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_3SameStartTimeEvents() throws Exception {
final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(3);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
// Input
// allDay, begin, end, title, location, eventId
i = 0;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
// Secondary test - Add one more afterwards
cursor.addRow(getRow(0, now + TWO_HOURS, now + TWO_HOURS + 1, title + i, location + i, 0));
// Test again, nothing should have changed, same expected result
events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
actual = CalendarAppWidgetService.buildAppWidgetModel(getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_2InProgress2After() throws Exception {
final long now = 1262340000000L + HALF_HOUR; // Fri Jan 01 2010 01:30:00 GMT-0700 (PDT)
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(4);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "2am (in progress)";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "2am (in progress)";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "4:30am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "4:30am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
// Input
// allDay, begin, end, title, location, eventId
i = 0;
cursor.addRow(getRow(0, now - HALF_HOUR, now + HALF_HOUR, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now - HALF_HOUR, now + HALF_HOUR, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + TWO_HOURS, now + 3 * ONE_HOUR, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + TWO_HOURS, now + 4 * ONE_HOUR, title + i, location + i, 0));
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_AllDayEventToday() throws Exception {
final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "Today";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i = 0;
cursor.addRow(getRow(1, 1262304000000L, 1262390400000L, title + i, location + i, 0));
++i;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_AllDayEventTomorrow() throws Exception {
final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "Tomorrow";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i = 0;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(1, 1262390400000L, 1262476800000L, title + i, location + i, 0));
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
@SmallTest
public void testGetAppWidgetModel_AllDayEventLater() throws Exception {
final long now = 1262340000000L; // Fri Jan 01 2010 01:00:00 GMT-0700 (PDT)
CalendarAppWidgetModel expected = new CalendarAppWidgetModel(2);
MatrixCursor cursor = new MatrixCursor(CalendarAppWidgetService.EVENT_PROJECTION, 0);
int i = 0;
// Expected Output
expected.dayOfMonth = "1";
expected.dayOfWeek = "FRI";
expected.visibNoEvents = View.GONE;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "3am";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i++;
expected.eventInfos[i].visibWhen = View.VISIBLE;
expected.eventInfos[i].visibWhere = View.VISIBLE;
expected.eventInfos[i].visibTitle = View.VISIBLE;
expected.eventInfos[i].when = "Sun";
expected.eventInfos[i].where = location + i;
expected.eventInfos[i].title = title + i;
i = 0;
cursor.addRow(getRow(0, now + ONE_HOUR, now + TWO_HOURS, title + i, location + i, 0));
++i;
cursor.addRow(getRow(1, 1262476800000L, 1262563200000L, title + i, location + i, 0));
// Test
MarkedEvents events = CalendarAppWidgetService.buildMarkedEvents(cursor, null, now);
CalendarAppWidgetModel actual = CalendarAppWidgetService.buildAppWidgetModel(
getContext(), cursor, events, now);
assertEquals(expected.toString(), actual.toString());
}
private Object[] getRow(int allDay, long begin, long end, String title, String location,
long eventId) {
Object[] row = new Object[CalendarAppWidgetService.EVENT_PROJECTION.length];
row[CalendarAppWidgetService.INDEX_ALL_DAY] = new Integer(allDay);
row[CalendarAppWidgetService.INDEX_BEGIN] = new Long(begin);
row[CalendarAppWidgetService.INDEX_END] = new Long(end);
row[CalendarAppWidgetService.INDEX_TITLE] = new String(title);
row[CalendarAppWidgetService.INDEX_EVENT_LOCATION] = new String(location);
row[CalendarAppWidgetService.INDEX_EVENT_ID] = new Long(eventId);
return row;
}
}