CalendarAppWidgetService.java revision c46c2dc5dbec57616d799b1d0290d7c827b48d0c
1bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang/* 2bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Copyright (C) 2009 The Android Open Source Project 3bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 4bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Licensed under the Apache License, Version 2.0 (the "License"); 5bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * you may not use this file except in compliance with the License. 6bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * You may obtain a copy of the License at 7bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 8bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * http://www.apache.org/licenses/LICENSE-2.0 9bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * 10bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * Unless required by applicable law or agreed to in writing, software 11bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * distributed under the License is distributed on an "AS IS" BASIS, 12bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * See the License for the specific language governing permissions and 14bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang * limitations under the License. 15bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang */ 16bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 179a3cb14e28536e4133dddbe952f47189fe344ec1Mason Tangpackage com.android.calendar.widget; 18bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 19a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport com.google.common.annotations.VisibleForTesting; 20a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa 214143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.R; 224143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.Utils; 234143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.DayInfo; 244143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.EventInfo; 254143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawaimport com.android.calendar.widget.CalendarAppWidgetModel.RowInfo; 264143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa 27c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.appwidget.AppWidgetManager; 28c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.BroadcastReceiver; 29bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.ContentResolver; 30bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Context; 31c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.CursorLoader; 32bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.content.Intent; 33c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.IntentFilter; 34c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.content.Loader; 35a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawaimport android.content.res.Resources; 36bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.database.Cursor; 3747d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.database.MatrixCursor; 38bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.net.Uri; 39c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.os.Handler; 40c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErikimport android.provider.Calendar; 41bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Attendees; 42bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Calendars; 43bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.provider.Calendar.Instances; 44bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.DateUtils; 45bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.text.format.Time; 46bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.util.Log; 47bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.view.View; 48bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tangimport android.widget.RemoteViews; 4947d40324272ae39af0872bf5cbf27e1800478021Mason Tangimport android.widget.RemoteViewsService; 5047d40324272ae39af0872bf5cbf27e1800478021Mason Tang 51bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 5247d40324272ae39af0872bf5cbf27e1800478021Mason Tangpublic class CalendarAppWidgetService extends RemoteViewsService { 530c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static final String TAG = "CalendarWidget"; 54bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int EVENT_MIN_COUNT = 20; 563ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int EVENT_MAX_COUNT = 503; 57c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // Minimum delay between queries on the database for widget updates in ms 58c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik static final int WIDGET_UPDATE_THROTTLE = 500; 5947d40324272ae39af0872bf5cbf27e1800478021Mason Tang 60bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SORT_ORDER = Instances.START_DAY + " ASC, " 61bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.START_MINUTE + " ASC, " + Instances.END_DAY + " ASC, " 623ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang + Instances.END_MINUTE + " ASC LIMIT " + EVENT_MAX_COUNT; 63bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 64bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang // TODO can't use parameter here because provider is dropping them 65bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang private static final String EVENT_SELECTION = Calendars.SELECTED + "=1 AND " 66bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang + Instances.SELF_ATTENDEE_STATUS + "!=" + Attendees.ATTENDEE_STATUS_DECLINED; 67bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 68bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final String[] EVENT_PROJECTION = new String[] { 69bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.ALL_DAY, 70bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.BEGIN, 71bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.END, 72bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.TITLE, 73bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_LOCATION, 74bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang Instances.EVENT_ID, 753ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Instances.START_DAY, 76a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa Instances.END_DAY, 77a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa Instances.COLOR 78bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang }; 79bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 80bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_ALL_DAY = 0; 81bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_BEGIN = 1; 82bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_END = 2; 83bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_TITLE = 3; 84bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_LOCATION = 4; 85bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang static final int INDEX_EVENT_ID = 5; 863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_START_DAY = 6; 873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int INDEX_END_DAY = 7; 88a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa static final int INDEX_COLOR = 8; 89bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static final int MAX_DAYS = 7; 913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang private static final long SEARCH_DURATION = MAX_DAYS * DateUtils.DAY_IN_MILLIS; 93bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 940c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa /** 950c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Update interval used when no next-update calculated, or bad trigger time in past. 960c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Unit: milliseconds. 970c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa */ 980c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static final long UPDATE_TIME_NO_EVENTS = DateUtils.HOUR_IN_MILLIS * 6; 99bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 10047d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 10147d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViewsFactory onGetViewFactory(Intent intent) { 10247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return new CalendarFactory(getApplicationContext(), intent); 103bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 104bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 105c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik protected static class CalendarFactory extends BroadcastReceiver implements 106c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik RemoteViewsService.RemoteViewsFactory, Loader.OnLoadCompleteListener<Cursor> { 107f9df037f350fad73659307ba05f230d2db69051aMason Tang private static final boolean LOGD = false; 1080c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa 1090c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // Suppress unnecessary logging about update time. Need to be static as this object is 1100c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // re-instanciated frequently. 1110c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // TODO: It seems loadData() is called via onCreate() four times, which should mean 1120c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // unnecessary CalendarFactory object is created and dropped. It is not efficient. 1130c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa private static long sLastUpdateTime = UPDATE_TIME_NO_EVENTS; 1140c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa 11547d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Context mContext; 116a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa private Resources mResources; 11747d40324272ae39af0872bf5cbf27e1800478021Mason Tang private CalendarAppWidgetModel mModel; 11847d40324272ae39af0872bf5cbf27e1800478021Mason Tang private Cursor mCursor; 119c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private CursorLoader mLoader; 120c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private Handler mHandler = new Handler(); 121c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private int mAppWidgetId; 122c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 123c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private Runnable mTimezoneChanged = new Runnable() { 124c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 125c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void run() { 126c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (mLoader != null) { 127c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.forceLoad(); 128c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 129c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 130c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik }; 131c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 132c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private Runnable mUpdateLoader = new Runnable() { 133c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 134c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void run() { 135c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (mLoader != null) { 136c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Uri uri = createLoaderUri(); 137c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.setUri(uri); 138c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.forceLoad(); 139c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 140c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 141c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik }; 14247d40324272ae39af0872bf5cbf27e1800478021Mason Tang 14347d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected CalendarFactory(Context context, Intent intent) { 14447d40324272ae39af0872bf5cbf27e1800478021Mason Tang mContext = context; 145a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa mResources = context.getResources(); 146c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mAppWidgetId = intent.getIntExtra( 147c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 148bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 149bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 15047d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 15147d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onCreate() { 152c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik initLoader(); 153bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 154bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 15547d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 156d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung public void onDataSetChanged() { 157d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung } 158d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung 159d4b2dbb63039e8e63ef92b4b984aae9c68e1a3b5Winson Chung @Override 16047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public void onDestroy() { 16147d40324272ae39af0872bf5cbf27e1800478021Mason Tang mCursor.close(); 162c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.reset(); 163c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mContext.unregisterReceiver(this); 164bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 165bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 16647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 16747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getLoadingView() { 16847d40324272ae39af0872bf5cbf27e1800478021Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 16947d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_loading); 17047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 171bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 172bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 17347d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 17447d40324272ae39af0872bf5cbf27e1800478021Mason Tang public RemoteViews getViewAt(int position) { 17547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // we use getCount here so that it doesn't return null when empty 17647d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (position < 0 || position >= getCount()) { 17747d40324272ae39af0872bf5cbf27e1800478021Mason Tang return null; 17847d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 179bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 180c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (mModel == null || mModel.mEventInfos.isEmpty() || mModel.mRowInfos.isEmpty()) { 1813ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 1823ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_no_events); 183bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan final Intent intent = CalendarAppWidgetProvider.getLaunchFillInIntent(0, 0, 0); 1844143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa views.setOnClickFillInIntent(R.id.appwidget_no_events, intent); 1853ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 1863ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 1873ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 1883ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RowInfo rowInfo = mModel.mRowInfos.get(position); 1893ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (rowInfo.mType == RowInfo.TYPE_DAY) { 1903ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang RemoteViews views = new RemoteViews(mContext.getPackageName(), 1913ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang R.layout.appwidget_day); 1923ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang DayInfo dayInfo = mModel.mDayInfos.get(rowInfo.mIndex); 1933ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang updateTextView(views, R.id.date, View.VISIBLE, dayInfo.mDayLabel); 1943ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return views; 1953ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 196a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa final RemoteViews views = new RemoteViews(mContext.getPackageName(), 19747d40324272ae39af0872bf5cbf27e1800478021Mason Tang R.layout.appwidget_row); 198a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa final EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex); 199a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa 200a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa final long now = System.currentTimeMillis(); 201a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa if (!eventInfo.allDay && eventInfo.start <= now && now <= eventInfo.end) { 202a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa views.setInt(R.id.appwidget_row, "setBackgroundColor", 203a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa mResources.getColor(R.color.appwidget_row_in_progress)); 204ffaeace621183dfe8770471a30b2f1138aac5f86Daisuke Miyakawa } else { 205309c34fcce4912a9c6f1c0a39c090cebf61296beMichael Chan views.setInt(R.id.appwidget_row, "setBackgroundResource", 206309c34fcce4912a9c6f1c0a39c090cebf61296beMichael Chan R.drawable.bg_event_cal_widget_holo); 207a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa } 208a07cedd5af736339a7dd6166970311c487366500Daisuke Miyakawa 209a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.when, eventInfo.visibWhen, eventInfo.when); 210a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.where, eventInfo.visibWhere, eventInfo.where); 211a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa updateTextView(views, R.id.title, eventInfo.visibTitle, eventInfo.title); 212bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 213a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa views.setViewVisibility(R.id.color, View.VISIBLE); 214a71e0a520493532dbb7e9bfa164ab78e59e797a3Daisuke Miyakawa views.setInt(R.id.color, "setBackgroundColor", eventInfo.color); 215bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 2164143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa // An element in ListView. 217bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan final Intent fillInIntent = CalendarAppWidgetProvider.getLaunchFillInIntent( 218bdbf15078ad5efdf27c021d7aca8c8aa4693878cMichael Chan eventInfo.id, eventInfo.start, eventInfo.end); 2194143cfabb36f0c3adcad410a6eed29bc2b89d6e3Daisuke Miyakawa views.setOnClickFillInIntent(R.id.appwidget_row, fillInIntent); 22047d40324272ae39af0872bf5cbf27e1800478021Mason Tang return views; 22147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 222bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 223bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22447d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 22547d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getViewTypeCount() { 2263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return 4; 22747d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 228bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 22947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 23047d40324272ae39af0872bf5cbf27e1800478021Mason Tang public int getCount() { 23147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // if there are no events, we still return 1 to represent the "no 23247d40324272ae39af0872bf5cbf27e1800478021Mason Tang // events" view 2333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return Math.max(1, mModel.mRowInfos.size()); 23447d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 235bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 23647d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 23747d40324272ae39af0872bf5cbf27e1800478021Mason Tang public long getItemId(int position) { 238c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik RowInfo rowInfo = mModel.mRowInfos.get(position); 239c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (rowInfo.mType == RowInfo.TYPE_DAY) { 240c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return rowInfo.mIndex; 241c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 242c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik EventInfo eventInfo = mModel.mEventInfos.get(rowInfo.mIndex); 243c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long prime = 31; 244c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long result = 1; 245c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik result = prime * result + (int) (eventInfo.id ^ (eventInfo.id >>> 32)); 246c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik result = prime * result + (int) (eventInfo.start ^ (eventInfo.start >>> 32)); 247c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return result; 24847d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 249bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25047d40324272ae39af0872bf5cbf27e1800478021Mason Tang @Override 25147d40324272ae39af0872bf5cbf27e1800478021Mason Tang public boolean hasStableIds() { 25247d40324272ae39af0872bf5cbf27e1800478021Mason Tang return true; 253bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 254bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 25547d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 256c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * Query across all calendars for upcoming event instances from now 257c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * until some time in the future. Widen the time range that we query by 258c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * one day on each end so that we can catch all-day events. All-day 259c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * events are stored starting at midnight in UTC but should be included 260c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * in the list of events starting at midnight local time. This may fetch 261c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * more events than we actually want, so we filter them out later. 26247d40324272ae39af0872bf5cbf27e1800478021Mason Tang * 26347d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param resolver {@link ContentResolver} to use when querying 26447d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link Instances#CONTENT_URI}. 26547d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param searchDuration Distance into the future to look for event 26647d40324272ae39af0872bf5cbf27e1800478021Mason Tang * instances, in milliseconds. 26747d40324272ae39af0872bf5cbf27e1800478021Mason Tang * @param now Current system time to use for this update, possibly from 26847d40324272ae39af0872bf5cbf27e1800478021Mason Tang * {@link System#currentTimeMillis()}. 26947d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 270c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void initLoader() { 271c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (LOGD) 272c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Log.d(TAG, "Querying for widget events..."); 273c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik IntentFilter filter = new IntentFilter(); 274c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addAction(Intent.ACTION_PROVIDER_CHANGED); 275c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addDataScheme(ContentResolver.SCHEME_CONTENT); 276c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addDataAuthority(Calendar.AUTHORITY, null); 277c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mContext.registerReceiver(this, filter); 278c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 279c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter = new IntentFilter(); 280c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 281c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addAction(Intent.ACTION_TIME_CHANGED); 282c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik filter.addAction(Intent.ACTION_DATE_CHANGED); 283c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mContext.registerReceiver(this, filter); 284c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 28547d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Search for events from now until some time in the future 286c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Uri uri = createLoaderUri(); 287c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 288c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader = new CursorLoader( 289c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mContext, uri, EVENT_PROJECTION, EVENT_SELECTION, null, EVENT_SORT_ORDER); 290c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.setUpdateThrottle(WIDGET_UPDATE_THROTTLE); 291c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.startLoading(); 292c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mLoader.registerListener(mAppWidgetId, this); 293c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 294c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 295bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 296c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik /** 297c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * @return The uri for the loader 298c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik */ 299c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik private Uri createLoaderUri() { 300c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long now = System.currentTimeMillis(); 30147d40324272ae39af0872bf5cbf27e1800478021Mason Tang // Add a day on either side to catch all-day events 30247d40324272ae39af0872bf5cbf27e1800478021Mason Tang long begin = now - DateUtils.DAY_IN_MILLIS; 303c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long end = now + SEARCH_DURATION + DateUtils.DAY_IN_MILLIS; 304bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 305e0cb5ba884c52e9d36875fb4a9ebdf40a81cb642Michael Chan Uri uri = Uri.withAppendedPath(Instances.CONTENT_URI, Long.toString(begin) + "/" + end); 306c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik return uri; 30721a183875fbbfa54f5a2a87779888a5fb7d1af44Erik } 30821a183875fbbfa54f5a2a87779888a5fb7d1af44Erik 30947d40324272ae39af0872bf5cbf27e1800478021Mason Tang @VisibleForTesting 31047d40324272ae39af0872bf5cbf27e1800478021Mason Tang protected static CalendarAppWidgetModel buildAppWidgetModel( 31121a183875fbbfa54f5a2a87779888a5fb7d1af44Erik Context context, Cursor cursor, String timeZone) { 312ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan CalendarAppWidgetModel model = new CalendarAppWidgetModel(context, timeZone); 31321a183875fbbfa54f5a2a87779888a5fb7d1af44Erik model.buildFromCursor(cursor, timeZone); 31447d40324272ae39af0872bf5cbf27e1800478021Mason Tang return model; 315bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 316bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 31747d40324272ae39af0872bf5cbf27e1800478021Mason Tang /** 3180c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa * Calculates and returns the next time we should push widget updates. 31947d40324272ae39af0872bf5cbf27e1800478021Mason Tang */ 320ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan private long calculateUpdateTime(CalendarAppWidgetModel model, long now, String timeZone) { 3210c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // Make sure an update happens at midnight or earlier 322ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long minUpdateTime = getNextMidnightTimeMillis(timeZone); 3230c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa for (EventInfo event : model.mEventInfos) { 3240c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa final boolean allDay = event.allDay; 3250c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa final long start; 3260c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa final long end; 32747d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (allDay) { 3280c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // Adjust all-day times into local timezone 32947d40324272ae39af0872bf5cbf27e1800478021Mason Tang final Time recycle = new Time(); 3300c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa start = Utils.convertUtcToLocal(recycle, event.start); 3310c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa end = Utils.convertUtcToLocal(recycle, event.end); 3320c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa } else { 3330c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa start = event.start; 3340c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa end = event.end; 335bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 336bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 3370c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa // We want to update widget when we enter/exit time range of an event. 3380c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa if (now < start) { 3390c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa minUpdateTime = Math.min(minUpdateTime, start); 3400c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa } else if (now < end) { 3410c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa minUpdateTime = Math.min(minUpdateTime, end); 3420c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa } 343bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 3440c715c837a7ecc2cfda3a62d952f8dc7e79a39f3Daisuke Miyakawa return minUpdateTime; 345bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 346bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 347ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan private static long getNextMidnightTimeMillis(String timezone) { 34847d40324272ae39af0872bf5cbf27e1800478021Mason Tang Time time = new Time(); 34947d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.setToNow(); 35047d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.monthDay++; 35147d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.hour = 0; 35247d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.minute = 0; 35347d40324272ae39af0872bf5cbf27e1800478021Mason Tang time.second = 0; 354ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long midnightDeviceTz = time.normalize(true); 355ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan 356ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.timezone = timezone; 357ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.setToNow(); 358ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.monthDay++; 359ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.hour = 0; 360ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.minute = 0; 361ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan time.second = 0; 362ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan long midnightHomeTz = time.normalize(true); 363ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan 364ece2fbd8c2695910148ffa20fb46a508443fd034Michael Chan return Math.min(midnightDeviceTz, midnightHomeTz); 365bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 366bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 36747d40324272ae39af0872bf5cbf27e1800478021Mason Tang static void updateTextView(RemoteViews views, int id, int visibility, String string) { 36847d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setViewVisibility(id, visibility); 36947d40324272ae39af0872bf5cbf27e1800478021Mason Tang if (visibility == View.VISIBLE) { 37047d40324272ae39af0872bf5cbf27e1800478021Mason Tang views.setTextViewText(id, string); 37147d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 37247d40324272ae39af0872bf5cbf27e1800478021Mason Tang } 373c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 374c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik /* 375c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * (non-Javadoc) 376c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * @see 377c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * android.content.Loader.OnLoadCompleteListener#onLoadComplete(android 378c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik * .content.Loader, java.lang.Object) 379c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik */ 380c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 381c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) { 382c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // Copy it to a local static cursor. 383c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik MatrixCursor matrixCursor = Utils.matrixCursorFromCursor(cursor); 384c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik cursor.close(); 385c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 386c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik final long now = System.currentTimeMillis(); 387c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (mCursor != null) { 388c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mCursor.close(); 389c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 390c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mCursor = matrixCursor; 391c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik String tz = Utils.getTimeZone(mContext, mTimezoneChanged); 392c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mModel = buildAppWidgetModel(mContext, mCursor, tz); 393c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 394c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // Schedule an alarm to wake ourselves up for the next update. 395c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // We also cancel 396c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // all existing wake-ups because PendingIntents don't match 397c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // against extras. 398c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik long triggerTime = calculateUpdateTime(mModel, now, tz); 399c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 400c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // If no next-update calculated, or bad trigger time in past, 401c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // schedule 402c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik // update about six hours from now. 403c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik if (triggerTime < now) { 404c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik Log.w(TAG, "Encountered bad trigger time " + formatDebugTime(triggerTime, now)); 405c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik triggerTime = now + UPDATE_TIME_NO_EVENTS; 406c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 407c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 408c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mHandler.postDelayed(mUpdateLoader, triggerTime - now); 409c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 410c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik AppWidgetManager.getInstance(mContext).notifyAppWidgetViewDataChanged( 411c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mAppWidgetId, R.id.events_list); 412c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 413c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik 414c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik @Override 415c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik public void onReceive(Context context, Intent intent) { 416c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mHandler.removeCallbacks(mUpdateLoader); 417c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik mHandler.post(mUpdateLoader); 418c46c2dc5dbec57616d799b1d0290d7c827b48d0cRoboErik } 4193ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 420bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang 4213ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang /** 4223ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * Format given time for debugging output. 4233ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * 4243ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param unixTime Target time to report. 4253ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param now Current system time from {@link System#currentTimeMillis()} 4263ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * for calculating time difference. 4273ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang */ 4283ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang static String formatDebugTime(long unixTime, long now) { 4293ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang Time time = new Time(); 4303ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.set(unixTime); 4313ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 4323ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang long delta = unixTime - now; 4333ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (delta > DateUtils.MINUTE_IN_MILLIS) { 4343ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.MINUTE_IN_MILLIS; 4353ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d mins)", unixTime, 4363ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 4373ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } else { 4383ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang delta /= DateUtils.SECOND_IN_MILLIS; 4393ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return String.format("[%d] %s (%+d secs)", unixTime, 4403ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang time.format("%H:%M:%S"), delta); 441bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 442bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang } 443bb3f08abac01e3083d837e5e823b311e3c039e90Mason Tang} 444