AlertUtils.java revision 487d52c2789114e0ee3e7ce85694611b8d59dd70
19881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson/*
29881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * Copyright (C) 2012 The Android Open Source Project
39881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson *
49881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * Licensed under the Apache License, Version 2.0 (the "License");
59881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * you may not use this file except in compliance with the License.
69881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * You may obtain a copy of the License at
79881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson *
89881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson *      http://www.apache.org/licenses/LICENSE-2.0
99881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson *
109881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * Unless required by applicable law or agreed to in writing, software
119881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * distributed under the License is distributed on an "AS IS" BASIS,
129881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * See the License for the specific language governing permissions and
149881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson * limitations under the License
159881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson */
169881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
179881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonpackage com.android.calendar.alerts;
189881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
199881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.app.AlarmManager;
209881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.app.PendingIntent;
2160edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Tingimport android.content.ContentUris;
229881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.content.ContentValues;
239881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.content.Context;
249881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.content.Intent;
259881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.net.Uri;
269881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.provider.CalendarContract;
279881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonimport android.provider.CalendarContract.CalendarAlerts;
2842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport android.text.TextUtils;
2942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport android.text.format.DateFormat;
3042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport android.text.format.DateUtils;
3142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport android.text.format.Time;
329881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
330ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelsonimport com.android.calendar.EventInfoActivity;
3442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport com.android.calendar.R;
3542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport com.android.calendar.Utils;
3642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
3742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport java.util.Locale;
3842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Tingimport java.util.TimeZone;
399881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
409881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelsonpublic class AlertUtils {
419881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
429881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static final long SNOOZE_DELAY = 5 * 60 * 1000L;
439881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
4442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    // We use one notification id for the expired events notification.  All
4542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    // other notifications (the 'active' future/concurrent ones) use a unique ID.
4642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    public static final int EXPIRED_GROUP_NOTIFICATION_ID = 0;
479881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
489881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static final String EVENT_ID_KEY = "eventid";
499881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static final String SHOW_EVENT_KEY = "showevent";
509881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static final String EVENT_START_KEY = "eventstart";
519881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static final String EVENT_END_KEY = "eventend";
5242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    public static final String NOTIFICATION_ID_KEY = "notificationid";
53660f1b4ab88bed6e8066ad05b324e95a9940cf73Sara Ting    public static final String EVENT_IDS_KEY = "eventids";
549881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
559881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    /**
56487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting     * Creates an AlarmManagerInterface that wraps a real AlarmManager.  The alarm code
57487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting     * was abstracted to an interface to make it testable.
58487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting     */
59487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    public static AlarmManagerInterface createAlarmManager(Context context) {
60487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting        final AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
61487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting        return new AlarmManagerInterface() {
62487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting            @Override
63487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting            public void set(int type, long triggerAtMillis, PendingIntent operation) {
64487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting                mgr.set(type, triggerAtMillis, operation);
65487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting            }
66487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting        };
67487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    }
68487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting
69487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    /**
709881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * Schedules an alarm intent with the system AlarmManager that will notify
719881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * listeners when a reminder should be fired. The provider will keep
729881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * scheduled reminders up to date but apps may use this to implement snooze
739881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * functionality without modifying the reminders table. Scheduled alarms
749881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * will generate an intent using {@link #ACTION_EVENT_REMINDER}.
759881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     *
769881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * @param context A context for referencing system resources
779881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * @param manager The AlarmManager to use or null
789881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     * @param alarmTime The time to fire the intent in UTC millis since epoch
799881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson     */
80487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    public static void scheduleAlarm(Context context, AlarmManagerInterface manager,
81487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting            long alarmTime) {
8260edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        scheduleAlarmHelper(context, manager, alarmTime, false);
8360edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting    }
849881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
8560edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting    /**
8660edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting     * Schedules the next alarm to silently refresh the notifications.  Note that if there
8760edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting     * is a pending silent refresh alarm, it will be replaced with this one.
8860edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting     */
89487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    static void scheduleNextNotificationRefresh(Context context, AlarmManagerInterface manager,
9060edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            long alarmTime) {
9160edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        scheduleAlarmHelper(context, manager, alarmTime, true);
9260edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting    }
939881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
94487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting    private static void scheduleAlarmHelper(Context context, AlarmManagerInterface manager,
95487d52c2789114e0ee3e7ce85694611b8d59dd70Sara Ting            long alarmTime, boolean quietUpdate) {
9660edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        int alarmType = AlarmManager.RTC_WAKEUP;
979881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        Intent intent = new Intent(CalendarContract.ACTION_EVENT_REMINDER);
9860edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        intent.setClass(context, AlertReceiver.class);
9960edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        if (quietUpdate) {
10060edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            alarmType = AlarmManager.RTC;
10160edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        } else {
10260edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            // Set data field so we get a unique PendingIntent instance per alarm or else alarms
10360edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            // may be dropped.
10460edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            Uri.Builder builder = CalendarAlerts.CONTENT_URI.buildUpon();
10560edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            ContentUris.appendId(builder, alarmTime);
10660edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting            intent.setData(builder.build());
10760edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        }
10860edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting
1099881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime);
1109881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent,
1119881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson                PendingIntent.FLAG_UPDATE_CURRENT);
11260edea82999b3a4d9a2c29d04c7ea611c86f4b78Sara Ting        manager.set(alarmType, alarmTime, pi);
1139881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    }
1149881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
11542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    /**
11642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     * Format the second line which shows time and location for single alert or the
11742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     * number of events for multiple alerts
11842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     *     1) Show time only for non-all day events
11942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     *     2) No date for today
12042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     *     3) Show "tomorrow" for tomorrow
12142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     *     4) Show date for days beyond that
12242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting     */
12342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    static String formatTimeLocation(Context context, long startMillis, boolean allDay,
12442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            String location) {
12542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        String tz = Utils.getTimeZone(context, null);
12642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        Time time = new Time(tz);
12742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        time.setToNow();
12842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        int today = Time.getJulianDay(time.toMillis(false), time.gmtoff);
12942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        time.set(startMillis);
13042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        int eventDay = Time.getJulianDay(time.toMillis(false), time.gmtoff);
13142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
13242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        int flags = DateUtils.FORMAT_ABBREV_ALL;
13342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (!allDay) {
13442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            flags |= DateUtils.FORMAT_SHOW_TIME;
13542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            if (DateFormat.is24HourFormat(context)) {
13642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                flags |= DateUtils.FORMAT_24HOUR;
13742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            }
13842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        } else {
13942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            flags |= DateUtils.FORMAT_UTC;
14042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
14142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
14242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (eventDay < today || eventDay > today + 1) {
14342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            flags |= DateUtils.FORMAT_SHOW_DATE;
14442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
14542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
14642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        StringBuilder sb = new StringBuilder(Utils.formatDateRange(context, startMillis,
14742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                startMillis, flags));
14842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
14942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (!allDay && tz != Time.getCurrentTimezone()) {
15042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            // Assumes time was set to the current tz
15142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            time.set(startMillis);
15242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            boolean isDST = time.isDst != 0;
15342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName(
15442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting                    isDST, TimeZone.SHORT, Locale.getDefault()));
15542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
15642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
15742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (eventDay == today + 1) {
15842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            // Tomorrow
15942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            sb.append(", ");
16042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            sb.append(context.getString(R.string.tomorrow));
16142ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
16242ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
16342ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        String loc;
16442ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        if (location != null && !TextUtils.isEmpty(loc = location.trim())) {
16542ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            sb.append(", ");
16642ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting            sb.append(loc);
16742ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        }
16842ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting        return sb.toString();
16942ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting    }
17042ba5efed5945b0e96735ec9ca4b388ae35b56f7Sara Ting
1719881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    public static ContentValues makeContentValues(long eventId, long begin, long end,
1729881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson            long alarmTime, int minutes) {
1739881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        ContentValues values = new ContentValues();
1749881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.EVENT_ID, eventId);
1759881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.BEGIN, begin);
1769881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.END, end);
1779881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.ALARM_TIME, alarmTime);
1789881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        long currentTime = System.currentTimeMillis();
1799881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.CREATION_TIME, currentTime);
1809881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.RECEIVED_TIME, 0);
1819881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.NOTIFY_TIME, 0);
1829881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_SCHEDULED);
1839881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        values.put(CalendarAlerts.MINUTES, minutes);
1849881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        return values;
1859881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    }
1869881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
1870ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson    public static Intent buildEventViewIntent(Context c, long eventId, long begin, long end) {
1889881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        Intent i = new Intent(Intent.ACTION_VIEW);
1899881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
1909881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        builder.appendEncodedPath("events/" + eventId);
1919881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        i.setData(builder.build());
1920ef732f240516f9f8b8add982f82244e01ec509bIsaac Katzenelson        i.setClass(c, EventInfoActivity.class);
1939881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        i.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, begin);
1949881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        i.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, end);
1959881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson        return i;
1969881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson    }
1979881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson
1989881907c47b2658fa85954bfb339c4b1eab9fc8eIsaac Katzenelson}
199