aboutsummaryrefslogtreecommitdiff
path: root/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android')
-rw-r--r--src/com/android/car/calendar/CarCalendarActivity.java30
-rw-r--r--src/com/android/car/calendar/CarCalendarView.java39
-rw-r--r--src/com/android/car/calendar/CarCalendarViewModel.java13
-rw-r--r--src/com/android/car/calendar/DrawableStateImageButton.java72
-rw-r--r--src/com/android/car/calendar/EventCalendarItem.java7
-rw-r--r--src/com/android/car/calendar/common/Dialer.java15
-rw-r--r--src/com/android/car/calendar/common/Event.java62
-rw-r--r--src/com/android/car/calendar/common/EventDescriptions.java68
-rw-r--r--src/com/android/car/calendar/common/EventsLiveData.java39
9 files changed, 191 insertions, 154 deletions
diff --git a/src/com/android/car/calendar/CarCalendarActivity.java b/src/com/android/car/calendar/CarCalendarActivity.java
index 97e2031..94e0db6 100644
--- a/src/com/android/car/calendar/CarCalendarActivity.java
+++ b/src/com/android/car/calendar/CarCalendarActivity.java
@@ -20,6 +20,7 @@ import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.StrictMode;
+import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -32,6 +33,8 @@ import androidx.lifecycle.ViewModelProvider;
import com.android.car.calendar.common.CalendarFormatter;
import com.android.car.calendar.common.Dialer;
import com.android.car.calendar.common.Navigator;
+import com.android.car.ui.core.CarUi;
+import com.android.car.ui.toolbar.ToolbarController;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
@@ -55,10 +58,16 @@ public class CarCalendarActivity extends FragmentActivity {
super.onCreate(savedInstanceState);
maybeEnableStrictMode();
+ ToolbarController toolbar = CarUi.requireToolbar(this);
+ toolbar.setTitle(R.string.app_name);
+
// Tests can set fake dependencies before onCreate.
if (mDependencies == null) {
mDependencies = new Dependencies(
- Locale.getDefault(), Clock.systemDefaultZone(), getContentResolver());
+ Locale.getDefault(),
+ Clock.systemDefaultZone(),
+ getContentResolver(),
+ getSystemService(TelephonyManager.class));
}
CarCalendarViewModel carCalendarViewModel =
@@ -67,6 +76,7 @@ public class CarCalendarActivity extends FragmentActivity {
new CarCalendarViewModelFactory(
mDependencies.mResolver,
mDependencies.mLocale,
+ mDependencies.mTelephonyManager,
mDependencies.mClock))
.get(CarCalendarViewModel.class);
@@ -142,12 +152,18 @@ public class CarCalendarActivity extends FragmentActivity {
private static class CarCalendarViewModelFactory implements ViewModelProvider.Factory {
private final ContentResolver mResolver;
+ private final TelephonyManager mTelephonyManager;
private final Locale mLocale;
private final Clock mClock;
- CarCalendarViewModelFactory(ContentResolver resolver, Locale locale, Clock clock) {
+ CarCalendarViewModelFactory(
+ ContentResolver resolver,
+ Locale locale,
+ TelephonyManager telephonyManager,
+ Clock clock) {
mResolver = resolver;
mLocale = locale;
+ mTelephonyManager = telephonyManager;
mClock = clock;
}
@@ -155,7 +171,7 @@ public class CarCalendarActivity extends FragmentActivity {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> aClass) {
- return (T) new CarCalendarViewModel(mResolver, mLocale, mClock);
+ return (T) new CarCalendarViewModel(mResolver, mLocale, mTelephonyManager, mClock);
}
}
@@ -163,11 +179,17 @@ public class CarCalendarActivity extends FragmentActivity {
private final Locale mLocale;
private final Clock mClock;
private final ContentResolver mResolver;
+ private final TelephonyManager mTelephonyManager;
- Dependencies(Locale locale, Clock clock, ContentResolver resolver) {
+ Dependencies(
+ Locale locale,
+ Clock clock,
+ ContentResolver resolver,
+ TelephonyManager telephonyManager) {
mLocale = locale;
mClock = clock;
mResolver = resolver;
+ mTelephonyManager = telephonyManager;
}
}
}
diff --git a/src/com/android/car/calendar/CarCalendarView.java b/src/com/android/car/calendar/CarCalendarView.java
index 07b9516..1a63588 100644
--- a/src/com/android/car/calendar/CarCalendarView.java
+++ b/src/com/android/car/calendar/CarCalendarView.java
@@ -17,7 +17,6 @@
package com.android.car.calendar;
import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
import android.Manifest;
import android.util.Log;
@@ -25,10 +24,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.car.calendar.common.CalendarFormatter;
import com.android.car.calendar.common.Dialer;
@@ -64,15 +63,8 @@ class CarCalendarView {
/** Holds an instance of either {@link LocalDate} or {@link Event} for each item in the list. */
private final List<CalendarItem> mRecyclerViewItems = new ArrayList<>();
- private final RecyclerView.Adapter mAdapter = new EventRecyclerViewAdapter();
- private final Observer<ImmutableList<Event>> mEventsObserver =
- events -> {
- if (DEBUG) Log.d(TAG, "Events changed");
- updateRecyclerViewItems(events);
-
- // TODO(jdp) Only change the affected items (DiffUtil) to allow animated changes.
- mAdapter.notifyDataSetChanged();
- };
+ private final RecyclerView.Adapter<ViewHolder> mAdapter = new EventRecyclerViewAdapter();
+ private final Observer<ImmutableList<Event>> mEventsObserver = this::onEventsChanged;
CarCalendarView(
CarCalendarActivity carCalendarActivity,
@@ -102,23 +94,30 @@ class CarCalendarView {
private void showWithPermission() {
EventsLiveData eventsLiveData = mCarCalendarViewModel.getEventsLiveData();
eventsLiveData.observe(mCarCalendarActivity, mEventsObserver);
- updateRecyclerViewItems(verifyNotNull(eventsLiveData.getValue()));
+ }
+
+ private void onEventsChanged(ImmutableList<Event> events) {
+ updateRecyclerViewItems(events);
+
+ // TODO(jdp) Only change the affected items (DiffUtil) to allow animated changes.
+ mAdapter.notifyDataSetChanged();
}
/**
* If the events list is null there is no calendar data available. If the events list is empty
* there is calendar data but no events.
*/
- private void updateRecyclerViewItems(@Nullable ImmutableList<Event> carCalendarEvents) {
+ private void updateRecyclerViewItems(@Nullable ImmutableList<Event> events) {
+ if (DEBUG) Log.d(TAG, "Update events");
LocalDate currentDate = null;
mRecyclerViewItems.clear();
- if (carCalendarEvents == null) {
+ if (events == null) {
mNoEventsTextView.setVisibility(View.VISIBLE);
mNoEventsTextView.setText(R.string.no_calendars);
return;
}
- if (carCalendarEvents.isEmpty()) {
+ if (events.isEmpty()) {
mNoEventsTextView.setVisibility(View.VISIBLE);
mNoEventsTextView.setText(R.string.no_events);
return;
@@ -130,7 +129,7 @@ class CarCalendarView {
// add the event rows after looking at all events for the day.
List<CalendarItem> eventItems = null;
List<EventCalendarItem> allDayEventItems = null;
- for (Event event : carCalendarEvents) {
+ for (Event event : events) {
LocalDate date =
event.getDayStartInstant().atZone(ZoneId.systemDefault()).toLocalDate();
@@ -177,17 +176,15 @@ class CarCalendarView {
mRecyclerViewItems.addAll(eventItems);
}
- private class EventRecyclerViewAdapter extends RecyclerView.Adapter {
-
- @NonNull
+ private class EventRecyclerViewAdapter extends RecyclerView.Adapter<ViewHolder> {
@Override
- public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CalendarItem.Type type = CalendarItem.Type.values()[viewType];
return type.createViewHolder(parent);
}
@Override
- public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ public void onBindViewHolder(ViewHolder holder, int position) {
mRecyclerViewItems.get(position).bind(holder);
}
diff --git a/src/com/android/car/calendar/CarCalendarViewModel.java b/src/com/android/car/calendar/CarCalendarViewModel.java
index c8e80ee..337f794 100644
--- a/src/com/android/car/calendar/CarCalendarViewModel.java
+++ b/src/com/android/car/calendar/CarCalendarViewModel.java
@@ -17,7 +17,9 @@
package com.android.car.calendar;
import android.content.ContentResolver;
+import android.os.Handler;
import android.os.HandlerThread;
+import android.telephony.TelephonyManager;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -38,14 +40,17 @@ class CarCalendarViewModel extends ViewModel {
private final Clock mClock;
private final ContentResolver mResolver;
private final Locale mLocale;
+ private final TelephonyManager mTelephonyManager;
@Nullable private EventsLiveData mEventsLiveData;
- CarCalendarViewModel(ContentResolver resolver, Locale locale, Clock clock) {
+ CarCalendarViewModel(ContentResolver resolver, Locale locale,
+ TelephonyManager telephonyManager, Clock clock) {
+ mLocale = locale;
if (DEBUG) Log.d(TAG, "Creating view model");
mResolver = resolver;
+ mTelephonyManager = telephonyManager;
mHandlerThread.start();
- mLocale = locale;
mClock = clock;
}
@@ -55,9 +60,9 @@ class CarCalendarViewModel extends ViewModel {
mEventsLiveData =
new EventsLiveData(
mClock,
- mHandlerThread.getThreadHandler(),
+ new Handler(mHandlerThread.getLooper()),
mResolver,
- new EventDescriptions(mLocale),
+ new EventDescriptions(mLocale, mTelephonyManager),
new EventLocations());
}
return mEventsLiveData;
diff --git a/src/com/android/car/calendar/DrawableStateImageButton.java b/src/com/android/car/calendar/DrawableStateImageButton.java
deleted file mode 100644
index 4ebd06e..0000000
--- a/src/com/android/car/calendar/DrawableStateImageButton.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.car.calendar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ImageButton;
-
-import androidx.annotation.Nullable;
-
-import com.android.car.ui.uxr.DrawableStateView;
-
-/**
- * An {@link ImageButton} that implements {@link DrawableStateView}, for allowing additional states
- * such as ux restriction.
- *
- * @see com.android.car.ui.uxr.DrawableStateButton
- *
- * TODO(jdp) Move this to car-ui-lib.
- */
-public class DrawableStateImageButton extends ImageButton implements DrawableStateView {
-
- private int[] mState;
-
- public DrawableStateImageButton(Context context) {
- super(context);
- }
-
- public DrawableStateImageButton(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- public DrawableStateImageButton(
- Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public DrawableStateImageButton(
- Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- public void setDrawableState(int[] state) {
- mState = state;
- refreshDrawableState();
- }
-
- @Override
- public int[] onCreateDrawableState(int extraSpace) {
- if (mState == null) {
- return super.onCreateDrawableState(extraSpace);
- } else {
- return mergeDrawableStates(
- super.onCreateDrawableState(extraSpace + mState.length), mState);
- }
- }
-}
diff --git a/src/com/android/car/calendar/EventCalendarItem.java b/src/com/android/car/calendar/EventCalendarItem.java
index 0642baa..e83c21c 100644
--- a/src/com/android/car/calendar/EventCalendarItem.java
+++ b/src/com/android/car/calendar/EventCalendarItem.java
@@ -17,7 +17,6 @@
package com.android.car.calendar;
import android.Manifest;
-import android.annotation.ColorInt;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.InsetDrawable;
@@ -30,9 +29,11 @@ import android.text.style.StyleSpan;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
+import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -171,8 +172,8 @@ class EventCalendarItem implements CalendarItem {
private final TextView mTitleView;
private final TextView mDescriptionView;
- private final DrawableStateImageButton mPrimaryActionButton;
- private final DrawableStateImageButton mSecondaryActionButton;
+ private final ImageButton mPrimaryActionButton;
+ private final ImageButton mSecondaryActionButton;
private final int mCalendarIndicatorSize;
private final int mCalendarIndicatorPadding;
@ColorInt private final int mTimeTextColor;
diff --git a/src/com/android/car/calendar/common/Dialer.java b/src/com/android/car/calendar/common/Dialer.java
index 889a3c8..df843aa 100644
--- a/src/com/android/car/calendar/common/Dialer.java
+++ b/src/com/android/car/calendar/common/Dialer.java
@@ -26,6 +26,8 @@ import android.util.Log;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
+import java.util.Objects;
+
import javax.annotation.Nullable;
/** Calls the default dialer with an optional access code. */
@@ -94,5 +96,18 @@ public class Dialer {
.add("mAccess", mAccess)
.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NumberAndAccess that = (NumberAndAccess) o;
+ return mNumber.equals(that.mNumber) && Objects.equals(mAccess, that.mAccess);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mNumber, mAccess);
+ }
}
}
diff --git a/src/com/android/car/calendar/common/Event.java b/src/com/android/car/calendar/common/Event.java
index 4395d33..6f88717 100644
--- a/src/com/android/car/calendar/common/Event.java
+++ b/src/com/android/car/calendar/common/Event.java
@@ -20,6 +20,9 @@ import com.android.car.calendar.common.Dialer.NumberAndAccess;
import java.time.Duration;
import java.time.Instant;
+import java.util.Objects;
+
+import javax.annotation.Nullable;
/**
* An immutable value representing a calendar event. Should contain only details that are relevant
@@ -34,9 +37,7 @@ public final class Event {
NONE,
}
- /**
- * The details required for display of the calendar indicator.
- */
+ /** The details required for display of the calendar indicator. */
public static class CalendarDetails {
private final String mName;
private final int mColor;
@@ -53,6 +54,19 @@ public final class Event {
public String getName() {
return mName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ CalendarDetails that = (CalendarDetails) o;
+ return mColor == that.mColor && mName.equals(that.mName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mColor);
+ }
}
private final boolean mAllDay;
@@ -62,8 +76,8 @@ public final class Event {
private final Instant mDayEndInstant;
private final String mTitle;
private final Status mStatus;
- private final String mLocation;
- private final NumberAndAccess mNumberAndAccess;
+ @Nullable private final String mLocation;
+ @Nullable private final NumberAndAccess mNumberAndAccess;
private final CalendarDetails mCalendarDetails;
Event(
@@ -74,8 +88,8 @@ public final class Event {
Instant dayEndInstant,
String title,
Status status,
- String location,
- NumberAndAccess numberAndAccess,
+ @Nullable String location,
+ @Nullable NumberAndAccess numberAndAccess,
CalendarDetails calendarDetails) {
mAllDay = allDay;
mStartInstant = startInstant;
@@ -109,6 +123,7 @@ public final class Event {
return mTitle;
}
+ @Nullable
public NumberAndAccess getNumberAndAccess() {
return mNumberAndAccess;
}
@@ -117,6 +132,7 @@ public final class Event {
return mCalendarDetails;
}
+ @Nullable
public String getLocation() {
return mLocation;
}
@@ -132,4 +148,36 @@ public final class Event {
public Duration getDuration() {
return Duration.between(getStartInstant(), getEndInstant());
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Event event = (Event) o;
+ return mAllDay == event.mAllDay
+ && mStartInstant.equals(event.mStartInstant)
+ && mDayStartInstant.equals(event.mDayStartInstant)
+ && mEndInstant.equals(event.mEndInstant)
+ && mDayEndInstant.equals(event.mDayEndInstant)
+ && mTitle.equals(event.mTitle)
+ && mStatus == event.mStatus
+ && Objects.equals(mLocation, event.mLocation)
+ && Objects.equals(mNumberAndAccess, event.mNumberAndAccess)
+ && mCalendarDetails.equals(event.mCalendarDetails);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mAllDay,
+ mStartInstant,
+ mDayStartInstant,
+ mEndInstant,
+ mDayEndInstant,
+ mTitle,
+ mStatus,
+ mLocation,
+ mNumberAndAccess,
+ mCalendarDetails);
+ }
}
diff --git a/src/com/android/car/calendar/common/EventDescriptions.java b/src/com/android/car/calendar/common/EventDescriptions.java
index e6aad5b..8d2030f 100644
--- a/src/com/android/car/calendar/common/EventDescriptions.java
+++ b/src/com/android/car/calendar/common/EventDescriptions.java
@@ -16,20 +16,15 @@
package com.android.car.calendar.common;
-import static com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL;
-import static com.android.i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE;
-import static com.android.i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE_LOCAL_ONLY;
-import static com.android.i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_LONG;
-
import static com.google.common.base.Verify.verifyNotNull;
import android.net.Uri;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
import com.android.car.calendar.common.Dialer.NumberAndAccess;
-import com.android.i18n.phonenumbers.NumberParseException;
-import com.android.i18n.phonenumbers.PhoneNumberUtil;
-import com.android.i18n.phonenumbers.Phonenumber;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.LinkedHashMap;
@@ -45,37 +40,45 @@ import javax.annotation.Nullable;
public class EventDescriptions {
// Requires a phone number to include only numbers, spaces and dash, optionally a leading "+".
- // The number must be at least 6 characters.
+ // The number must be at least 6 characters and can contain " " or "-" but end with a digit.
// The access code must be at least 3 characters.
// The number and the access to include "pin" or "code" between the numbers.
private static final Pattern PHONE_PIN_PATTERN =
Pattern.compile(
- "(\\+?[\\d -]{6,})(?:.*\\b(?:PIN|code)\\b.*?([\\d,;#*]{3,}))?",
+ "(\\+?[\\d -]{6,}\\d)(?:.*\\b(?:PIN|code)\\b.*?([\\d,;#*]{3,}))?",
Pattern.CASE_INSENSITIVE);
// Matches numbers in the encoded format "<tel: ... >".
private static final Pattern TEL_PIN_PATTERN =
Pattern.compile("<tel:(\\+?[\\d -]{6,})([\\d,;#*]{3,})?>");
- private static final PhoneNumberUtil PHONE_NUMBER_UTIL = PhoneNumberUtil.getInstance();
-
// Ensure numbers are over 5 digits to reduce false positives.
- private static final int MIN_NATIONAL_NUMBER = 10_000;
+ private static final int MIN_DIGITS = 5;
- private final Locale mLocale;
+ private final String mCountryIso;
- public EventDescriptions(Locale locale) {
- mLocale = locale;
+ public EventDescriptions(Locale locale, TelephonyManager telephonyManager) {
+ String networkCountryIso = telephonyManager.getNetworkCountryIso().toUpperCase();
+ if (!Strings.isNullOrEmpty(networkCountryIso)) {
+ mCountryIso = networkCountryIso;
+ } else {
+ mCountryIso = locale.getCountry();
+ }
}
/** Find conference call data embedded in the description. */
public List<NumberAndAccess> extractNumberAndPins(String descriptionText) {
String decoded = Uri.decode(descriptionText);
+ // Use a map keyed by number to act like a set and only add a single number.
Map<String, NumberAndAccess> results = new LinkedHashMap<>();
addMatchedNumbers(decoded, results, PHONE_PIN_PATTERN);
+
+ // Add the most restrictive precise format last to replace others with the same number.
addMatchedNumbers(decoded, results, TEL_PIN_PATTERN);
- return ImmutableList.copyOf(results.values());
+
+ // Reverse order so the most precise format is first.
+ return ImmutableList.copyOf(results.values()).reverse();
}
private void addMatchedNumbers(
@@ -93,27 +96,18 @@ public class EventDescriptions {
private NumberAndAccess validNumberAndAccess(Matcher phoneFormatMatcher) {
String number = verifyNotNull(phoneFormatMatcher.group(1));
String access = phoneFormatMatcher.group(2);
- try {
- Phonenumber.PhoneNumber phoneNumber =
- PHONE_NUMBER_UTIL.parse(number, mLocale.getCountry());
- PhoneNumberUtil.ValidationResult result =
- PHONE_NUMBER_UTIL.isPossibleNumberWithReason(phoneNumber);
- if (isAcceptableResult(result)) {
- if (phoneNumber.getNationalNumber() < MIN_NATIONAL_NUMBER) {
- return null;
- }
- String formatted = PHONE_NUMBER_UTIL.format(phoneNumber, INTERNATIONAL);
- return new NumberAndAccess(formatted, access);
- }
- } catch (NumberParseException e) {
- // Ignore invalid numbers.
+
+ // Ensure that there are a minimum number of digits to reduce false positives.
+ String onlyDigits = number.replaceAll("\\D", "");
+ if (onlyDigits.length() < MIN_DIGITS) {
+ return null;
}
- return null;
- }
- private boolean isAcceptableResult(PhoneNumberUtil.ValidationResult result) {
- // The result can be too long and still valid because the US locale is used by default
- // which does not accept valid long numbers from other regions.
- return result == IS_POSSIBLE || result == IS_POSSIBLE_LOCAL_ONLY || result == TOO_LONG;
+ // Keep local numbers in local format which the dialer can make more sense of.
+ String formatted = PhoneNumberUtils.formatNumber(number, mCountryIso);
+ if (formatted == null) {
+ return null;
+ }
+ return new NumberAndAccess(formatted, access);
}
}
diff --git a/src/com/android/car/calendar/common/EventsLiveData.java b/src/com/android/car/calendar/common/EventsLiveData.java
index 12c91e7..92ae0bb 100644
--- a/src/com/android/car/calendar/common/EventsLiveData.java
+++ b/src/com/android/car/calendar/common/EventsLiveData.java
@@ -31,7 +31,9 @@ import android.provider.CalendarContract;
import android.provider.CalendarContract.Instances;
import android.util.Log;
+import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -44,6 +46,7 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import javax.annotation.Nullable;
@@ -53,12 +56,17 @@ import javax.annotation.Nullable;
* Provider</a>.
*
* <p>While in the active state the content provider is observed for changes.
+ *
+ * <p>When the value given to the observer is null it signals that there are no calendars.
*/
public class EventsLiveData extends LiveData<ImmutableList<Event>> {
private static final String TAG = "CarCalendarEventsLiveData";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ // The duration to delay before updating the value to reduce the frequency of changes.
+ private static final int UPDATE_DELAY_MILLIS = 1000;
+
// Sort events by start date and title.
private static final Comparator<Event> EVENT_COMPARATOR =
Comparator.comparing(Event::getDayStartInstant).thenComparing(Event::getTitle);
@@ -68,19 +76,22 @@ public class EventsLiveData extends LiveData<ImmutableList<Event>> {
private final ContentResolver mContentResolver;
private final EventDescriptions mEventDescriptions;
private final EventLocations mLocations;
+ private final Runnable mUpdateIfChangedRunnable = this::updateIfChanged;
/** The event instances cursor is a field to allow observers to be managed. */
@Nullable private Cursor mEventsCursor;
@Nullable private ContentObserver mEventInstancesObserver;
+ // This can be updated on the background thread but read from any thread.
+ private volatile boolean mValueUpdated;
+
public EventsLiveData(
Clock clock,
Handler backgroundHandler,
ContentResolver contentResolver,
EventDescriptions eventDescriptions,
EventLocations locations) {
- super(ImmutableList.of());
mClock = clock;
mBackgroundHandler = backgroundHandler;
mContentResolver = contentResolver;
@@ -89,8 +100,16 @@ public class EventsLiveData extends LiveData<ImmutableList<Event>> {
}
/** Refreshes the event instances and sets the new value which notifies observers. */
- private void update() {
- postValue(getEventsUntilTomorrow());
+ private void updateIfChanged() {
+ Log.d(TAG, "Update if changed");
+ ImmutableList<Event> latest = getEventsUntilTomorrow();
+ ImmutableList<Event> current = getValue();
+
+ // Always post the first value even if it is null.
+ if (!mValueUpdated || !Objects.equals(latest, current)) {
+ postValue(latest);
+ mValueUpdated = true;
+ }
}
/** Queries the content provider for event instances. */
@@ -167,7 +186,7 @@ public class EventsLiveData extends LiveData<ImmutableList<Event>> {
@Override
public void onChange(boolean selfChange) {
if (DEBUG) Log.d(TAG, "Events changed");
- update();
+ updateWithDelay();
}
};
cursor.setNotificationUri(mContentResolver, eventInstanceUri);
@@ -176,6 +195,13 @@ public class EventsLiveData extends LiveData<ImmutableList<Event>> {
return cursor;
}
+ private void updateWithDelay() {
+ // Do not update the events until there have been no changes for a given duration.
+ Log.d(TAG, "Events changed");
+ mBackgroundHandler.removeCallbacks(mUpdateIfChangedRunnable);
+ mBackgroundHandler.postDelayed(mUpdateIfChangedRunnable, UPDATE_DELAY_MILLIS);
+ }
+
/** Can return multiple events for a single cursor row when an event spans multiple days. */
private List<Event> createEventsForRow(
Cursor eventInstancesCursor, EventDescriptions eventDescriptions) {
@@ -262,13 +288,14 @@ public class EventsLiveData extends LiveData<ImmutableList<Event>> {
if (DEBUG) Log.d(TAG, "Live data inactive");
mBackgroundHandler.post(this::cancelScheduledUpdate);
mBackgroundHandler.post(this::tearDownCursor);
+ mValueUpdated = false;
}
- /** Calls {@link #update()} every minute to keep the displayed time range correct. */
+ /** Calls {@link #updateIfChanged()} every minute to keep the displayed time range correct. */
private void updateAndScheduleNext() {
if (DEBUG) Log.d(TAG, "Update and schedule");
if (hasActiveObservers()) {
- update();
+ updateIfChanged();
ZonedDateTime now = ZonedDateTime.now(mClock);
ZonedDateTime truncatedNowTime = now.truncatedTo(MINUTES);
ZonedDateTime updateTime = truncatedNowTime.plus(1, MINUTES);