Skip to content

Commit

Permalink
Merge pull request #550 from laalto/bug549_accessibility
Browse files Browse the repository at this point in the history
Fix: Crash with accessibility next/prev events and min/max date (#549)
  • Loading branch information
wdullaer authored Nov 22, 2018
2 parents cb1b5a6 + 87e6cd0 commit 2dcef16
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
@Override
public void onPageChanged(int position) {
updateButtonVisibility(position);
dayPickerView.accessibilityAnnouncePageChanged();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,13 @@

package com.wdullaer.materialdatetimepicker.date;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.wdullaer.materialdatetimepicker.GravitySnapHelper;
import com.wdullaer.materialdatetimepicker.Utils;
Expand All @@ -39,15 +32,18 @@
import java.util.Calendar;
import java.util.Locale;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
* This displays a list of months in a calendar format with selectable days.
*/
public abstract class DayPickerView extends RecyclerView implements OnDateChangedListener {

private static final String TAG = "MonthFragment";

private static SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());

protected Context mContext;

// highlighted time
Expand Down Expand Up @@ -91,15 +87,15 @@ public void setController(DatePickerController controller) {
mController.registerOnDateChangedListener(this);
mSelectedDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
mTempDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
YEAR_FORMAT = new SimpleDateFormat("yyyy", controller.getLocale());
refreshAdapter();
onDateChanged();
}

public void init(Context context, DatePickerDialog.ScrollOrientation scrollOrientation) {
@RecyclerView.Orientation
int layoutOrientation = scrollOrientation == DatePickerDialog.ScrollOrientation.VERTICAL
? LinearLayoutManager.VERTICAL
: LinearLayoutManager.HORIZONTAL;
? RecyclerView.VERTICAL
: RecyclerView.HORIZONTAL;
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, layoutOrientation, false);
setLayoutManager(linearLayoutManager);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
Expand Down Expand Up @@ -241,6 +237,10 @@ public void postSetSelection(final int position) {
clearFocus();
post(() -> {
((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, 0);

// Set initial accessibility focus to selected day
restoreAccessibilityFocus(mSelectedDay);

if (pageListener != null) pageListener.onPageChanged(position);
});
}
Expand Down Expand Up @@ -350,81 +350,16 @@ public void onInitializeAccessibilityEvent(@NonNull AccessibilityEvent event) {
event.setItemCount(-1);
}

private static String getMonthAndYearString(MonthAdapter.CalendarDay day, Locale locale) {
Calendar cal = Calendar.getInstance();
cal.set(day.year, day.month, day.day);

String sbuf = "";
sbuf += cal.getDisplayName(Calendar.MONTH, Calendar.LONG, locale);
sbuf += " ";
sbuf += YEAR_FORMAT.format(cal.getTime());
return sbuf;
void accessibilityAnnouncePageChanged() {
MonthView mv = getMostVisibleMonth();
String monthYear = getMonthAndYearString(mv.mMonth, mv.mYear, mController.getLocale());
Utils.tryAccessibilityAnnounce(this, monthYear);
}

/**
* Necessary for accessibility, to ensure we support "scrolling" forward and backward
* in the month list.
*/
@Override
@SuppressWarnings("deprecation")
public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (Build.VERSION.SDK_INT >= 21) {
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
} else {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
}
}

/**
* When scroll forward/backward events are received, announce the newly scrolled-to month.
*/
@SuppressLint("NewApi")
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
return super.performAccessibilityAction(action, arguments);
}
// Figure out what month is showing.
int firstVisiblePosition = getFirstVisiblePosition();
int minMonth = mController.getStartDate().get(Calendar.MONTH);
int month = (firstVisiblePosition + minMonth) % MonthAdapter.MONTHS_IN_YEAR;
int year = (firstVisiblePosition + minMonth) / MonthAdapter.MONTHS_IN_YEAR + mController.getMinYear();
MonthAdapter.CalendarDay day = new MonthAdapter.CalendarDay(year, month, 1, mController.getTimeZone());

// Scroll either forward or backward one month.
if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) {
day.month++;
if (day.month == 12) {
day.month = 0;
day.year++;
}
} else if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
View firstVisibleView = getChildAt(0);
// If the view is fully visible, jump one month back. Otherwise, we'll just jump
// to the first day of first visible month.
if (firstVisibleView != null && firstVisibleView.getTop() >= -1) {
// There's an off-by-one somewhere, so the top of the first visible item will
// actually be -1 when it's at the exact top.
day.month--;
if (day.month == -1) {
day.month = 11;
day.year--;
}
}
}

// Go to that month.
Utils.tryAccessibilityAnnounce(this, getMonthAndYearString(day, mController.getLocale()));
goTo(day, true, false, true);
return true;
}

private int getFirstVisiblePosition() {
return getChildAdapterPosition(getChildAt(0));
private static String getMonthAndYearString(int month, int year, Locale locale) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.MONTH, month);
calendar.set(Calendar.YEAR, year);
return new SimpleDateFormat("MMMM yyyy", locale).format(calendar.getTime());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,9 @@ protected void onPopulateNodeForVirtualView(int virtualViewId,
node.setBoundsInParent(mTempRect);
node.addAction(AccessibilityNodeInfo.ACTION_CLICK);

// Flag non-selectable dates as disabled
node.setEnabled(!mController.isOutOfRange(mYear, mMonth, virtualViewId));

if (virtualViewId == mSelectedDay) {
node.setSelected(true);
}
Expand Down Expand Up @@ -753,14 +756,7 @@ void getItemBounds(int day, Rect rect) {
*/
CharSequence getItemDescription(int day) {
mTempCalendar.set(mYear, mMonth, day);
final CharSequence date = DateFormat.format(DATE_FORMAT,
mTempCalendar.getTimeInMillis());

if (day == mSelectedDay) {
return getContext().getString(R.string.mdtp_item_is_selected, date);
}

return date;
return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
}
}

Expand Down

0 comments on commit 2dcef16

Please sign in to comment.