Utils.java revision ede0fb1b3031ee75200f4030ecae8c69c9656cba
1146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project/* 2146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Copyright (C) 2006 The Android Open Source Project 3146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * 4146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 5146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * you may not use this file except in compliance with the License. 6146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * You may obtain a copy of the License at 7146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * 8146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 9146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * 10146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * Unless required by applicable law or agreed to in writing, software 11146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 12146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * See the License for the specific language governing permissions and 14146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * limitations under the License. 15146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */ 16146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 17146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpackage com.android.calendar; 18146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 19a27a886892fe3ec5edbc63c0b58e0a988623011aRoboErikimport static android.provider.CalendarContract.EXTRA_EVENT_BEGIN_TIME; 20e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan 21ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzsteinimport android.accounts.Account; 22d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chanimport android.app.Activity; 2350f10944eddba97e6730bbdc7545672d0eeec08cRoboErikimport android.app.SearchManager; 24c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelsonimport android.content.BroadcastReceiver; 25ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzsteinimport android.content.ContentResolver; 26146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Context; 27146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.content.Intent; 28c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelsonimport android.content.IntentFilter; 29e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chanimport android.content.SharedPreferences; 30dacfb663d63253880090151fde87bd9da9a59347Sara Tingimport android.content.pm.PackageManager; 31092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErikimport android.content.res.Resources; 32ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chanimport android.database.Cursor; 33a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErikimport android.database.MatrixCursor; 34bbb5b556996dfe989ef5267bfb3ba707bb740070RoboErikimport android.graphics.Color; 35c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelsonimport android.graphics.drawable.Drawable; 36c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelsonimport android.graphics.drawable.LayerDrawable; 371ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erikimport android.net.Uri; 38fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Tingimport android.os.Build; 39d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chanimport android.os.Bundle; 404bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelsonimport android.os.Handler; 41ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzsteinimport android.provider.CalendarContract.Calendars; 427e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport android.text.Spannable; 437e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport android.text.SpannableString; 447e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport android.text.Spanned; 45eb10fa8bee049e0052b5cb53dcfbdaccef9f2740Erikimport android.text.TextUtils; 4675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Tingimport android.text.format.DateFormat; 4729190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawaimport android.text.format.DateUtils; 48146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectimport android.text.format.Time; 497e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport android.text.style.URLSpan; 507e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport android.text.util.Linkify; 511ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erikimport android.util.Log; 5250f10944eddba97e6730bbdc7545672d0eeec08cRoboErikimport android.widget.SearchView; 5350f10944eddba97e6730bbdc7545672d0eeec08cRoboErik 54c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelsonimport com.android.calendar.CalendarController.ViewType; 55636269c7220a2b12e090cab43a91eb34922eb61fAndy McFaddenimport com.android.calendar.CalendarUtils.TimeZoneUtils; 56146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 5782400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelsonimport java.util.ArrayList; 58092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErikimport java.util.Arrays; 5956adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashiimport java.util.Calendar; 601427657d0bf7e69b831aa495828f67b45b69fd99Erikimport java.util.Formatter; 61092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErikimport java.util.HashMap; 6282400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelsonimport java.util.Iterator; 63e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chanimport java.util.LinkedHashSet; 64092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErikimport java.util.LinkedList; 651ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erikimport java.util.List; 6675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Tingimport java.util.Locale; 67ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chanimport java.util.Map; 68e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chanimport java.util.Set; 6975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Tingimport java.util.TimeZone; 7029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzsteinimport java.util.regex.Matcher; 717e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzsteinimport java.util.regex.Pattern; 7256adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi 73146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Projectpublic class Utils { 74092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static final boolean DEBUG = false; 753dc5e908a825b879978ba523d9099dc2255da9a5Erik private static final String TAG = "CalUtils"; 76ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting 77bed0275111ecc6c4a3a638f90a9bac13bee594f4Michael Chan // Set to 0 until we have UI to perform undo 78bed0275111ecc6c4a3a638f90a9bac13bee594f4Michael Chan public static final long UNDO_DELAY = 0; 79bed0275111ecc6c4a3a638f90a9bac13bee594f4Michael Chan 8079f228124de7d98146ca526d743436f6419e2365Erik // For recurring events which instances of the series are being modified 8179f228124de7d98146ca526d743436f6419e2365Erik public static final int MODIFY_UNINITIALIZED = 0; 8279f228124de7d98146ca526d743436f6419e2365Erik public static final int MODIFY_SELECTED = 1; 8379f228124de7d98146ca526d743436f6419e2365Erik public static final int MODIFY_ALL_FOLLOWING = 2; 8479f228124de7d98146ca526d743436f6419e2365Erik public static final int MODIFY_ALL = 3; 8579f228124de7d98146ca526d743436f6419e2365Erik 867b92da258a480284dcc15a518ea570072329a31dErik // When the edit event view finishes it passes back the appropriate exit 877b92da258a480284dcc15a518ea570072329a31dErik // code. 887b92da258a480284dcc15a518ea570072329a31dErik public static final int DONE_REVERT = 1 << 0; 897b92da258a480284dcc15a518ea570072329a31dErik public static final int DONE_SAVE = 1 << 1; 907b92da258a480284dcc15a518ea570072329a31dErik public static final int DONE_DELETE = 1 << 2; 917b92da258a480284dcc15a518ea570072329a31dErik // And should re run with DONE_EXIT if it should also leave the view, just 927b92da258a480284dcc15a518ea570072329a31dErik // exiting is identical to reverting 937b92da258a480284dcc15a518ea570072329a31dErik public static final int DONE_EXIT = 1 << 0; 9479f228124de7d98146ca526d743436f6419e2365Erik 952aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan public static final String OPEN_EMAIL_MARKER = " <"; 962aeb8d988aa4b65d3402374832613ab977e009dcMichael Chan public static final String CLOSE_EMAIL_MARKER = ">"; 97ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan 98d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan public static final String INTENT_KEY_DETAIL_VIEW = "DETAIL_VIEW"; 99d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan public static final String INTENT_KEY_VIEW_TYPE = "VIEW"; 100d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan public static final String INTENT_VALUE_VIEW_TYPE_DAY = "DAY"; 1014ba19dfbd8eccc5a22c3e064a9810336ef73f658RoboErik public static final String INTENT_KEY_HOME = "KEY_HOME"; 102275232dae58bb24e3360a779ada9d24601a99bcfErik 103981874e61ecf29a96a77601a3172b2503b6537eeErik public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3; 104e6109c584d27a6c25df1819a5c6ec444e789ce61Isaac Katzenelson public static final int DECLINED_EVENT_ALPHA = 0x66; 1054ecf0648e68bc9339a3d64745008a4617f7beddaIsaac Katzenelson public static final int DECLINED_EVENT_TEXT_ALPHA = 0xC0; 106981874e61ecf29a96a77601a3172b2503b6537eeErik 107f9411fefba89504c90a7bd8da5472cac538e916aMichael Chan private static final float SATURATION_ADJUST = 1.3f; 108f9411fefba89504c90a7bd8da5472cac538e916aMichael Chan private static final float INTENSITY_ADJUST = 0.8f; 1094acb2fd087308dea146b8b10f5278c59df387680RoboErik 110092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Defines used by the DNA generation code 111092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static final int DAY_IN_MINUTES = 60 * 24; 112092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static final int WEEK_IN_MINUTES = DAY_IN_MINUTES * 7; 113092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // The work day is being counted as 6am to 8pm 114092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static int WORK_DAY_MINUTES = 14 * 60; 115092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static int WORK_DAY_START_MINUTES = 6 * 60; 116092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static int WORK_DAY_END_MINUTES = 20 * 60; 117092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static int WORK_DAY_END_LENGTH = (24 * 60) - WORK_DAY_END_MINUTES; 118092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static int CONFLICT_COLOR = 0xFF000000; 119092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik static boolean mMinutesLoaded = false; 120092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 121a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik // The name of the shared preferences file. This name must be maintained for 122a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik // historical 123a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik // reasons, as it's what PreferenceManager assigned the first time the file 124a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik // was created. 12575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting static final String SHARED_PREFS_NAME = "com.android.calendar_preferences"; 12635d1362a75eac7cebbe9de23d08fea08c4aac817Erik 127e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan public static final String KEY_QUICK_RESPONSES = "preferences_quick_responses"; 128e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 1290b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan public static final String KEY_ALERTS_VIBRATE_WHEN = "preferences_alerts_vibrateWhen"; 1300b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan 13142dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik public static final String APPWIDGET_DATA_TYPE = "vnd.android.data/update"; 13242dabd1e1e89b0d4af5afb776c6ecc53f6ae9fbaRoboErik 133ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting static final String MACHINE_GENERATED_ADDRESS = "calendar.google.com"; 134ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting 135a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik private static final TimeZoneUtils mTZUtils = new TimeZoneUtils(SHARED_PREFS_NAME); 136b60218a31d948ea0a549daf6464063d20b48421fMichael Chan private static boolean mAllowWeekForDetailView = false; 137ca4786769151f97069980443ce43d9c4f867ac5bErik private static long mTardis = 0; 138dacfb663d63253880090151fde87bd9da9a59347Sara Ting private static String sVersion = null; 139d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan 1407e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static final Pattern mWildcardPattern = Pattern.compile("^.*$"); 14129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 14229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein /** 14329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * A coordinate must be of the following form for Google Maps to correctly use it: 14429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Latitude, Longitude 14529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 14629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * This may be in decimal form: 14729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Latitude: {-90 to 90} 14829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Longitude: {-180 to 180} 14929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 15029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Or, in degrees, minutes, and seconds: 15129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Latitude: {-90 to 90}° {0 to 59}' {0 to 59}" 15229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Latitude: {-180 to 180}° {0 to 59}' {0 to 59}" 15329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * + or - degrees may also be represented with N or n, S or s for latitude, and with 15429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * E or e, W or w for longitude, where the direction may either precede or follow the value. 15529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 15629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Some examples of coordinates that will be accepted by the regex: 15729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 37.422081°, -122.084576° 15829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 37.422081,-122.084576 15929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * +37°25'19.49", -122°5'4.47" 16029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 37°25'19.49"N, 122°5'4.47"W 16129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * N 37° 25' 19.49", W 122° 5' 4.47" 16229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein **/ 16329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DEGREES_LATITUDE = 16429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein "([-+NnSs]" + "(\\s)*)?" 16529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "[1-9]?[0-9](\u00B0)" + "(\\s)*" 16629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "([1-5]?[0-9]\')?" + "(\\s)*" 16729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" 16829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "((\\s)*" + "[NnSs])?"; 16929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DEGREES_LONGITUDE = 17029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein "([-+EeWw]" + "(\\s)*)?" 17129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(1)?[0-9]?[0-9](\u00B0)" + "(\\s)*" 17229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "([1-5]?[0-9]\')?" + "(\\s)*" 17329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "([1-5]?[0-9]" + "(\\.[0-9]+)?\")?" 17429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "((\\s)*" + "[EeWw])?"; 17529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DEGREES_PATTERN = 17629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein COORD_DEGREES_LATITUDE 17729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(\\s)*" + "," + "(\\s)*" 17829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + COORD_DEGREES_LONGITUDE; 17929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DECIMAL_LATITUDE = 18029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein "[+-]?" 18129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "[1-9]?[0-9]" + "(\\.[0-9]+)" 18229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(\u00B0)?"; 18329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DECIMAL_LONGITUDE = 18429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein "[+-]?" 18529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(1)?[0-9]?[0-9]" + "(\\.[0-9]+)" 18629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(\u00B0)?"; 18729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final String COORD_DECIMAL_PATTERN = 18829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein COORD_DECIMAL_LATITUDE 18929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + "(\\s)*" + "," + "(\\s)*" 19029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein + COORD_DECIMAL_LONGITUDE; 19129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein private static final Pattern COORD_PATTERN = 19229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Pattern.compile(COORD_DEGREES_PATTERN + "|" + COORD_DECIMAL_PATTERN); 19329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 1947e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static final String NANP_ALLOWED_SYMBOLS = "()+-*#."; 1957e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static final int NANP_MIN_DIGITS = 7; 1967e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static final int NANP_MAX_DIGITS = 11; 1977e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 1987e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 199fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting /** 200fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting * Returns whether the SDK is the Jellybean release or later. 201fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting */ 202fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting public static boolean isJellybeanOrLater() { 203fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; 204fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting } 205fac2d1594f7ecae8e50dc7790ce67de0c9b70d22Sara Ting 206d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan public static int getViewTypeFromIntentAndSharedPref(Activity activity) { 207dd95df57c8c5a58a85c4c0effad5652dec14f621Erik Intent intent = activity.getIntent(); 208dd95df57c8c5a58a85c4c0effad5652dec14f621Erik Bundle extras = intent.getExtras(); 2094b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(activity); 210d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan 2117b92da258a480284dcc15a518ea570072329a31dErik if (TextUtils.equals(intent.getAction(), Intent.ACTION_EDIT)) { 212dd95df57c8c5a58a85c4c0effad5652dec14f621Erik return ViewType.EDIT; 213dd95df57c8c5a58a85c4c0effad5652dec14f621Erik } 214d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan if (extras != null) { 215d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan if (extras.getBoolean(INTENT_KEY_DETAIL_VIEW, false)) { 216d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan // This is the "detail" view which is either agenda or day view 2174b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa return prefs.getInt(GeneralPreferences.KEY_DETAILED_VIEW, 2184b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa GeneralPreferences.DEFAULT_DETAILED_VIEW); 219d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan } else if (INTENT_VALUE_VIEW_TYPE_DAY.equals(extras.getString(INTENT_KEY_VIEW_TYPE))) { 220d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan // Not sure who uses this. This logic came from LaunchActivity 221d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan return ViewType.DAY; 222d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan } 223d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan } 224d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan 225d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan // Default to the last view 2267b92da258a480284dcc15a518ea570072329a31dErik return prefs.getInt( 2277b92da258a480284dcc15a518ea570072329a31dErik GeneralPreferences.KEY_START_VIEW, GeneralPreferences.DEFAULT_START_VIEW); 228d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan } 229ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan 230235d59cf61769ec8ab777d81cd1ceb2e7530f439Erik /** 231064beb957ae443760118023a40fd7001eb11c1a1RoboErik * Gets the intent action for telling the widget to update. 232064beb957ae443760118023a40fd7001eb11c1a1RoboErik */ 233064beb957ae443760118023a40fd7001eb11c1a1RoboErik public static String getWidgetUpdateAction(Context context) { 234064beb957ae443760118023a40fd7001eb11c1a1RoboErik return context.getPackageName() + ".APPWIDGET_UPDATE"; 235064beb957ae443760118023a40fd7001eb11c1a1RoboErik } 236064beb957ae443760118023a40fd7001eb11c1a1RoboErik 237064beb957ae443760118023a40fd7001eb11c1a1RoboErik /** 238064beb957ae443760118023a40fd7001eb11c1a1RoboErik * Gets the intent action for telling the widget to update. 239064beb957ae443760118023a40fd7001eb11c1a1RoboErik */ 240064beb957ae443760118023a40fd7001eb11c1a1RoboErik public static String getWidgetScheduledUpdateAction(Context context) { 241064beb957ae443760118023a40fd7001eb11c1a1RoboErik return context.getPackageName() + ".APPWIDGET_SCHEDULED_UPDATE"; 242064beb957ae443760118023a40fd7001eb11c1a1RoboErik } 243064beb957ae443760118023a40fd7001eb11c1a1RoboErik 244064beb957ae443760118023a40fd7001eb11c1a1RoboErik /** 245064beb957ae443760118023a40fd7001eb11c1a1RoboErik * Gets the intent action for telling the widget to update. 246064beb957ae443760118023a40fd7001eb11c1a1RoboErik */ 247064beb957ae443760118023a40fd7001eb11c1a1RoboErik public static String getSearchAuthority(Context context) { 248064beb957ae443760118023a40fd7001eb11c1a1RoboErik return context.getPackageName() + ".CalendarRecentSuggestionsProvider"; 249064beb957ae443760118023a40fd7001eb11c1a1RoboErik } 250064beb957ae443760118023a40fd7001eb11c1a1RoboErik 251064beb957ae443760118023a40fd7001eb11c1a1RoboErik /** 2527b92da258a480284dcc15a518ea570072329a31dErik * Writes a new home time zone to the db. Updates the home time zone in the 2537b92da258a480284dcc15a518ea570072329a31dErik * db asynchronously and updates the local cache. Sending a time zone of 2547b92da258a480284dcc15a518ea570072329a31dErik * **tbd** will cause it to be set to the device's time zone. null or empty 2557b92da258a480284dcc15a518ea570072329a31dErik * tz will be ignored. 2563dc5e908a825b879978ba523d9099dc2255da9a5Erik * 2573dc5e908a825b879978ba523d9099dc2255da9a5Erik * @param context The calling activity 2583dc5e908a825b879978ba523d9099dc2255da9a5Erik * @param timeZone The time zone to set Calendar to, or **tbd** 2593dc5e908a825b879978ba523d9099dc2255da9a5Erik */ 2603dc5e908a825b879978ba523d9099dc2255da9a5Erik public static void setTimeZone(Context context, String timeZone) { 261a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik mTZUtils.setTimeZone(context, timeZone); 2623dc5e908a825b879978ba523d9099dc2255da9a5Erik } 2633dc5e908a825b879978ba523d9099dc2255da9a5Erik 2643dc5e908a825b879978ba523d9099dc2255da9a5Erik /** 2657b92da258a480284dcc15a518ea570072329a31dErik * Gets the time zone that Calendar should be displayed in This is a helper 2667b92da258a480284dcc15a518ea570072329a31dErik * method to get the appropriate time zone for Calendar. If this is the 2677b92da258a480284dcc15a518ea570072329a31dErik * first time this method has been called it will initiate an asynchronous 2687b92da258a480284dcc15a518ea570072329a31dErik * query to verify that the data in preferences is correct. The callback 2697b92da258a480284dcc15a518ea570072329a31dErik * supplied will only be called if this query returns a value other than 2707b92da258a480284dcc15a518ea570072329a31dErik * what is stored in preferences and should cause the calling activity to 2717b92da258a480284dcc15a518ea570072329a31dErik * refresh anything that depends on calling this method. 272235d59cf61769ec8ab777d81cd1ceb2e7530f439Erik * 273235d59cf61769ec8ab777d81cd1ceb2e7530f439Erik * @param context The calling activity 2747b92da258a480284dcc15a518ea570072329a31dErik * @param callback The runnable that should execute if a query returns new 2757b92da258a480284dcc15a518ea570072329a31dErik * values 2767b92da258a480284dcc15a518ea570072329a31dErik * @return The string value representing the time zone Calendar should 2777b92da258a480284dcc15a518ea570072329a31dErik * display 278235d59cf61769ec8ab777d81cd1ceb2e7530f439Erik */ 279235d59cf61769ec8ab777d81cd1ceb2e7530f439Erik public static String getTimeZone(Context context, Runnable callback) { 280a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik return mTZUtils.getTimeZone(context, callback); 28145efa09d6e06f5569b2c0ae0dae0436dbfe6cb28Michael Chan } 28245efa09d6e06f5569b2c0ae0dae0436dbfe6cb28Michael Chan 2831427657d0bf7e69b831aa495828f67b45b69fd99Erik /** 2841427657d0bf7e69b831aa495828f67b45b69fd99Erik * Formats a date or a time range according to the local conventions. 2851427657d0bf7e69b831aa495828f67b45b69fd99Erik * 2861427657d0bf7e69b831aa495828f67b45b69fd99Erik * @param context the context is required only if the time is shown 2871427657d0bf7e69b831aa495828f67b45b69fd99Erik * @param startMillis the start time in UTC milliseconds 2881427657d0bf7e69b831aa495828f67b45b69fd99Erik * @param endMillis the end time in UTC milliseconds 28929190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa * @param flags a bit mask of options See {@link DateUtils#formatDateRange(Context, Formatter, 29029190975b9238dd6841f822f82f2fb83b0557f36Daisuke Miyakawa * long, long, int, String) formatDateRange} 2911427657d0bf7e69b831aa495828f67b45b69fd99Erik * @return a string containing the formatted date/time range. 2921427657d0bf7e69b831aa495828f67b45b69fd99Erik */ 2937b92da258a480284dcc15a518ea570072329a31dErik public static String formatDateRange( 2947b92da258a480284dcc15a518ea570072329a31dErik Context context, long startMillis, long endMillis, int flags) { 295a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik return mTZUtils.formatDateRange(context, startMillis, endMillis, flags); 296a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik } 297a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik 2980b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan public static boolean getDefaultVibrate(Context context, SharedPreferences prefs) { 2990b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan boolean vibrate; 3000b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan if (prefs.contains(KEY_ALERTS_VIBRATE_WHEN)) { 3010b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan // Migrate setting to new 4.2 behavior 3020b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan // 3030b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan // silent and never -> off 3040b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan // always -> on 3050b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan String vibrateWhen = prefs.getString(KEY_ALERTS_VIBRATE_WHEN, null); 3060b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan vibrate = vibrateWhen != null && vibrateWhen.equals(context 3070b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan .getString(R.string.prefDefault_alerts_vibrate_true)); 3080b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan prefs.edit().remove(KEY_ALERTS_VIBRATE_WHEN).commit(); 3090b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan Log.d(TAG, "Migrating KEY_ALERTS_VIBRATE_WHEN(" + vibrateWhen 3100b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan + ") to KEY_ALERTS_VIBRATE = " + vibrate); 3110b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan } else { 3120b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan vibrate = prefs.getBoolean(GeneralPreferences.KEY_ALERTS_VIBRATE, 3130b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan false); 3140b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan } 3150b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan return vibrate; 3160b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan } 3170b674bef73eac24a4c41cfd5af8be516f4936844Michael Chan 318e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan public static String[] getSharedPreference(Context context, String key, String[] defaultValue) { 319e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 320e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan Set<String> ss = prefs.getStringSet(key, null); 321e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan if (ss != null) { 322e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan String strings[] = new String[ss.size()]; 323e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan return ss.toArray(strings); 324e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 325e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan return defaultValue; 326e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 327e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 328a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik public static String getSharedPreference(Context context, String key, String defaultValue) { 329a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 330a48b9d426236d8d26bd99602bf0a84315b3f1b09Erik return prefs.getString(key, defaultValue); 3311427657d0bf7e69b831aa495828f67b45b69fd99Erik } 3321427657d0bf7e69b831aa495828f67b45b69fd99Erik 333d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan public static int getSharedPreference(Context context, String key, int defaultValue) { 3344b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 335d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan return prefs.getInt(key, defaultValue); 336d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan } 337d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan 338ca4786769151f97069980443ce43d9c4f867ac5bErik public static boolean getSharedPreference(Context context, String key, boolean defaultValue) { 339ca4786769151f97069980443ce43d9c4f867ac5bErik SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 340ca4786769151f97069980443ce43d9c4f867ac5bErik return prefs.getBoolean(key, defaultValue); 341ca4786769151f97069980443ce43d9c4f867ac5bErik } 342ca4786769151f97069980443ce43d9c4f867ac5bErik 343f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang /** 344f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang * Asynchronously sets the preference with the given key to the given value 345f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang * 346f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang * @param context the context to use to get preferences from 347f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang * @param key the key of the preference to set 348f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang * @param value the value to set 349f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang */ 350fbce65e53c7a111955f638db5bf8bee35381e5b7Erik public static void setSharedPreference(Context context, String key, String value) { 3514b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 35224fac46d6b87ce21d5e6a4b1c0fdcaa83d408997Brad Fitzpatrick prefs.edit().putString(key, value).apply(); 35345efa09d6e06f5569b2c0ae0dae0436dbfe6cb28Michael Chan } 35445efa09d6e06f5569b2c0ae0dae0436dbfe6cb28Michael Chan 355e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan public static void setSharedPreference(Context context, String key, String[] values) { 356e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 357e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan LinkedHashSet<String> set = new LinkedHashSet<String>(); 3586427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan for (String value : values) { 3596427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan set.add(value); 360e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 361e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan prefs.edit().putStringSet(key, set).apply(); 362e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 363e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 364ca4786769151f97069980443ce43d9c4f867ac5bErik protected static void tardis() { 365ca4786769151f97069980443ce43d9c4f867ac5bErik mTardis = System.currentTimeMillis(); 366ca4786769151f97069980443ce43d9c4f867ac5bErik } 367ca4786769151f97069980443ce43d9c4f867ac5bErik 368ca4786769151f97069980443ce43d9c4f867ac5bErik protected static long getTardis() { 369ca4786769151f97069980443ce43d9c4f867ac5bErik return mTardis; 370ca4786769151f97069980443ce43d9c4f867ac5bErik } 371ca4786769151f97069980443ce43d9c4f867ac5bErik 3723a07a68da6460c36a5dbec5b8828baa4355dbe04Sara Ting public static void setSharedPreference(Context context, String key, boolean value) { 3734b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 3743dc5e908a825b879978ba523d9099dc2255da9a5Erik SharedPreferences.Editor editor = prefs.edit(); 3753dc5e908a825b879978ba523d9099dc2255da9a5Erik editor.putBoolean(key, value); 376275232dae58bb24e3360a779ada9d24601a99bcfErik editor.apply(); 3773dc5e908a825b879978ba523d9099dc2255da9a5Erik } 3783dc5e908a825b879978ba523d9099dc2255da9a5Erik 379d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan static void setSharedPreference(Context context, String key, int value) { 380d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 381d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan SharedPreferences.Editor editor = prefs.edit(); 382d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan editor.putInt(key, value); 383d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan editor.apply(); 384d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan } 385d885c1a5876735bbf2c0086956101bd70bac37e7Michael Chan 386d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan /** 387d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan * Save default agenda/day/week/month view for next time 388d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan * 389d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan * @param context 390d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan * @param viewId {@link CalendarController.ViewType} 391d6734dbbd704cdb1bc331d1bd74b7a3be58f69ffMichael Chan */ 392e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan static void setDefaultView(Context context, int viewId) { 3934b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 394e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan SharedPreferences.Editor editor = prefs.edit(); 395f4ad4757de32ace6971cf4c3db7c395aa249001aMason Tang 396b60218a31d948ea0a549daf6464063d20b48421fMichael Chan boolean validDetailView = false; 397b60218a31d948ea0a549daf6464063d20b48421fMichael Chan if (mAllowWeekForDetailView && viewId == CalendarController.ViewType.WEEK) { 398b60218a31d948ea0a549daf6464063d20b48421fMichael Chan validDetailView = true; 399b60218a31d948ea0a549daf6464063d20b48421fMichael Chan } else { 400b60218a31d948ea0a549daf6464063d20b48421fMichael Chan validDetailView = viewId == CalendarController.ViewType.AGENDA 401b60218a31d948ea0a549daf6464063d20b48421fMichael Chan || viewId == CalendarController.ViewType.DAY; 402b60218a31d948ea0a549daf6464063d20b48421fMichael Chan } 403b60218a31d948ea0a549daf6464063d20b48421fMichael Chan 404b60218a31d948ea0a549daf6464063d20b48421fMichael Chan if (validDetailView) { 405b60218a31d948ea0a549daf6464063d20b48421fMichael Chan // Record the detail start view 4064b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa editor.putInt(GeneralPreferences.KEY_DETAILED_VIEW, viewId); 407e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan } 408e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan 409e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan // Record the (new) start view 4104b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa editor.putInt(GeneralPreferences.KEY_START_VIEW, viewId); 41124fac46d6b87ce21d5e6a4b1c0fdcaa83d408997Brad Fitzpatrick editor.apply(); 412e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan } 413e8aa59d4575d712601a133a9263acc23adbc8c17Michael Chan 414a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik public static MatrixCursor matrixCursorFromCursor(Cursor cursor) { 4156427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan if (cursor == null) { 4166427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan return null; 4176427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan } 4186427090cf0d69c2409f5bb7b0d8d6aba31027cc0Michael Chan 41985e3cef67e8d775afa8b08e8bffdd77fac007762Sara Ting String[] columnNames = cursor.getColumnNames(); 42085e3cef67e8d775afa8b08e8bffdd77fac007762Sara Ting if (columnNames == null) { 42185e3cef67e8d775afa8b08e8bffdd77fac007762Sara Ting columnNames = new String[] {}; 42285e3cef67e8d775afa8b08e8bffdd77fac007762Sara Ting } 42385e3cef67e8d775afa8b08e8bffdd77fac007762Sara Ting MatrixCursor newCursor = new MatrixCursor(columnNames); 424a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik int numColumns = cursor.getColumnCount(); 425a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik String data[] = new String[numColumns]; 426a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik cursor.moveToPosition(-1); 427a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik while (cursor.moveToNext()) { 428a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik for (int i = 0; i < numColumns; i++) { 429a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik data[i] = cursor.getString(i); 430a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 431a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik newCursor.addRow(data); 432a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 433a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return newCursor; 434a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 435a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 436a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik /** 437a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik * Compares two cursors to see if they contain the same data. 438a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik * 4397b92da258a480284dcc15a518ea570072329a31dErik * @return Returns true of the cursors contain the same data and are not 4407b92da258a480284dcc15a518ea570072329a31dErik * null, false otherwise 441a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik */ 442a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik public static boolean compareCursors(Cursor c1, Cursor c2) { 4437b92da258a480284dcc15a518ea570072329a31dErik if (c1 == null || c2 == null) { 444a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return false; 445a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 446a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 447a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik int numColumns = c1.getColumnCount(); 448a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik if (numColumns != c2.getColumnCount()) { 449a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return false; 450a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 451a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 452a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik if (c1.getCount() != c2.getCount()) { 453a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return false; 454a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 455a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 456a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik c1.moveToPosition(-1); 457a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik c2.moveToPosition(-1); 4587b92da258a480284dcc15a518ea570072329a31dErik while (c1.moveToNext() && c2.moveToNext()) { 4597b92da258a480284dcc15a518ea570072329a31dErik for (int i = 0; i < numColumns; i++) { 4607b92da258a480284dcc15a518ea570072329a31dErik if (!TextUtils.equals(c1.getString(i), c2.getString(i))) { 461a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return false; 462a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 463a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 464a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 465a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 466a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik return true; 467a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik } 468a144f86b41170e8ee7fe8d966cc51c5fc90cd44aErik 469146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project /** 470146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * If the given intent specifies a time (in milliseconds since the epoch), 471146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * then that time is returned. Otherwise, the current time is returned. 472146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */ 473146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project public static final long timeFromIntentInMillis(Intent intent) { 4747b92da258a480284dcc15a518ea570072329a31dErik // If the time was specified, then use that. Otherwise, use the current 4757b92da258a480284dcc15a518ea570072329a31dErik // time. 4761ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik Uri data = intent.getData(); 477a27a886892fe3ec5edbc63c0b58e0a988623011aRoboErik long millis = intent.getLongExtra(EXTRA_EVENT_BEGIN_TIME, -1); 4781ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik if (millis == -1 && data != null && data.isHierarchical()) { 4791ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik List<String> path = data.getPathSegments(); 4807b92da258a480284dcc15a518ea570072329a31dErik if (path.size() == 2 && path.get(0).equals("time")) { 4811ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik try { 4821ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik millis = Long.valueOf(data.getLastPathSegment()); 4831ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik } catch (NumberFormatException e) { 4847b92da258a480284dcc15a518ea570072329a31dErik Log.i("Calendar", "timeFromIntentInMillis: Data existed but no valid time " 4857b92da258a480284dcc15a518ea570072329a31dErik + "found. Using current time."); 4861ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik } 4871ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik } 4881ef7f3ae2831dce8fa5e350f78ac4258c1a0a605Erik } 48976727b7a9cf780f200414548b9d454bf9a701e3eErik if (millis <= 0) { 490146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project millis = System.currentTimeMillis(); 491146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 492146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project return millis; 493146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 494146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 495146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project /** 4967b92da258a480284dcc15a518ea570072329a31dErik * Formats the given Time object so that it gives the month and year (for 4977b92da258a480284dcc15a518ea570072329a31dErik * example, "September 2007"). 498146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * 499146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * @param time the time to format 500146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project * @return the string containing the weekday and the date 501146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project */ 502ad36a3c3cde7a2ec6d3a35d2529d46f03bd8d59dMichael Chan public static String formatMonthYear(Context context, Time time) { 503cfa204ba5c59eb6ebd4b54788ceb31010e4a37a0RoboErik int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY 504cfa204ba5c59eb6ebd4b54788ceb31010e4a37a0RoboErik | DateUtils.FORMAT_SHOW_YEAR; 505cfa204ba5c59eb6ebd4b54788ceb31010e4a37a0RoboErik long millis = time.toMillis(true); 506cfa204ba5c59eb6ebd4b54788ceb31010e4a37a0RoboErik return formatDateRange(context, millis, millis, flags); 507146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project } 508146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project 509146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project /** 5104c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * Returns a list joined together by the provided delimiter, for example, 5114c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * ["a", "b", "c"] could be joined into "a,b,c" 5124c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * 5134c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * @param things the things to join together 5144c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * @param delim the delimiter to use 5154c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang * @return a string contained the things joined together 5164c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang */ 5174c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang public static String join(List<?> things, String delim) { 5184c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang StringBuilder builder = new StringBuilder(); 5194c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang boolean first = true; 5204c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang for (Object thing : things) { 5214c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang if (first) { 5224c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang first = false; 5234c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang } else { 5244c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang builder.append(delim); 5254c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang } 5264c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang builder.append(thing.toString()); 5274c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang } 5284c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang return builder.toString(); 5294c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang } 5304c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang 5314c8871bf5dee3b3586b375aee98effde31b781a8Mason Tang /** 532981874e61ecf29a96a77601a3172b2503b6537eeErik * Returns the week since {@link Time#EPOCH_JULIAN_DAY} (Jan 1, 1970) 533981874e61ecf29a96a77601a3172b2503b6537eeErik * adjusted for first day of week. 534981874e61ecf29a96a77601a3172b2503b6537eeErik * 535981874e61ecf29a96a77601a3172b2503b6537eeErik * This takes a julian day and the week start day and calculates which 536981874e61ecf29a96a77601a3172b2503b6537eeErik * week since {@link Time#EPOCH_JULIAN_DAY} that day occurs in, starting 537981874e61ecf29a96a77601a3172b2503b6537eeErik * at 0. *Do not* use this to compute the ISO week number for the year. 538981874e61ecf29a96a77601a3172b2503b6537eeErik * 539981874e61ecf29a96a77601a3172b2503b6537eeErik * @param julianDay The julian day to calculate the week number for 540981874e61ecf29a96a77601a3172b2503b6537eeErik * @param firstDayOfWeek Which week day is the first day of the week, 541981874e61ecf29a96a77601a3172b2503b6537eeErik * see {@link Time#SUNDAY} 542981874e61ecf29a96a77601a3172b2503b6537eeErik * @return Weeks since the epoch 543981874e61ecf29a96a77601a3172b2503b6537eeErik */ 544981874e61ecf29a96a77601a3172b2503b6537eeErik public static int getWeeksSinceEpochFromJulianDay(int julianDay, int firstDayOfWeek) { 545981874e61ecf29a96a77601a3172b2503b6537eeErik int diff = Time.THURSDAY - firstDayOfWeek; 546981874e61ecf29a96a77601a3172b2503b6537eeErik if (diff < 0) { 547981874e61ecf29a96a77601a3172b2503b6537eeErik diff += 7; 548981874e61ecf29a96a77601a3172b2503b6537eeErik } 549981874e61ecf29a96a77601a3172b2503b6537eeErik int refDay = Time.EPOCH_JULIAN_DAY - diff; 550981874e61ecf29a96a77601a3172b2503b6537eeErik return (julianDay - refDay) / 7; 551981874e61ecf29a96a77601a3172b2503b6537eeErik } 552981874e61ecf29a96a77601a3172b2503b6537eeErik 553981874e61ecf29a96a77601a3172b2503b6537eeErik /** 554981874e61ecf29a96a77601a3172b2503b6537eeErik * Takes a number of weeks since the epoch and calculates the Julian day of 555981874e61ecf29a96a77601a3172b2503b6537eeErik * the Monday for that week. 556981874e61ecf29a96a77601a3172b2503b6537eeErik * 557981874e61ecf29a96a77601a3172b2503b6537eeErik * This assumes that the week containing the {@link Time#EPOCH_JULIAN_DAY} 558981874e61ecf29a96a77601a3172b2503b6537eeErik * is considered week 0. It returns the Julian day for the Monday 559981874e61ecf29a96a77601a3172b2503b6537eeErik * {@code week} weeks after the Monday of the week containing the epoch. 560981874e61ecf29a96a77601a3172b2503b6537eeErik * 561981874e61ecf29a96a77601a3172b2503b6537eeErik * @param week Number of weeks since the epoch 562981874e61ecf29a96a77601a3172b2503b6537eeErik * @return The julian day for the Monday of the given week since the epoch 563981874e61ecf29a96a77601a3172b2503b6537eeErik */ 564981874e61ecf29a96a77601a3172b2503b6537eeErik public static int getJulianMondayFromWeeksSinceEpoch(int week) { 565981874e61ecf29a96a77601a3172b2503b6537eeErik return MONDAY_BEFORE_JULIAN_EPOCH + week * 7; 566981874e61ecf29a96a77601a3172b2503b6537eeErik } 567981874e61ecf29a96a77601a3172b2503b6537eeErik 568981874e61ecf29a96a77601a3172b2503b6537eeErik /** 56956adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * Get first day of week as android.text.format.Time constant. 5707b92da258a480284dcc15a518ea570072329a31dErik * 57156adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @return the first day of week in android.text.format.Time 57256adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi */ 5738e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang public static int getFirstDayOfWeek(Context context) { 5744b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 5757b92da258a480284dcc15a518ea570072329a31dErik String pref = prefs.getString( 5767b92da258a480284dcc15a518ea570072329a31dErik GeneralPreferences.KEY_WEEK_START_DAY, GeneralPreferences.WEEK_START_DEFAULT); 5778e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang 5788e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang int startDay; 5794b441bd6544fe6d11be75f974a41afd8fa040a4fDaisuke Miyakawa if (GeneralPreferences.WEEK_START_DEFAULT.equals(pref)) { 5808e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang startDay = Calendar.getInstance().getFirstDayOfWeek(); 5818e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang } else { 5828e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang startDay = Integer.parseInt(pref); 5838e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang } 5848e3d430a020744faa21bf4ca24f1a99c36ec5c4fMason Tang 58556adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi if (startDay == Calendar.SATURDAY) { 58656adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi return Time.SATURDAY; 58756adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } else if (startDay == Calendar.MONDAY) { 58856adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi return Time.MONDAY; 58956adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } else { 59056adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi return Time.SUNDAY; 59156adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } 59256adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } 59356adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi 59456adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi /** 595d644b0df14ae6e204369b3454d16976fba32f15cDaisuke Miyakawa * @return true when week number should be shown. 596981874e61ecf29a96a77601a3172b2503b6537eeErik */ 597981874e61ecf29a96a77601a3172b2503b6537eeErik public static boolean getShowWeekNumber(Context context) { 598d644b0df14ae6e204369b3454d16976fba32f15cDaisuke Miyakawa final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 599981874e61ecf29a96a77601a3172b2503b6537eeErik return prefs.getBoolean( 600981874e61ecf29a96a77601a3172b2503b6537eeErik GeneralPreferences.KEY_SHOW_WEEK_NUM, GeneralPreferences.DEFAULT_SHOW_WEEK_NUM); 601981874e61ecf29a96a77601a3172b2503b6537eeErik } 602981874e61ecf29a96a77601a3172b2503b6537eeErik 603981874e61ecf29a96a77601a3172b2503b6537eeErik /** 60440bcd101b212c9863c3110c05a487a7ae6ebc3caErik * @return true when declined events should be hidden. 60540bcd101b212c9863c3110c05a487a7ae6ebc3caErik */ 60640bcd101b212c9863c3110c05a487a7ae6ebc3caErik public static boolean getHideDeclinedEvents(Context context) { 60740bcd101b212c9863c3110c05a487a7ae6ebc3caErik final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 60840bcd101b212c9863c3110c05a487a7ae6ebc3caErik return prefs.getBoolean(GeneralPreferences.KEY_HIDE_DECLINED, false); 60940bcd101b212c9863c3110c05a487a7ae6ebc3caErik } 61040bcd101b212c9863c3110c05a487a7ae6ebc3caErik 61191b01ed605e36fc5a7a924c226597a62c789b50dErik public static int getDaysPerWeek(Context context) { 61291b01ed605e36fc5a7a924c226597a62c789b50dErik final SharedPreferences prefs = GeneralPreferences.getSharedPreferences(context); 61391b01ed605e36fc5a7a924c226597a62c789b50dErik return prefs.getInt(GeneralPreferences.KEY_DAYS_PER_WEEK, 7); 61491b01ed605e36fc5a7a924c226597a62c789b50dErik } 61591b01ed605e36fc5a7a924c226597a62c789b50dErik 61640bcd101b212c9863c3110c05a487a7ae6ebc3caErik /** 61756adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * Determine whether the column position is Saturday or not. 6187b92da258a480284dcc15a518ea570072329a31dErik * 61956adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @param column the column position 62056adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @param firstDayOfWeek the first day of week in android.text.format.Time 62156adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @return true if the column is Saturday position 62256adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi */ 62356adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi public static boolean isSaturday(int column, int firstDayOfWeek) { 62456adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi return (firstDayOfWeek == Time.SUNDAY && column == 6) 6257b92da258a480284dcc15a518ea570072329a31dErik || (firstDayOfWeek == Time.MONDAY && column == 5) 6267b92da258a480284dcc15a518ea570072329a31dErik || (firstDayOfWeek == Time.SATURDAY && column == 0); 62756adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } 62856adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi 62956adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi /** 63056adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * Determine whether the column position is Sunday or not. 6317b92da258a480284dcc15a518ea570072329a31dErik * 63256adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @param column the column position 63356adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @param firstDayOfWeek the first day of week in android.text.format.Time 63456adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi * @return true if the column is Sunday position 63556adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi */ 63656adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi public static boolean isSunday(int column, int firstDayOfWeek) { 63756adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi return (firstDayOfWeek == Time.SUNDAY && column == 0) 6387b92da258a480284dcc15a518ea570072329a31dErik || (firstDayOfWeek == Time.MONDAY && column == 6) 6397b92da258a480284dcc15a518ea570072329a31dErik || (firstDayOfWeek == Time.SATURDAY && column == 1); 64056adc7b3f9e62ada7f3708c5c7228e8ac5af1755Takaoka G. Tadashi } 641ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan 642ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan /** 6439da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik * Convert given UTC time into current local time. This assumes it is for an 6449da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik * allday event and will adjust the time to be on a midnight boundary. 6453ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * 6463ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param recycle Time object to recycle, otherwise null. 6473ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang * @param utcTime Time to convert, in UTC. 6489da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik * @param tz The time zone to convert this time to. 6493ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang */ 6509da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik public static long convertAlldayUtcToLocal(Time recycle, long utcTime, String tz) { 6513ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang if (recycle == null) { 6523ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang recycle = new Time(); 6533ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 6543ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang recycle.timezone = Time.TIMEZONE_UTC; 6553ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang recycle.set(utcTime); 6569da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik recycle.timezone = tz; 6579da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik return recycle.normalize(true); 6589da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik } 6599da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik 6609da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik public static long convertAlldayLocalToUTC(Time recycle, long localTime, String tz) { 6619da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik if (recycle == null) { 6629da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik recycle = new Time(); 6639da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik } 6649da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik recycle.timezone = tz; 6659da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik recycle.set(localTime); 6669da910f65de0a325d4268d88f0cf2e2fd653f24aRoboErik recycle.timezone = Time.TIMEZONE_UTC; 6673ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang return recycle.normalize(true); 6683ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang } 6693ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang 6703ea333d41c04fd5f3a5d45f540c17894874429e8Mason Tang /** 671c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson * Finds and returns the next midnight after "theTime" in milliseconds UTC 672c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson * 673c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson * @param recycle - Time object to recycle, otherwise null. 674c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson * @param theTime - Time used for calculations (in UTC) 675c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson * @param tz The time zone to convert this time to. 676c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson */ 677c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson public static long getNextMidnight(Time recycle, long theTime, String tz) { 678c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson if (recycle == null) { 679c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle = new Time(); 680c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson } 681c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.timezone = tz; 682c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.set(theTime); 683c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.monthDay ++; 684c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.hour = 0; 685c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.minute = 0; 686c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson recycle.second = 0; 687c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson return recycle.normalize(true); 688c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson } 689c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson 690c1fae4df6202ac82c3facd76e5f33c7cbacb39d1Isaac Katzenelson /** 691ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan * Scan through a cursor of calendars and check if names are duplicated. 6927b92da258a480284dcc15a518ea570072329a31dErik * This travels a cursor containing calendar display names and fills in the 6937b92da258a480284dcc15a518ea570072329a31dErik * provided map with whether or not each name is repeated. 694ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan * 695ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan * @param isDuplicateName The map to put the duplicate check results in. 696ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan * @param cursor The query of calendars to check 697ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan * @param nameIndex The column of the query that contains the display name 698ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan */ 6997b92da258a480284dcc15a518ea570072329a31dErik public static void checkForDuplicateNames( 7007b92da258a480284dcc15a518ea570072329a31dErik Map<String, Boolean> isDuplicateName, Cursor cursor, int nameIndex) { 701ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan isDuplicateName.clear(); 702ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan cursor.moveToPosition(-1); 703ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan while (cursor.moveToNext()) { 704ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan String displayName = cursor.getString(nameIndex); 705ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan // Set it to true if we've seen this name before, false otherwise 706ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan if (displayName != null) { 707ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan isDuplicateName.put(displayName, isDuplicateName.containsKey(displayName)); 708ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan } 709ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan } 710ff6be831fc682374be6b78c13ecf5daca81f86d9Michael Chan } 7119138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang 7129138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang /** 7139138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang * Null-safe object comparison 7147b92da258a480284dcc15a518ea570072329a31dErik * 7159138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang * @param s1 7169138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang * @param s2 7179138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang * @return 7189138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang */ 7199138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang public static boolean equals(Object o1, Object o2) { 7209138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang return o1 == null ? o2 == null : o1.equals(o2); 7219138ce8a14924612c014da2b6e727b4117ba1a92Mason Tang } 722b60218a31d948ea0a549daf6464063d20b48421fMichael Chan 72363cd053150e00fde045b019dbe0f48b8a9ed3559Erik public static void setAllowWeekForDetailView(boolean allowWeekView) { 724b60218a31d948ea0a549daf6464063d20b48421fMichael Chan mAllowWeekForDetailView = allowWeekView; 725b60218a31d948ea0a549daf6464063d20b48421fMichael Chan } 72663cd053150e00fde045b019dbe0f48b8a9ed3559Erik 72763cd053150e00fde045b019dbe0f48b8a9ed3559Erik public static boolean getAllowWeekForDetailView() { 72863cd053150e00fde045b019dbe0f48b8a9ed3559Erik return mAllowWeekForDetailView; 72963cd053150e00fde045b019dbe0f48b8a9ed3559Erik } 7300b1bd10dbf90e55821a555c68a8a444854e31252Isaac Katzenelson 731ff5c4345f794ec12b0a92f284434ddc1c88ff3a8Isaac Katzenelson public static boolean getConfigBool(Context c, int key) { 732ff5c4345f794ec12b0a92f284434ddc1c88ff3a8Isaac Katzenelson return c.getResources().getBoolean(key); 733ff5c4345f794ec12b0a92f284434ddc1c88ff3a8Isaac Katzenelson } 73482400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson 735ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung /** 736ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung * For devices with Jellybean or later, darkens the given color to ensure that white text is 737ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung * clearly visible on top of it. For devices prior to Jellybean, does nothing, as the 738ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung * sync adapter handles the color change. 739ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung * 740ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung * @param color 741ede0fb1b3031ee75200f4030ecae8c69c9656cbaJames Kung */ 7424acb2fd087308dea146b8b10f5278c59df387680RoboErik public static int getDisplayColorFromColor(int color) { 743537f43d0eb9e86133ea8ed95862dfdbd788bba67Michael Chan if (!isJellybeanOrLater()) { 744537f43d0eb9e86133ea8ed95862dfdbd788bba67Michael Chan return color; 745537f43d0eb9e86133ea8ed95862dfdbd788bba67Michael Chan } 746f9411fefba89504c90a7bd8da5472cac538e916aMichael Chan 7474acb2fd087308dea146b8b10f5278c59df387680RoboErik float[] hsv = new float[3]; 7484acb2fd087308dea146b8b10f5278c59df387680RoboErik Color.colorToHSV(color, hsv); 749f9411fefba89504c90a7bd8da5472cac538e916aMichael Chan hsv[1] = Math.min(hsv[1] * SATURATION_ADJUST, 1.0f); 750f9411fefba89504c90a7bd8da5472cac538e916aMichael Chan hsv[2] = hsv[2] * INTENSITY_ADJUST; 7514acb2fd087308dea146b8b10f5278c59df387680RoboErik return Color.HSVToColor(hsv); 7524acb2fd087308dea146b8b10f5278c59df387680RoboErik } 7534acb2fd087308dea146b8b10f5278c59df387680RoboErik 7543c40e07972c989719723e24db6d5d8812200823bRoboErik // This takes a color and computes what it would look like blended with 7553c40e07972c989719723e24db6d5d8812200823bRoboErik // white. The result is the color that should be used for declined events. 7563c40e07972c989719723e24db6d5d8812200823bRoboErik public static int getDeclinedColorFromColor(int color) { 7573c40e07972c989719723e24db6d5d8812200823bRoboErik int bg = 0xffffffff; 758e6109c584d27a6c25df1819a5c6ec444e789ce61Isaac Katzenelson int a = DECLINED_EVENT_ALPHA; 7593c40e07972c989719723e24db6d5d8812200823bRoboErik int r = (((color & 0x00ff0000) * a) + ((bg & 0x00ff0000) * (0xff - a))) & 0xff000000; 7603c40e07972c989719723e24db6d5d8812200823bRoboErik int g = (((color & 0x0000ff00) * a) + ((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000; 7613c40e07972c989719723e24db6d5d8812200823bRoboErik int b = (((color & 0x000000ff) * a) + ((bg & 0x000000ff) * (0xff - a))) & 0x0000ff00; 7623c40e07972c989719723e24db6d5d8812200823bRoboErik return (0xff000000) | ((r | g | b) >> 8); 7633c40e07972c989719723e24db6d5d8812200823bRoboErik } 7643c40e07972c989719723e24db6d5d8812200823bRoboErik 765092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // A single strand represents one color of events. Events are divided up by 766092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // color to make them convenient to draw. The black strand is special in 767092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // that it holds conflicting events as well as color settings for allday on 768092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // each day. 769092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik public static class DNAStrand { 770092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik public float[] points; 771092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik public int[] allDays; // color for the allday, 0 means no event 772092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int position; 773092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik public int color; 774092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int count; 775092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 77682400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson 777092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // A segment is a single continuous length of time occupied by a single 778092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // color. Segments should never span multiple days. 779092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static class DNASegment { 780092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int startMinute; // in minutes since the start of the week 781092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int endMinute; 782092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int color; // Calendar color or black for conflicts 783092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int day; // quick reference to the day this segment is on 78472a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 78572a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson 78672a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson /** 787092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * Converts a list of events to a list of segments to draw. Assumes list is 788092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * ordered by start time of the events. The function processes events for a 789092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * range of days from firstJulianDay to firstJulianDay + dayXs.length - 1. 790092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * The algorithm goes over all the events and creates a set of segments 791092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * ordered by start time. This list of segments is then converted into a 792092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * HashMap of strands which contain the draw points and are organized by 793092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * color. The strands can then be drawn by setting the paint color to each 794092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * strand's color and calling drawLines on its set of points. The points are 795092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * set up using the following parameters. 796092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * <ul> 797092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * <li>Events between midnight and WORK_DAY_START_MINUTES are compressed 798092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * into the first 1/8th of the space between top and bottom.</li> 799092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * <li>Events between WORK_DAY_END_MINUTES and the following midnight are 800092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * compressed into the last 1/8th of the space between top and bottom</li> 801092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * <li>Events between WORK_DAY_START_MINUTES and WORK_DAY_END_MINUTES use 802092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * the remaining 3/4ths of the space</li> 803092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * <li>All segments drawn will maintain at least minPixels height, except 804092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * for conflicts in the first or last 1/8th, which may be smaller</li> 805092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * </ul> 80671b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson * 807092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param firstJulianDay The julian day of the first day of events 808092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param events A list of events sorted by start time 809092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param top The lowest y value the dna should be drawn at 810092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param bottom The highest y value the dna should be drawn at 811092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param dayXs An array of x values to draw the dna at, one for each day 812092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @param conflictColor the color to use for conflicts 813092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * @return 81472a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson */ 815092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik public static HashMap<Integer, DNAStrand> createDNAStrands(int firstJulianDay, 816092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik ArrayList<Event> events, int top, int bottom, int minPixels, int[] dayXs, 817092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Context context) { 81872a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson 819092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (!mMinutesLoaded) { 820092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (context == null) { 821092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.wtf(TAG, "No context and haven't loaded parameters yet! Can't create DNA."); 82271b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson } 823092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Resources res = context.getResources(); 824092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik CONFLICT_COLOR = res.getColor(R.color.month_dna_conflict_time_color); 825092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik WORK_DAY_START_MINUTES = res.getInteger(R.integer.work_start_minutes); 826092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik WORK_DAY_END_MINUTES = res.getInteger(R.integer.work_end_minutes); 827092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik WORK_DAY_END_LENGTH = DAY_IN_MINUTES - WORK_DAY_END_MINUTES; 828092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik WORK_DAY_MINUTES = WORK_DAY_END_MINUTES - WORK_DAY_START_MINUTES; 829092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik mMinutesLoaded = true; 83072a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 83172a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson 832092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (events == null || events.isEmpty() || dayXs == null || dayXs.length < 1 833092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik || bottom - top < 8 || minPixels < 0) { 834092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.e(TAG, 835092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik "Bad values for createDNAStrands! events:" + events + " dayXs:" 836092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + Arrays.toString(dayXs) + " bot-top:" + (bottom - top) + " minPixels:" 837092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + minPixels); 838092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik return null; 83971b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson } 84071b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson 841092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik LinkedList<DNASegment> segments = new LinkedList<DNASegment>(); 842092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik HashMap<Integer, DNAStrand> strands = new HashMap<Integer, DNAStrand>(); 843092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // add a black strand by default, other colors will get added in 844092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the loop 845092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand blackStrand = new DNAStrand(); 846092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik blackStrand.color = CONFLICT_COLOR; 847092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.put(CONFLICT_COLOR, blackStrand); 848092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the min length is the number of minutes that will occupy 849092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // MIN_SEGMENT_PIXELS in the 'work day' time slot. This computes the 850092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // minutes/pixel * minpx where the number of pixels are 3/4 the total 851092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // dna height: 4*(mins/(px * 3/4)) 852092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int minMinutes = minPixels * 4 * WORK_DAY_MINUTES / (3 * (bottom - top)); 853092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 854092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // There are slightly fewer than half as many pixels in 1/6 the space, 855092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // so round to 2.5x for the min minutes in the non-work area 856092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int minOtherMinutes = minMinutes * 5 / 2; 857092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int lastJulianDay = firstJulianDay + dayXs.length - 1; 858092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 859092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Event event = new Event(); 860092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Go through all the events for the week 861092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik for (Event currEvent : events) { 862092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if this event is outside the weeks range skip it 863092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (currEvent.endDay < firstJulianDay || currEvent.startDay > lastJulianDay) { 864092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik continue; 86572a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 866092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (currEvent.drawAsAllday()) { 867092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik addAllDayToStrands(currEvent, strands, firstJulianDay, dayXs.length); 868092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik continue; 86971b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson } 870092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Copy the event over so we can clip its start and end to our range 871092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currEvent.copyTo(event); 872092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startDay < firstJulianDay) { 873092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.startDay = firstJulianDay; 874092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.startTime = 0; 87571b9ce3a20ebaff8f7e40fc0e5ec2a388bcc394cIsaac Katzenelson } 876092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If it starts after the work day make sure the start is at least 877092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // minPixels from midnight 878092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startTime > DAY_IN_MINUTES - minOtherMinutes) { 879092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.startTime = DAY_IN_MINUTES - minOtherMinutes; 88082400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 881092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.endDay > lastJulianDay) { 882092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endDay = lastJulianDay; 883092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endTime = DAY_IN_MINUTES - 1; 88472a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 885092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If the end time is before the work day make sure it ends at least 886092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // minPixels after midnight 887092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.endTime < minOtherMinutes) { 888092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endTime = minOtherMinutes; 88982400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 890092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If the start and end are on the same day make sure they are at 891092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // least minPixels apart. This only needs to be done for times 892092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // outside the work day as the min distance for within the work day 893092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // is enforced in the segment code. 894092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startDay == event.endDay && 895092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endTime - event.startTime < minOtherMinutes) { 896092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If it's less than minPixels in an area before the work 897092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // day 898092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startTime < WORK_DAY_START_MINUTES) { 899092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // extend the end to the first easy guarantee that it's 900092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // minPixels 901092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endTime = Math.min(event.startTime + minOtherMinutes, 902092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik WORK_DAY_START_MINUTES + minMinutes); 903092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if it's in the area after the work day 904092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } else if (event.endTime > WORK_DAY_END_MINUTES) { 905092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // First try shifting the end but not past midnight 906092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.endTime = Math.min(event.endTime + minOtherMinutes, DAY_IN_MINUTES - 1); 907092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if it's still too small move the start back 908092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.endTime - event.startTime < minOtherMinutes) { 909092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event.startTime = event.endTime - minOtherMinutes; 910092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 911092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 91282400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 91382400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson 914092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // This handles adding the first segment 915092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (segments.size() == 0) { 916092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik addNewSegment(segments, event, strands, firstJulianDay, 0, minMinutes); 91782400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson continue; 91882400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 919092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Now compare our current start time to the end time of the last 920092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // segment in the list 921092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment lastSegment = segments.getLast(); 922092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int startMinute = (event.startDay - firstJulianDay) * DAY_IN_MINUTES + event.startTime; 923092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int endMinute = Math.max((event.endDay - firstJulianDay) * DAY_IN_MINUTES 924092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + event.endTime, startMinute + minMinutes); 925092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 926092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (startMinute < 0) { 927092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik startMinute = 0; 928092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 929092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (endMinute >= WEEK_IN_MINUTES) { 930092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik endMinute = WEEK_IN_MINUTES - 1; 931092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 932092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If we start before the last segment in the list ends we need to 933092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // start going through the list as this may conflict with other 934092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // events 935092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (startMinute < lastSegment.endMinute) { 936092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int i = segments.size(); 937092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // find the last segment this event intersects with 938092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik while (--i >= 0 && endMinute < segments.get(i).startMinute); 939092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 940092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment currSegment; 941092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // for each segment this event intersects with 942092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik for (; i >= 0 && startMinute <= (currSegment = segments.get(i)).endMinute; i--) { 943092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if the segment is already a conflict ignore it 944092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (currSegment.color == CONFLICT_COLOR) { 945092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik continue; 94672a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 947092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if the event ends before the segment and wouldn't create 948092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // a segment that is too small split off the right side 949092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (endMinute < currSegment.endMinute - minMinutes) { 950092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment rhs = new DNASegment(); 951092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik rhs.endMinute = currSegment.endMinute; 952092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik rhs.color = currSegment.color; 953092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik rhs.startMinute = endMinute + 1; 954092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik rhs.day = currSegment.day; 955092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currSegment.endMinute = endMinute; 956092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segments.add(i + 1, rhs); 957092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(rhs.color).count++; 958092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (DEBUG) { 959092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.d(TAG, "Added rhs, curr:" + currSegment.toString() + " i:" 960092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + segments.get(i).toString()); 961092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 962092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 963092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if the event starts after the segment and wouldn't create 964092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // a segment that is too small split off the left side 965092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (startMinute > currSegment.startMinute + minMinutes) { 966092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment lhs = new DNASegment(); 967092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.startMinute = currSegment.startMinute; 968092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.color = currSegment.color; 969092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endMinute = startMinute - 1; 970092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.day = currSegment.day; 971092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currSegment.startMinute = startMinute; 972092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // increment i so that we are at the right position when 973092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // referencing the segments to the right and left of the 974092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // current segment. 975092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segments.add(i++, lhs); 976092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(lhs.color).count++; 977092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (DEBUG) { 978092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.d(TAG, "Added lhs, curr:" + currSegment.toString() + " i:" 979092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + segments.get(i).toString()); 980092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 981092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 982092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if the right side is black merge this with the segment to 983092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the right if they're on the same day and overlap 984092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (i + 1 < segments.size()) { 985092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment rhs = segments.get(i + 1); 986092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (rhs.color == CONFLICT_COLOR && currSegment.day == rhs.day 987092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik && rhs.startMinute <= currSegment.endMinute + 1) { 988092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik rhs.startMinute = Math.min(currSegment.startMinute, rhs.startMinute); 989092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segments.remove(currSegment); 990092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(currSegment.color).count--; 991092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // point at the new current segment 992092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currSegment = rhs; 993092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 994092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 995092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if the left side is black merge this with the segment to 996092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the left if they're on the same day and overlap 997092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (i - 1 >= 0) { 998092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment lhs = segments.get(i - 1); 999092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (lhs.color == CONFLICT_COLOR && currSegment.day == lhs.day 1000092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik && lhs.endMinute >= currSegment.startMinute - 1) { 1001092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endMinute = Math.max(currSegment.endMinute, lhs.endMinute); 1002092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segments.remove(currSegment); 1003092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(currSegment.color).count--; 1004092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // point at the new current segment 1005092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currSegment = lhs; 1006092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // point i at the new current segment in case new 1007092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // code is added 1008092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik i--; 1009092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1010092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1011092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if we're still not black, decrement the count for the 1012092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // color being removed, change this to black, and increment 1013092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the black count 1014092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (currSegment.color != CONFLICT_COLOR) { 1015092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(currSegment.color).count--; 1016092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik currSegment.color = CONFLICT_COLOR; 1017092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.get(CONFLICT_COLOR).count++; 101872a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 101972a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 1020092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 102172a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 1022092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If this event extends beyond the last segment add a new segment 1023092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (endMinute > lastSegment.endMinute) { 1024092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik addNewSegment(segments, event, strands, firstJulianDay, lastSegment.endMinute, 1025092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik minMinutes); 102682400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 102782400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 1028092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik weaveDNAStrands(segments, firstJulianDay, strands, top, bottom, dayXs); 1029092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik return strands; 1030092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 103182400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson 1032092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // This figures out allDay colors as allDay events are found 1033092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static void addAllDayToStrands(Event event, HashMap<Integer, DNAStrand> strands, 1034092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int firstJulianDay, int numDays) { 1035092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand strand = getOrCreateStrand(strands, CONFLICT_COLOR); 1036092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if we haven't initialized the allDay portion create it now 1037092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (strand.allDays == null) { 1038092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.allDays = new int[numDays]; 103972a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 104072a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson 1041092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // For each day this event is on update the color 1042092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int end = Math.min(event.endDay - firstJulianDay, numDays - 1); 1043092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik for (int i = Math.max(event.startDay - firstJulianDay, 0); i <= end; i++) { 1044092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (strand.allDays[i] != 0) { 1045092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // if this day already had a color, it is now a conflict 1046092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.allDays[i] = CONFLICT_COLOR; 1047092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } else { 1048092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // else it's just the color of the event 1049092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.allDays[i] = event.color; 1050092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1051092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1052092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1053092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1054092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // This processes all the segments, sorts them by color, and generates a 1055092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // list of points to draw 1056092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static void weaveDNAStrands(LinkedList<DNASegment> segments, int firstJulianDay, 1057092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik HashMap<Integer, DNAStrand> strands, int top, int bottom, int[] dayXs) { 1058092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // First, get rid of any colors that ended up with no segments 1059092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Iterator<DNAStrand> strandIterator = strands.values().iterator(); 1060092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik while (strandIterator.hasNext()) { 1061092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand strand = strandIterator.next(); 1062092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (strand.count < 1 && strand.allDays == null) { 1063092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strandIterator.remove(); 106472a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson continue; 106572a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 1066092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.points = new float[strand.count * 4]; 1067092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.position = 0; 1068092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1069092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Go through each segment and compute its points 1070092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik for (DNASegment segment : segments) { 1071092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Add the points to the strand of that color 1072092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand strand = strands.get(segment.color); 1073092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int dayIndex = segment.day - firstJulianDay; 1074092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int dayStartMinute = segment.startMinute % DAY_IN_MINUTES; 1075092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int dayEndMinute = segment.endMinute % DAY_IN_MINUTES; 1076092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int height = bottom - top; 1077092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int workDayHeight = height * 3 / 4; 1078092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int remainderHeight = (height - workDayHeight) / 2; 1079092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1080092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int x = dayXs[dayIndex]; 1081092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int y0 = 0; 1082092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int y1 = 0; 1083092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1084092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik y0 = top + getPixelOffsetFromMinutes(dayStartMinute, workDayHeight, remainderHeight); 1085092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik y1 = top + getPixelOffsetFromMinutes(dayEndMinute, workDayHeight, remainderHeight); 1086092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (DEBUG) { 1087092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.d(TAG, "Adding " + Integer.toHexString(segment.color) + " at x,y0,y1: " + x 1088092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik + " " + y0 + " " + y1 + " for " + dayStartMinute + " " + dayEndMinute); 108972a9459e1f4cec02ad9e8dbdf824d66920b762eeIsaac Katzenelson } 1090092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.points[strand.position++] = x; 1091092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.points[strand.position++] = y0; 1092092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.points[strand.position++] = x; 1093092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.points[strand.position++] = y1; 109482400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 109582400dd70331df7885dd59b809c4bc0667046320Isaac Katzenelson } 1096092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1097092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik /** 1098092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * Compute a pixel offset from the top for a given minute from the work day 1099092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * height and the height of the top area. 1100092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik */ 1101092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static int getPixelOffsetFromMinutes(int minute, int workDayHeight, 1102092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int remainderHeight) { 1103092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int y; 1104092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (minute < WORK_DAY_START_MINUTES) { 1105092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik y = minute * remainderHeight / WORK_DAY_START_MINUTES; 1106092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } else if (minute < WORK_DAY_END_MINUTES) { 1107092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik y = remainderHeight + (minute - WORK_DAY_START_MINUTES) * workDayHeight 1108092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik / WORK_DAY_MINUTES; 1109092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } else { 1110092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik y = remainderHeight + workDayHeight + (minute - WORK_DAY_END_MINUTES) * remainderHeight 1111092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik / WORK_DAY_END_LENGTH; 1112092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1113092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik return y; 1114092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1115092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1116092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik /** 1117092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * Add a new segment based on the event provided. This will handle splitting 1118092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * segments across day boundaries and ensures a minimum size for segments. 1119092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik */ 1120092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static void addNewSegment(LinkedList<DNASegment> segments, Event event, 1121092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik HashMap<Integer, DNAStrand> strands, int firstJulianDay, int minStart, int minMinutes) { 1122092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startDay > event.endDay) { 1123092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Log.wtf(TAG, "Event starts after it ends: " + event.toString()); 1124092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1125092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // If this is a multiday event split it up by day 1126092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (event.startDay != event.endDay) { 1127092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik Event lhs = new Event(); 1128092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.color = event.color; 1129092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.startDay = event.startDay; 1130092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // the first day we want the start time to be the actual start time 1131092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.startTime = event.startTime; 1132092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endDay = lhs.startDay; 1133092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endTime = DAY_IN_MINUTES - 1; 1134092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Nearly recursive iteration! 1135092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik while (lhs.startDay != event.endDay) { 1136092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik addNewSegment(segments, lhs, strands, firstJulianDay, minStart, minMinutes); 1137092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // The days in between are all day, even though that shouldn't 1138092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // actually happen due to the allday filtering 1139092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.startDay++; 1140092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endDay = lhs.startDay; 1141092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.startTime = 0; 1142092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik minStart = 0; 1143092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1144092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // The last day we want the end time to be the actual end time 1145092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik lhs.endTime = event.endTime; 1146092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik event = lhs; 1147092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1148092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // Create the new segment and compute its fields 1149092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNASegment segment = new DNASegment(); 1150092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int dayOffset = (event.startDay - firstJulianDay) * DAY_IN_MINUTES; 1151092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int endOfDay = dayOffset + DAY_IN_MINUTES - 1; 1152092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // clip the start if needed 1153092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segment.startMinute = Math.max(dayOffset + event.startTime, minStart); 1154092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // and extend the end if it's too small, but not beyond the end of the 1155092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // day 1156092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik int minEnd = Math.min(segment.startMinute + minMinutes, endOfDay); 1157092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segment.endMinute = Math.max(dayOffset + event.endTime, minEnd); 1158092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (segment.endMinute > endOfDay) { 1159092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segment.endMinute = endOfDay; 1160092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1161092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1162092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segment.color = event.color; 1163092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segment.day = event.startDay; 1164092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik segments.add(segment); 1165092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // increment the count for the correct color or add a new strand if we 1166092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik // don't have that color yet 1167092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand strand = getOrCreateStrand(strands, segment.color); 1168092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.count++; 1169092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1170092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1171092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik /** 1172092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik * Try to get a strand of the given color. Create it if it doesn't exist. 1173092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik */ 1174092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik private static DNAStrand getOrCreateStrand(HashMap<Integer, DNAStrand> strands, int color) { 1175092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik DNAStrand strand = strands.get(color); 1176092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik if (strand == null) { 1177092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand = new DNAStrand(); 1178092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.color = color; 1179092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strand.count = 0; 1180092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik strands.put(strand.color, strand); 1181092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1182092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik return strand; 1183092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik } 1184092caec951caa0fabcd51729678e3ddcd6ca2f03RoboErik 1185c0f6efee87520ce0c8b52246129169f94850177bRoboErik /** 1186c0f6efee87520ce0c8b52246129169f94850177bRoboErik * Sends an intent to launch the top level Calendar view. 1187c0f6efee87520ce0c8b52246129169f94850177bRoboErik * 1188c0f6efee87520ce0c8b52246129169f94850177bRoboErik * @param context 1189c0f6efee87520ce0c8b52246129169f94850177bRoboErik */ 1190c0f6efee87520ce0c8b52246129169f94850177bRoboErik public static void returnToCalendarHome(Context context) { 11913864be025ed7fa5bcb33c7adaae9267d5b34f17fRoboErik Intent launchIntent = new Intent(context, AllInOneActivity.class); 11924ba19dfbd8eccc5a22c3e064a9810336ef73f658RoboErik launchIntent.setAction(Intent.ACTION_DEFAULT); 11933864be025ed7fa5bcb33c7adaae9267d5b34f17fRoboErik launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 11944ba19dfbd8eccc5a22c3e064a9810336ef73f658RoboErik launchIntent.putExtra(INTENT_KEY_HOME, true); 1195c0f6efee87520ce0c8b52246129169f94850177bRoboErik context.startActivity(launchIntent); 1196c0f6efee87520ce0c8b52246129169f94850177bRoboErik } 119714e82b453a95456277fa5c40189c9b253a8e487fRoboErik 119814e82b453a95456277fa5c40189c9b253a8e487fRoboErik /** 119950f10944eddba97e6730bbdc7545672d0eeec08cRoboErik * This sets up a search view to use Calendar's search suggestions provider 120050f10944eddba97e6730bbdc7545672d0eeec08cRoboErik * and to allow refining the search. 12015d89406c7ca7974b5fe61b21c82634f8726439efMichael Chan * 120250f10944eddba97e6730bbdc7545672d0eeec08cRoboErik * @param view The {@link SearchView} to set up 120350f10944eddba97e6730bbdc7545672d0eeec08cRoboErik * @param act The activity using the view 120450f10944eddba97e6730bbdc7545672d0eeec08cRoboErik */ 120550f10944eddba97e6730bbdc7545672d0eeec08cRoboErik public static void setUpSearchView(SearchView view, Activity act) { 120650f10944eddba97e6730bbdc7545672d0eeec08cRoboErik SearchManager searchManager = (SearchManager) act.getSystemService(Context.SEARCH_SERVICE); 120750f10944eddba97e6730bbdc7545672d0eeec08cRoboErik view.setSearchableInfo(searchManager.getSearchableInfo(act.getComponentName())); 120850f10944eddba97e6730bbdc7545672d0eeec08cRoboErik view.setQueryRefinementEnabled(true); 120950f10944eddba97e6730bbdc7545672d0eeec08cRoboErik } 121050f10944eddba97e6730bbdc7545672d0eeec08cRoboErik 121150f10944eddba97e6730bbdc7545672d0eeec08cRoboErik /** 121214e82b453a95456277fa5c40189c9b253a8e487fRoboErik * Given a context and a time in millis since unix epoch figures out the 121314e82b453a95456277fa5c40189c9b253a8e487fRoboErik * correct week of the year for that time. 121414e82b453a95456277fa5c40189c9b253a8e487fRoboErik * 121514e82b453a95456277fa5c40189c9b253a8e487fRoboErik * @param millisSinceEpoch 121614e82b453a95456277fa5c40189c9b253a8e487fRoboErik * @return 121714e82b453a95456277fa5c40189c9b253a8e487fRoboErik */ 121814e82b453a95456277fa5c40189c9b253a8e487fRoboErik public static int getWeekNumberFromTime(long millisSinceEpoch, Context context) { 121914e82b453a95456277fa5c40189c9b253a8e487fRoboErik Time weekTime = new Time(getTimeZone(context, null)); 122014e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.set(millisSinceEpoch); 122114e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.normalize(true); 122214e82b453a95456277fa5c40189c9b253a8e487fRoboErik int firstDayOfWeek = getFirstDayOfWeek(context); 122314e82b453a95456277fa5c40189c9b253a8e487fRoboErik // if the date is on Saturday or Sunday and the start of the week 122414e82b453a95456277fa5c40189c9b253a8e487fRoboErik // isn't Monday we may need to shift the date to be in the correct 122514e82b453a95456277fa5c40189c9b253a8e487fRoboErik // week 122614e82b453a95456277fa5c40189c9b253a8e487fRoboErik if (weekTime.weekDay == Time.SUNDAY 122714e82b453a95456277fa5c40189c9b253a8e487fRoboErik && (firstDayOfWeek == Time.SUNDAY || firstDayOfWeek == Time.SATURDAY)) { 122814e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.monthDay++; 122914e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.normalize(true); 123014e82b453a95456277fa5c40189c9b253a8e487fRoboErik } else if (weekTime.weekDay == Time.SATURDAY && firstDayOfWeek == Time.SATURDAY) { 123114e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.monthDay += 2; 123214e82b453a95456277fa5c40189c9b253a8e487fRoboErik weekTime.normalize(true); 123314e82b453a95456277fa5c40189c9b253a8e487fRoboErik } 123414e82b453a95456277fa5c40189c9b253a8e487fRoboErik return weekTime.getWeekNumber(); 123514e82b453a95456277fa5c40189c9b253a8e487fRoboErik } 12364eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik 12374eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik /** 12384eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * Formats a day of the week string. This is either just the name of the day 12394eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * or a combination of yesterday/today/tomorrow and the day of the week. 12404eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * 12414eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * @param julianDay The julian day to get the string for 12424eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * @param todayJulianDay The julian day for today's date 12434eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * @param millis A utc millis since epoch time that falls on julian day 12444eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * @param context The calling context, used to get the timezone and do the 12454eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * formatting 12464eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik * @return 12474eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik */ 12484eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik public static String getDayOfWeekString(int julianDay, int todayJulianDay, long millis, 12494eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik Context context) { 125099704a2787158bf670fa4a7b4e4f89ace10afa00Michael Chan getTimeZone(context, null); 12514eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik int flags = DateUtils.FORMAT_SHOW_WEEKDAY; 12524eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik String dayViewText; 12534eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik if (julianDay == todayJulianDay) { 12544eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik dayViewText = context.getString(R.string.agenda_today, 12554eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik mTZUtils.formatDateRange(context, millis, millis, flags).toString()); 12564eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik } else if (julianDay == todayJulianDay - 1) { 12574eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik dayViewText = context.getString(R.string.agenda_yesterday, 12584eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik mTZUtils.formatDateRange(context, millis, millis, flags).toString()); 12594eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik } else if (julianDay == todayJulianDay + 1) { 12604eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik dayViewText = context.getString(R.string.agenda_tomorrow, 12614eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik mTZUtils.formatDateRange(context, millis, millis, flags).toString()); 12624eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik } else { 12634eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik dayViewText = mTZUtils.formatDateRange(context, millis, millis, flags).toString(); 12644eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik } 12654eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik dayViewText = dayViewText.toUpperCase(); 12664eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik return dayViewText; 12674eb3432c5b0109376bcce9236da71e6fb4324b90RoboErik } 12684bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson 12694bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson // Calculate the time until midnight + 1 second and set the handler to 12704bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson // do run the runnable 12714bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson public static void setMidnightUpdater(Handler h, Runnable r, String timezone) { 12724bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson if (h == null || r == null || timezone == null) { 12734bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson return; 12744bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson } 12754bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson long now = System.currentTimeMillis(); 12764bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson Time time = new Time(timezone); 12774bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson time.set(now); 12784bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson long runInMillis = (24 * 3600 - time.hour * 3600 - time.minute * 60 - 12794bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson time.second + 1) * 1000; 12804bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson h.removeCallbacks(r); 12814bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson h.postDelayed(r, runInMillis); 12824bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson } 12834bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson 12844bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson // Stop the midnight update thread 12854bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson public static void resetMidnightUpdater(Handler h, Runnable r) { 12864bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson if (h == null || r == null) { 12874bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson return; 12884bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson } 12894bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson h.removeCallbacks(r); 12904bd4a5ccc3efcc282964e789e1e81577c64561c9Isaac Katzenelson } 129175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 129275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting /** 129375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting * Returns a string description of the specified time interval. 129475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting */ 129575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting public static String getDisplayedDatetime(long startMillis, long endMillis, long currentMillis, 129623acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting String localTimezone, boolean allDay, Context context) { 129775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // Configure date/time formatting. 129875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY; 129975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int flagsTime = DateUtils.FORMAT_SHOW_TIME; 130075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (DateFormat.is24HourFormat(context)) { 130175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting flagsTime |= DateUtils.FORMAT_24HOUR; 130275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 130375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 130475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting Time currentTime = new Time(localTimezone); 130575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting currentTime.set(currentMillis); 13064e92627c5eb10de50c49c57e116b080d48360c70Sara Ting Resources resources = context.getResources(); 130775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting String datetimeString = null; 130875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (allDay) { 130975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // All day events require special timezone adjustment. 131075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting long localStartMillis = convertAlldayUtcToLocal(null, startMillis, localTimezone); 131175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting long localEndMillis = convertAlldayUtcToLocal(null, endMillis, localTimezone); 131275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (singleDayEvent(localStartMillis, localEndMillis, currentTime.gmtoff)) { 131375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // If possible, use "Today" or "Tomorrow" instead of a full date string. 13144e92627c5eb10de50c49c57e116b080d48360c70Sara Ting int todayOrTomorrow = isTodayOrTomorrow(context.getResources(), 131575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting localStartMillis, currentMillis, currentTime.gmtoff); 13164e92627c5eb10de50c49c57e116b080d48360c70Sara Ting if (TODAY == todayOrTomorrow) { 13174e92627c5eb10de50c49c57e116b080d48360c70Sara Ting datetimeString = resources.getString(R.string.today); 13184e92627c5eb10de50c49c57e116b080d48360c70Sara Ting } else if (TOMORROW == todayOrTomorrow) { 13194e92627c5eb10de50c49c57e116b080d48360c70Sara Ting datetimeString = resources.getString(R.string.tomorrow); 13204e92627c5eb10de50c49c57e116b080d48360c70Sara Ting } 132175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 1322059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting if (datetimeString == null) { 1323059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting // For multi-day allday events or single-day all-day events that are not 1324059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting // today or tomorrow, use framework formatter. 1325059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault()); 1326059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting datetimeString = DateUtils.formatDateRange(context, f, startMillis, 1327059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting endMillis, flagsDate, Time.TIMEZONE_UTC).toString(); 1328059117da0141f5e3c6ce479dd87c6f8d8365e7e1Sara Ting } 132975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } else { 133075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (singleDayEvent(startMillis, endMillis, currentTime.gmtoff)) { 13314e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // Format the time. 133275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting String timeString = Utils.formatDateRange(context, startMillis, endMillis, 133375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting flagsTime); 13344e92627c5eb10de50c49c57e116b080d48360c70Sara Ting 13354e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // If possible, use "Today" or "Tomorrow" instead of a full date string. 13364e92627c5eb10de50c49c57e116b080d48360c70Sara Ting int todayOrTomorrow = isTodayOrTomorrow(context.getResources(), startMillis, 13374e92627c5eb10de50c49c57e116b080d48360c70Sara Ting currentMillis, currentTime.gmtoff); 13384e92627c5eb10de50c49c57e116b080d48360c70Sara Ting if (TODAY == todayOrTomorrow) { 13394e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // Example: "Today at 1:00pm - 2:00 pm" 13404e92627c5eb10de50c49c57e116b080d48360c70Sara Ting datetimeString = resources.getString(R.string.today_at_time_fmt, 13414e92627c5eb10de50c49c57e116b080d48360c70Sara Ting timeString); 13424e92627c5eb10de50c49c57e116b080d48360c70Sara Ting } else if (TOMORROW == todayOrTomorrow) { 13434e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // Example: "Tomorrow at 1:00pm - 2:00 pm" 13444e92627c5eb10de50c49c57e116b080d48360c70Sara Ting datetimeString = resources.getString(R.string.tomorrow_at_time_fmt, 13454e92627c5eb10de50c49c57e116b080d48360c70Sara Ting timeString); 13464e92627c5eb10de50c49c57e116b080d48360c70Sara Ting } else { 13474e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // Format the full date. Example: "Thursday, April 12, 1:00pm - 2:00pm" 13484e92627c5eb10de50c49c57e116b080d48360c70Sara Ting String dateString = Utils.formatDateRange(context, startMillis, endMillis, 13494e92627c5eb10de50c49c57e116b080d48360c70Sara Ting flagsDate); 13504e92627c5eb10de50c49c57e116b080d48360c70Sara Ting datetimeString = resources.getString(R.string.date_time_fmt, dateString, 13514e92627c5eb10de50c49c57e116b080d48360c70Sara Ting timeString); 13524e92627c5eb10de50c49c57e116b080d48360c70Sara Ting } 135375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } else { 135475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // For multiday events, shorten day/month names. 135575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm" 135675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH | 135775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting DateUtils.FORMAT_ABBREV_WEEKDAY; 135875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting datetimeString = Utils.formatDateRange(context, startMillis, endMillis, 135975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting flagsDatetime); 136075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 136123acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting } 136223acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting return datetimeString; 136323acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting } 136475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 136523acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting /** 136623acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting * Returns the timezone to display in the event info, if the local timezone is different 136723acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting * from the event timezone. Otherwise returns null. 136823acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting */ 136923acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting public static String getDisplayedTimezone(long startMillis, String localTimezone, 137023acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting String eventTimezone) { 137123acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting String tzDisplay = null; 137223acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting if (!TextUtils.equals(localTimezone, eventTimezone)) { 137323acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting // Figure out if this is in DST 137423acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting TimeZone tz = TimeZone.getTimeZone(localTimezone); 137523acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting if (tz == null || tz.getID().equals("GMT")) { 137623acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting tzDisplay = localTimezone; 137723acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting } else { 137823acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting Time startTime = new Time(localTimezone); 137923acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting startTime.set(startMillis); 138023acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting tzDisplay = tz.getDisplayName(startTime.isDst != 0, TimeZone.SHORT); 138175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 138275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 138323acd2668d65c2282f7aa922b4e86a7713d9b2faSara Ting return tzDisplay; 138475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 138575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 138675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting /** 138775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting * Returns whether the specified time interval is in a single day. 138875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting */ 138975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting private static boolean singleDayEvent(long startMillis, long endMillis, long localGmtOffset) { 139075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (startMillis == endMillis) { 139175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting return true; 139275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 139375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 139475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // An event ending at midnight should still be a single-day event, so check 139575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting // time end-1. 139675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int startDay = Time.getJulianDay(startMillis, localGmtOffset); 139775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int endDay = Time.getJulianDay(endMillis - 1, localGmtOffset); 139875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting return startDay == endDay; 139975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 140075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 14014e92627c5eb10de50c49c57e116b080d48360c70Sara Ting // Using int constants as a return value instead of an enum to minimize resources. 14024e92627c5eb10de50c49c57e116b080d48360c70Sara Ting private static final int TODAY = 1; 14034e92627c5eb10de50c49c57e116b080d48360c70Sara Ting private static final int TOMORROW = 2; 14044e92627c5eb10de50c49c57e116b080d48360c70Sara Ting private static final int NONE = 0; 14054e92627c5eb10de50c49c57e116b080d48360c70Sara Ting 140675f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting /** 14074e92627c5eb10de50c49c57e116b080d48360c70Sara Ting * Returns TODAY or TOMORROW if applicable. Otherwise returns NONE. 140875f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting */ 14094e92627c5eb10de50c49c57e116b080d48360c70Sara Ting private static int isTodayOrTomorrow(Resources r, long dayMillis, 141075f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting long currentMillis, long localGmtOffset) { 141175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int startDay = Time.getJulianDay(dayMillis, localGmtOffset); 141275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int currentDay = Time.getJulianDay(currentMillis, localGmtOffset); 141375f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting 141475f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting int days = startDay - currentDay; 141575f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting if (days == 1) { 14164e92627c5eb10de50c49c57e116b080d48360c70Sara Ting return TOMORROW; 141775f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } else if (days == 0) { 14184e92627c5eb10de50c49c57e116b080d48360c70Sara Ting return TODAY; 141975f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } else { 14204e92627c5eb10de50c49c57e116b080d48360c70Sara Ting return NONE; 142175f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 142275f53668f94c3ced9d3cc8583d7e45ce725ff9deSara Ting } 1423d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1424d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting /** 1425d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * Create an intent for emailing attendees of an event. 1426d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * 1427d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * @param resources The resources for translating strings. 1428d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * @param eventTitle The title of the event to use as the email subject. 1429e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * @param body The default text for the email body. 1430d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * @param toEmails The list of emails for the 'to' line. 1431d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * @param ccEmails The list of emails for the 'cc' line. 1432d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting * @param ownerAccount The owner account to use as the email sender. 1433d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting */ 1434d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting public static Intent createEmailAttendeesIntent(Resources resources, String eventTitle, 1435e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan String body, List<String> toEmails, List<String> ccEmails, String ownerAccount) { 1436d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting List<String> toList = toEmails; 1437d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting List<String> ccList = ccEmails; 1438d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (toEmails.size() <= 0) { 1439d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (ccEmails.size() <= 0) { 1440d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // TODO: Return a SEND intent if no one to email to, to at least populate 1441d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // a draft email with the subject (and no recipients). 1442e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan throw new IllegalArgumentException("Both toEmails and ccEmails are empty."); 1443d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1444d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1445d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Email app does not work with no "to" recipient. Move all 'cc' to 'to' 1446d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // in this case. 1447d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting toList = ccEmails; 1448d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting ccList = null; 1449d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1450d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1451d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Use the event title as the email subject (prepended with 'Re: '). 1452d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting String subject = null; 1453d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (eventTitle != null) { 1454d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting subject = resources.getString(R.string.email_subject_prefix) + eventTitle; 1455d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1456d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1457d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Use the SENDTO intent with a 'mailto' URI, because using SEND will cause 1458d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // the picker to show apps like text messaging, which does not make sense 1459d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // for email addresses. We put all data in the URI instead of using the extra 1460d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Intent fields (ie. EXTRA_CC, etc) because some email apps might not handle 1461d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // those (though gmail does). 1462d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting Uri.Builder uriBuilder = new Uri.Builder(); 1463d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting uriBuilder.scheme("mailto"); 1464d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1465d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // We will append the first email to the 'mailto' field later (because the 1466d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // current state of the Email app requires it). Add the remaining 'to' values 1467d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // here. When the email codebase is updated, we can simplify this. 1468d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (toList.size() > 1) { 1469d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting for (int i = 1; i < toList.size(); i++) { 1470d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // The Email app requires repeated parameter settings instead of 1471d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // a single comma-separated list. 1472d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting uriBuilder.appendQueryParameter("to", toList.get(i)); 1473d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1474d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1475d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1476d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Add the subject parameter. 1477d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (subject != null) { 1478d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting uriBuilder.appendQueryParameter("subject", subject); 1479d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1480d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1481e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan // Add the subject parameter. 1482e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan if (body != null) { 1483e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan uriBuilder.appendQueryParameter("body", body); 1484e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 1485e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 1486d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Add the cc parameters. 1487d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (ccList != null && ccList.size() > 0) { 1488d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting for (String email : ccList) { 1489d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting uriBuilder.appendQueryParameter("cc", email); 1490d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1491d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1492d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1493d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Insert the first email after 'mailto:' in the URI manually since Uri.Builder 1494d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // doesn't seem to have a way to do this. 1495d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting String uri = uriBuilder.toString(); 1496d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting if (uri.startsWith("mailto:")) { 1497d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting StringBuilder builder = new StringBuilder(uri); 1498d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting builder.insert(7, Uri.encode(toList.get(0))); 1499d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting uri = builder.toString(); 1500d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1501d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1502d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // Start the email intent. Email from the account of the calendar owner in case there 1503d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting // are multiple email accounts. 1504d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting Intent emailIntent = new Intent(android.content.Intent.ACTION_SENDTO, Uri.parse(uri)); 1505d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting emailIntent.putExtra("fromAccountString", ownerAccount); 1506e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting 1507e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting // Workaround a Email bug that overwrites the body with this intent extra. If not 1508e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting // set, it clears the body. 1509e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting if (body != null) { 1510e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting emailIntent.putExtra(Intent.EXTRA_TEXT, body); 1511e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting } 1512e6baa6a8ca06655e5d31edbca010795e439666fdSara Ting 1513d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting return Intent.createChooser(emailIntent, resources.getString(R.string.email_picker_label)); 1514d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1515d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting 1516d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting /** 1517ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting * Example fake email addresses used as attendee emails are resources like conference rooms, 1518ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting * or another calendar, etc. These all end in "calendar.google.com". 1519d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting */ 1520ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting public static boolean isValidEmail(String email) { 1521ddbc002f3471e224fc112d406116e396ee1c50aeSara Ting return email != null && !email.endsWith(MACHINE_GENERATED_ADDRESS); 1522d9d123dfa366f3217e8f5fbca06b92ab674384b9Sara Ting } 1523c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1524c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson /** 1525247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting * Returns true if: 1526247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting * (1) the email is not a resource like a conference room or another calendar. 1527247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting * Catch most of these by filtering out suffix calendar.google.com. 1528247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting * (2) the email is not equal to the sync account to prevent mailing himself. 1529247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting */ 1530247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting public static boolean isEmailableFrom(String email, String syncAccountName) { 1531247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting return Utils.isValidEmail(email) && !email.equals(syncAccountName); 1532247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting } 1533247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting 1534247a2f141c2b70d1ed36dfba708667edd86d4b61Sara Ting /** 1535c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson * Inserts a drawable with today's day into the today's icon in the option menu 1536c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson * @param icon - today's icon from the options menu 1537c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson */ 1538c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson public static void setTodayIcon(LayerDrawable icon, Context c, String timezone) { 1539c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson DayOfMonthDrawable today; 1540c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1541c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson // Reuse current drawable if possible 1542c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson Drawable currentDrawable = icon.findDrawableByLayerId(R.id.today_icon_day); 1543c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson if (currentDrawable != null && currentDrawable instanceof DayOfMonthDrawable) { 1544c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson today = (DayOfMonthDrawable)currentDrawable; 1545c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } else { 1546c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson today = new DayOfMonthDrawable(c); 1547c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1548c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson // Set the day and update the icon 1549c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson Time now = new Time(timezone); 1550c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson now.setToNow(); 1551c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson now.normalize(false); 1552c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson today.setDayOfMonth(now.monthDay); 1553c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson icon.mutate(); 1554c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson icon.setDrawableByLayerId(R.id.today_icon_day, today); 1555c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1556c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1557c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson private static class CalendarBroadcastReceiver extends BroadcastReceiver { 1558c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1559c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson Runnable mCallBack; 1560c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1561c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson public CalendarBroadcastReceiver(Runnable callback) { 1562c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson super(); 1563c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson mCallBack = callback; 1564c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1565c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson @Override 1566c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson public void onReceive(Context context, Intent intent) { 1567c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED) || 1568c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson intent.getAction().equals(Intent.ACTION_TIME_CHANGED) || 1569c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED) || 1570c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) { 1571c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson if (mCallBack != null) { 1572c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson mCallBack.run(); 1573c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1574c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1575c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1576c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1577c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1578c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson public static BroadcastReceiver setTimeChangesReceiver(Context c, Runnable callback) { 1579c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson IntentFilter filter = new IntentFilter(); 1580c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson filter.addAction(Intent.ACTION_TIME_CHANGED); 1581c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson filter.addAction(Intent.ACTION_DATE_CHANGED); 1582c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); 1583c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson filter.addAction(Intent.ACTION_LOCALE_CHANGED); 1584c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1585c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson CalendarBroadcastReceiver r = new CalendarBroadcastReceiver(callback); 1586c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson c.registerReceiver(r, filter); 1587c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson return r; 1588c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1589c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson 1590c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson public static void clearTimeChangesReceiver(Context c, BroadcastReceiver r) { 1591c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson c.unregisterReceiver(r); 1592c999316fd5a6a8c6291a885e0e239cedf86b59b2Isaac Katzenelson } 1593e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 1594e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan /** 1595e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * Get a list of quick responses used for emailing guests from the 1596e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * SharedPreferences. If not are found, get the hard coded ones that shipped 1597e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * with the app 1598e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * 1599e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * @param context 1600e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan * @return a list of quick responses. 1601e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan */ 1602e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan public static String[] getQuickResponses(Context context) { 1603e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan String[] s = Utils.getSharedPreference(context, KEY_QUICK_RESPONSES, (String[]) null); 1604e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 1605e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan if (s == null) { 1606e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan s = context.getResources().getStringArray(R.array.quick_response_defaults); 1607e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 1608e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan 1609e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan return s; 1610e98dca7e0042f7b36788b0e5d8dc6e94048c895cMichael Chan } 1611dacfb663d63253880090151fde87bd9da9a59347Sara Ting 1612dacfb663d63253880090151fde87bd9da9a59347Sara Ting /** 1613dacfb663d63253880090151fde87bd9da9a59347Sara Ting * Return the app version code. 1614dacfb663d63253880090151fde87bd9da9a59347Sara Ting */ 1615dacfb663d63253880090151fde87bd9da9a59347Sara Ting public static String getVersionCode(Context context) { 1616dacfb663d63253880090151fde87bd9da9a59347Sara Ting if (sVersion == null) { 1617dacfb663d63253880090151fde87bd9da9a59347Sara Ting try { 1618dacfb663d63253880090151fde87bd9da9a59347Sara Ting sVersion = context.getPackageManager().getPackageInfo( 1619dacfb663d63253880090151fde87bd9da9a59347Sara Ting context.getPackageName(), 0).versionName; 1620dacfb663d63253880090151fde87bd9da9a59347Sara Ting } catch (PackageManager.NameNotFoundException e) { 1621dacfb663d63253880090151fde87bd9da9a59347Sara Ting // Can't find version; just leave it blank. 1622dacfb663d63253880090151fde87bd9da9a59347Sara Ting Log.e(TAG, "Error finding package " + context.getApplicationInfo().packageName); 1623dacfb663d63253880090151fde87bd9da9a59347Sara Ting } 1624dacfb663d63253880090151fde87bd9da9a59347Sara Ting } 1625dacfb663d63253880090151fde87bd9da9a59347Sara Ting return sVersion; 1626dacfb663d63253880090151fde87bd9da9a59347Sara Ting } 1627ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein 1628ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein /** 1629ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * Checks the server for an updated list of Calendars (in the background). 1630ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * 1631ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * If a Calendar is added on the web (and it is selected and not 1632ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * hidden) then it will be added to the list of calendars on the phone 1633ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * (when this finishes). When a new calendar from the 1634ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * web is added to the phone, then the events for that calendar are also 1635ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * downloaded from the web. 1636ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * 1637ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * This sync is done automatically in the background when the 1638ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * SelectCalendars activity and fragment are started. 1639ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * 1640ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein * @param account - The account to sync. May be null to sync all accounts. 1641ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein */ 1642ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein public static void startCalendarMetafeedSync(Account account) { 1643ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein Bundle extras = new Bundle(); 1644ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 1645ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein extras.putBoolean("metafeedonly", true); 1646ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein ContentResolver.requestSync(account, Calendars.CONTENT_URI.getAuthority(), extras); 1647ceae8dba1fd70adeb0b07861f598794ec69d7767Sam Blitzstein } 16487e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 16497e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /** 16507e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Replaces stretches of text that look like addresses and phone numbers with clickable 16517e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * links. If lastDitchGeo is true, then if no links are found in the textview, the entire 165229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * string will be converted to a single geo link. Any spans that may have previously been 165329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * in the text will be cleared out. 16547e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * <p> 16557e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * This is really just an enhanced version of Linkify.addLinks(). 165629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 165729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * @param text - The string to search for links. 165829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * @param lastDitchGeo - If no links are found, turn the entire string into one geo link. 165929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * @return Spannable object containing the list of URL spans found. 16607e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 166129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein public static Spannable extendedLinkify(String text, boolean lastDitchGeo) { 166229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein // We use a copy of the string argument so it's available for later if necessary. 166329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Spannable spanText = SpannableString.valueOf(text); 166429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 16657e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 16667e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * If the text includes a street address like "1600 Amphitheater Parkway, 94043", 16677e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * the current Linkify code will identify "94043" as a phone number and invite 16687e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * you to dial it (and not provide a map link for the address). For outside US, 16697e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * use Linkify result iff it spans the entire text. Otherwise send the user to maps. 16707e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 16717e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein String defaultPhoneRegion = System.getProperty("user.region", "US"); 16727e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (!defaultPhoneRegion.equals("US")) { 167329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Linkify.addLinks(spanText, Linkify.ALL); 16747e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 16757e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // If Linkify links the entire text, use that result. 167629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein URLSpan[] spans = spanText.getSpans(0, spanText.length(), URLSpan.class); 167729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (spans.length == 1) { 167829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int linkStart = spanText.getSpanStart(spans[0]); 167929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int linkEnd = spanText.getSpanEnd(spans[0]); 168029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (linkStart <= indexFirstNonWhitespaceChar(spanText) && 168129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein linkEnd >= indexLastNonWhitespaceChar(spanText) + 1) { 168229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein return spanText; 16837e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 16847e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 16857e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 168629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein // Otherwise, to be cautious and to try to prevent false positives, reset the spannable. 168729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein spanText = SpannableString.valueOf(text); 168829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein // If lastDitchGeo is true, default the entire string to geo. 168929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (lastDitchGeo && !text.isEmpty()) { 169029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q="); 1691a92e760da2c9e6faf7656a277f72676dfcec82afSam Blitzstein } 169229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein return spanText; 16937e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 16947e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 16957e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 16967e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * For within US, we want to have better recognition of phone numbers without losing 16977e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * any of the existing annotations. Ideally this would be addressed by improving Linkify. 16987e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * For now we manage it as a second pass over the text. 16997e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 17007e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * URIs and e-mail addresses are pretty easy to pick out of text. Phone numbers 17017e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * are a bit tricky because they have radically different formats in different 17027e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * countries, in terms of both the digits and the way in which they are commonly 17037e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * written or presented (e.g. the punctuation and spaces in "(650) 555-1212"). 17047e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * The expected format of a street address is defined in WebView.findAddress(). It's 17057e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * pretty narrowly defined, so it won't often match. 17067e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 17077e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * The RFC 3966 specification defines the format of a "tel:" URI. 17087e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 17097e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Start by letting Linkify find anything that isn't a phone number. We have to let it 17107e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * run first because every invocation removes all previous URLSpan annotations. 17117e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 17127e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Ideally we'd use the external/libphonenumber routines, but those aren't available 17137e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * to unbundled applications. 17147e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 171529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein boolean linkifyFoundLinks = Linkify.addLinks(spanText, 17167e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein Linkify.ALL & ~(Linkify.PHONE_NUMBERS)); 17177e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17187e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 171929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Get a list of any spans created by Linkify, for the coordinate overlapping span check. 17207e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 172129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein URLSpan[] existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class); 17227e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17237e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 172429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Check for coordinates. 172529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * This must be done before phone numbers because longitude may look like a phone number. 17267e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 172729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Matcher coordMatcher = COORD_PATTERN.matcher(spanText); 172829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int coordCount = 0; 172929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein while (coordMatcher.find()) { 173029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int start = coordMatcher.start(); 173129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int end = coordMatcher.end(); 173229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (spanWillOverlap(spanText, existingSpans, start, end)) { 173329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein continue; 173429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein } 173529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 173629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein URLSpan span = new URLSpan("geo:0,0?q=" + coordMatcher.group()); 173729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 173829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein coordCount++; 17397e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 17407e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17417e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 174229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Update the list of existing spans, for the phone number overlapping span check. 17437e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 174429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein existingSpans = spanText.getSpans(0, spanText.length(), URLSpan.class); 174529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 174629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein /* 174729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Search for phone numbers. 174829dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * 174929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * Some URIs contain strings of digits that look like phone numbers. If both the URI 175029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * scanner and the phone number scanner find them, we want the URI link to win. Since 175129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * the URI scanner runs first, we just need to avoid creating overlapping spans. 175229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein */ 175329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein int[] phoneSequences = findNanpPhoneNumbers(text); 17547e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17557e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 17567e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Insert spans for the numbers we found. We generate "tel:" URIs. 17577e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 17587e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int phoneCount = 0; 17597e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (int match = 0; match < phoneSequences.length / 2; match++) { 17607e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int start = phoneSequences[match*2]; 17617e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int end = phoneSequences[match*2 + 1]; 17627e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17637e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (spanWillOverlap(spanText, existingSpans, start, end)) { 17647e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein continue; 17657e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 17667e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17677e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 17687e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * The Linkify code takes the matching span and strips out everything that isn't a 17697e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * digit or '+' sign. We do the same here. Extension numbers will get appended 17707e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * without a separator, but the dialer wasn't doing anything useful with ";ext=" 17717e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * anyway. 17727e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 17737e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17747e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein //String dialStr = phoneUtil.format(match.number(), 17757e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // PhoneNumberUtil.PhoneNumberFormat.RFC3966); 17767e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein StringBuilder dialBuilder = new StringBuilder(); 17777e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (int i = start; i < end; i++) { 17787e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein char ch = spanText.charAt(i); 17797e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (ch == '+' || Character.isDigit(ch)) { 17807e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein dialBuilder.append(ch); 17817e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 17827e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 17837e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein URLSpan span = new URLSpan("tel:" + dialBuilder.toString()); 17847e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 17857e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein spanText.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 17867e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein phoneCount++; 17877e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 17887e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 178929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein /* 179029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein * If lastDitchGeo, and no other links have been found, set the entire string as a geo link. 179129dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein */ 179229dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (lastDitchGeo && !text.isEmpty() && 179329dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein !linkifyFoundLinks && phoneCount == 0 && coordCount == 0) { 17947e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (Log.isLoggable(TAG, Log.VERBOSE)) { 17957e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein Log.v(TAG, "No linkification matches, using geo default"); 17967e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 179729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Linkify.addLinks(spanText, mWildcardPattern, "geo:0,0?q="); 17987e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 179929dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein 180029dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein return spanText; 18017e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18027e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18037e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static int indexFirstNonWhitespaceChar(CharSequence str) { 18047e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (int i = 0; i < str.length(); i++) { 18057e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (!Character.isWhitespace(str.charAt(i))) { 18067e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return i; 18077e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18087e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18097e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return -1; 18107e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18117e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18127e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static int indexLastNonWhitespaceChar(CharSequence str) { 18137e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (int i = str.length() - 1; i >= 0; i--) { 18147e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (!Character.isWhitespace(str.charAt(i))) { 18157e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return i; 18167e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18177e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18187e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return -1; 18197e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18207e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18217e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /** 18227e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Finds North American Numbering Plan (NANP) phone numbers in the input text. 18237e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 18247e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * @param text The text to scan. 18257e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * @return A list of [start, end) pairs indicating the positions of phone numbers in the input. 18267e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 18277e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // @VisibleForTesting 18287e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein static int[] findNanpPhoneNumbers(CharSequence text) { 18297e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein ArrayList<Integer> list = new ArrayList<Integer>(); 18307e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18317e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int startPos = 0; 18327e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int endPos = text.length() - NANP_MIN_DIGITS + 1; 18337e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (endPos < 0) { 18347e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return new int[] {}; 18357e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18367e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18377e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 18387e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * We can't just strip the whitespace out and crunch it down, because the whitespace 18397e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * is significant. March through, trying to figure out where numbers start and end. 18407e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 18417e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein while (startPos < endPos) { 18427e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // skip whitespace 18437e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein while (Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) { 18447e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein startPos++; 18457e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18467e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (startPos == endPos) { 18477e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein break; 18487e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18497e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18507e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // check for a match at this position 18517e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int matchEnd = findNanpMatchEnd(text, startPos); 18527e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (matchEnd > startPos) { 18537e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein list.add(startPos); 18547e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein list.add(matchEnd); 18557e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein startPos = matchEnd; // skip past match 18567e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else { 18577e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // skip to next whitespace char 18587e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein while (!Character.isWhitespace(text.charAt(startPos)) && startPos < endPos) { 18597e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein startPos++; 18607e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18617e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18627e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18637e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18647e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int[] result = new int[list.size()]; 18657e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (int i = list.size() - 1; i >= 0; i--) { 18667e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein result[i] = list.get(i); 18677e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18687e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return result; 18697e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18707e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18717e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /** 18727e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Checks to see if there is a valid phone number in the input, starting at the specified 18737e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * offset. If so, the index of the last character + 1 is returned. The input is assumed 18747e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * to begin with a non-whitespace character. 18757e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 18767e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * @return Exclusive end position, or -1 if not a match. 18777e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 18787e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static int findNanpMatchEnd(CharSequence text, int startPos) { 18797e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /* 18807e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * A few interesting cases: 18817e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 94043 # too short, ignore 18827e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 123456789012 # too long, ignore 18837e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * +1 (650) 555-1212 # 11 digits, spaces 18847e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * (650) 555 5555 # Second space, only when first is present. 18857e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * (650) 555-1212, (650) 555-1213 # two numbers, return first 18867e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 1-650-555-1212 # 11 digits with leading '1' 18877e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * *#650.555.1212#*! # 10 digits, include #*, ignore trailing '!' 18887e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 555.1212 # 7 digits 18897e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * 18907e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * For the most part we want to break on whitespace, but it's common to leave a space 18917e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * between the initial '1' and/or after the area code. 18927e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 18937e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 18947e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // Check for "tel:" URI prefix. 18957e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (text.length() > startPos+4 18967e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein && text.subSequence(startPos, startPos+4).toString().equalsIgnoreCase("tel:")) { 18977e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein startPos += 4; 18987e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 18997e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19007e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int endPos = text.length(); 19017e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int curPos = startPos; 19027e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int foundDigits = 0; 19037e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein char firstDigit = 'x'; 19047e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein boolean foundWhiteSpaceAfterAreaCode = false; 19057e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19067e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein while (curPos <= endPos) { 19077e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein char ch; 19087e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (curPos < endPos) { 19097e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein ch = text.charAt(curPos); 19107e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else { 19117e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein ch = 27; // fake invalid symbol at end to trigger loop break 19127e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19137e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19147e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (Character.isDigit(ch)) { 19157e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (foundDigits == 0) { 19167e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein firstDigit = ch; 19177e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19187e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein foundDigits++; 19197e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (foundDigits > NANP_MAX_DIGITS) { 19207e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // too many digits, stop early 19217e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return -1; 19227e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19237e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else if (Character.isWhitespace(ch)) { 19247e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if ( (firstDigit == '1' && foundDigits == 4) || 19257e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein (foundDigits == 3)) { 19267e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein foundWhiteSpaceAfterAreaCode = true; 19277e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else if (firstDigit == '1' && foundDigits == 1) { 19287e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else if (foundWhiteSpaceAfterAreaCode 19297e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein && ( (firstDigit == '1' && (foundDigits == 7)) || (foundDigits == 6))) { 19307e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else { 19317e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein break; 19327e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19337e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } else if (NANP_ALLOWED_SYMBOLS.indexOf(ch) == -1) { 19347e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein break; 19357e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19367e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // else it's an allowed symbol 19377e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19387e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein curPos++; 19397e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19407e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19417e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if ((firstDigit != '1' && (foundDigits == 7 || foundDigits == 10)) || 19427e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein (firstDigit == '1' && foundDigits == 11)) { 19437e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // match 19447e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return curPos; 19457e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19467e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19477e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return -1; 19487e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19497e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19507e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein /** 19517e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein * Determines whether a new span at [start,end) will overlap with any existing span. 19527e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein */ 19537e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein private static boolean spanWillOverlap(Spannable spanText, URLSpan[] spanList, int start, 19547e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int end) { 19557e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if (start == end) { 19567e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein // empty span, ignore 19577e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return false; 19587e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19597e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein for (URLSpan span : spanList) { 19607e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int existingStart = spanText.getSpanStart(span); 19617e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein int existingEnd = spanText.getSpanEnd(span); 19627e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein if ((start >= existingStart && start < existingEnd) || 19637e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein end > existingStart && end <= existingEnd) { 196429dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein if (Log.isLoggable(TAG, Log.VERBOSE)) { 196529dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein CharSequence seq = spanText.subSequence(start, end); 196629dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein Log.v(TAG, "Not linkifying " + seq + " as phone number due to overlap"); 196729dc76a401b073bcd69d610817e3781fc9ebe5fcSam Blitzstein } 19687e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return true; 19697e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19707e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19717e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 19727e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein return false; 19737e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein } 19747e19bf984bc280c0cc034adf1dfa8840c75a698dSam Blitzstein 1975146de36083f6ce8b7e8a1f974d3990594a36bfecThe Android Open Source Project} 1976