ZenModePanel.java revision f7d22132c2c9b83134b05aaed63a89f61c639fd9
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.volume;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.content.Context;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
25import android.net.Uri;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.provider.Settings;
30import android.provider.Settings.Global;
31import android.service.notification.Condition;
32import android.service.notification.ZenModeConfig;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.view.ContextThemeWrapper;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.animation.AnimationUtils;
39import android.view.animation.Interpolator;
40import android.widget.CompoundButton;
41import android.widget.CompoundButton.OnCheckedChangeListener;
42import android.widget.ImageView;
43import android.widget.LinearLayout;
44import android.widget.RadioButton;
45import android.widget.TextView;
46
47import com.android.systemui.R;
48import com.android.systemui.statusbar.policy.ZenModeController;
49
50import java.util.Arrays;
51import java.util.Objects;
52
53public class ZenModePanel extends LinearLayout {
54    private static final String TAG = "ZenModePanel";
55    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
56
57    private static final int[] MINUTE_BUCKETS = DEBUG
58            ? new int[] { 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
59            : new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
60    private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
61    private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
62    private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
63    private static final int FOREVER_CONDITION_INDEX = 0;
64    private static final int TIME_CONDITION_INDEX = 1;
65    private static final int FIRST_CONDITION_INDEX = 2;
66    private static final float SILENT_HINT_PULSE_SCALE = 1.1f;
67
68    private static final int SECONDS_MS = 1000;
69    private static final int MINUTES_MS = 60 * SECONDS_MS;
70
71    public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
72
73    private final Context mContext;
74    private final LayoutInflater mInflater;
75    private final H mHandler = new H();
76    private final Favorites mFavorites;
77    private final Interpolator mFastOutSlowInInterpolator;
78
79    private char mLogTag = '?';
80    private String mTag;
81
82    private SegmentedButtons mZenButtons;
83    private View mZenSubhead;
84    private TextView mZenSubheadCollapsed;
85    private TextView mZenSubheadExpanded;
86    private View mMoreSettings;
87    private LinearLayout mZenConditions;
88    private View mAlarmWarning;
89
90    private Callback mCallback;
91    private ZenModeController mController;
92    private boolean mRequestingConditions;
93    private Uri mExitConditionId;
94    private int mBucketIndex = -1;
95    private boolean mExpanded;
96    private int mAttachedZen;
97    private String mExitConditionText;
98
99    public ZenModePanel(Context context, AttributeSet attrs) {
100        super(context, attrs);
101        mContext = context;
102        mFavorites = new Favorites();
103        mInflater = LayoutInflater.from(mContext.getApplicationContext());
104        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
105                android.R.interpolator.fast_out_slow_in);
106        updateTag();
107        if (DEBUG) Log.d(mTag, "new ZenModePanel");
108    }
109
110    private void updateTag() {
111        mTag = TAG + "/" + mLogTag + "/" + Integer.toHexString(System.identityHashCode(this));
112    }
113
114    @Override
115    protected void onFinishInflate() {
116        super.onFinishInflate();
117
118        mZenButtons = (SegmentedButtons) findViewById(R.id.zen_buttons);
119        mZenButtons.addButton(R.string.interruption_level_none, Global.ZEN_MODE_NO_INTERRUPTIONS);
120        mZenButtons.addButton(R.string.interruption_level_priority,
121                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
122        mZenButtons.addButton(R.string.interruption_level_all, Global.ZEN_MODE_OFF);
123        mZenButtons.setCallback(mZenButtonsCallback);
124
125        mZenSubhead = findViewById(R.id.zen_subhead);
126
127        mZenSubheadCollapsed = (TextView) findViewById(R.id.zen_subhead_collapsed);
128        mZenSubheadCollapsed.setOnClickListener(new View.OnClickListener() {
129            @Override
130            public void onClick(View v) {
131                setExpanded(true);
132                fireInteraction();
133            }
134        });
135
136        mZenSubheadExpanded = (TextView) findViewById(R.id.zen_subhead_expanded);
137        mZenSubheadExpanded.setOnClickListener(new View.OnClickListener() {
138            @Override
139            public void onClick(View v) {
140                setExpanded(false);
141                fireInteraction();
142            }
143        });
144
145        mMoreSettings = findViewById(R.id.zen_more_settings);
146        mMoreSettings.setOnClickListener(new View.OnClickListener() {
147            @Override
148            public void onClick(View v) {
149                fireMoreSettings();
150                fireInteraction();
151            }
152        });
153
154        mZenConditions = (LinearLayout) findViewById(R.id.zen_conditions);
155
156        mAlarmWarning = findViewById(R.id.zen_alarm_warning);
157    }
158
159    @Override
160    protected void onAttachedToWindow() {
161        super.onAttachedToWindow();
162        if (DEBUG) Log.d(mTag, "onAttachedToWindow");
163        mAttachedZen = getSelectedZen(-1);
164        refreshExitConditionText();
165    }
166
167    @Override
168    protected void onDetachedFromWindow() {
169        super.onDetachedFromWindow();
170        if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
171        mAttachedZen = -1;
172        setExpanded(false);
173    }
174
175    private void setExpanded(boolean expanded) {
176        if (expanded == mExpanded) return;
177        mExpanded = expanded;
178        updateWidgets();
179        setRequestingConditions(mExpanded);
180        fireExpanded();
181    }
182
183    /** Start or stop requesting relevant zen mode exit conditions */
184    private void setRequestingConditions(boolean requesting) {
185        if (mRequestingConditions == requesting) return;
186        if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting);
187        mRequestingConditions = requesting;
188        if (mController != null) {
189            mController.requestConditions(mRequestingConditions);
190        }
191        if (mRequestingConditions) {
192            Condition timeCondition = parseExistingTimeCondition(mExitConditionId);
193            if (timeCondition != null) {
194                mBucketIndex = -1;
195            } else {
196                mBucketIndex = DEFAULT_BUCKET_INDEX;
197                timeCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
198            }
199            if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex);
200            handleUpdateConditions(new Condition[0]);  // ensures forever exists
201            bind(timeCondition, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
202            checkForDefault();
203        } else {
204            mZenConditions.removeAllViews();
205        }
206    }
207
208    public void init(ZenModeController controller, char logTag) {
209        mController = controller;
210        mLogTag = logTag;
211        updateTag();
212        setExitConditionId(mController.getExitConditionId());
213        refreshExitConditionText();
214        mAttachedZen = getSelectedZen(-1);
215        handleUpdateZen(mController.getZen());
216        if (DEBUG) Log.d(mTag, "init mExitConditionId=" + mExitConditionId);
217        mZenConditions.removeAllViews();
218        mController.addCallback(mZenCallback);
219    }
220
221    private void setExitConditionId(Uri exitConditionId) {
222        if (Objects.equals(mExitConditionId, exitConditionId)) return;
223        mExitConditionId = exitConditionId;
224        refreshExitConditionText();
225    }
226
227    private void refreshExitConditionText() {
228        if (mExitConditionId == null) {
229            mExitConditionText = mContext.getString(R.string.zen_mode_forever);
230        } else if (ZenModeConfig.isValidCountdownConditionId(mExitConditionId)) {
231            mExitConditionText = parseExistingTimeCondition(mExitConditionId).summary;
232        } else {
233            mExitConditionText = "(until condition ends)";  // TODO persist current description
234        }
235    }
236
237    public void setCallback(Callback callback) {
238        mCallback = callback;
239    }
240
241    public void showSilentHint() {
242        if (DEBUG) Log.d(mTag, "showSilentHint");
243        if (mZenButtons == null || mZenButtons.getChildCount() == 0) return;
244        final View noneButton = mZenButtons.getChildAt(0);
245        if (noneButton.getScaleX() != 1) return;  // already running
246        noneButton.animate().cancel();
247        noneButton.animate().scaleX(SILENT_HINT_PULSE_SCALE).scaleY(SILENT_HINT_PULSE_SCALE)
248                .setInterpolator(mFastOutSlowInInterpolator)
249                .setListener(new AnimatorListenerAdapter() {
250                    @Override
251                    public void onAnimationEnd(Animator animation) {
252                        noneButton.animate().scaleX(1).scaleY(1).setListener(null);
253                    }
254                });
255    }
256
257    private void handleUpdateZen(int zen) {
258        if (mAttachedZen != -1 && mAttachedZen != zen) {
259            setExpanded(zen != Global.ZEN_MODE_OFF);
260            mAttachedZen = zen;
261        }
262        mZenButtons.setSelectedValue(zen);
263        updateWidgets();
264    }
265
266    private int getSelectedZen(int defValue) {
267        final Object zen = mZenButtons.getSelectedValue();
268        return zen != null ? (Integer) zen : defValue;
269    }
270
271    private void updateWidgets() {
272        final int zen = getSelectedZen(Global.ZEN_MODE_OFF);
273        final boolean zenOff = zen == Global.ZEN_MODE_OFF;
274        final boolean zenImportant = zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
275        final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
276
277        mZenSubhead.setVisibility(!zenOff ? VISIBLE : GONE);
278        mZenSubheadExpanded.setVisibility(mExpanded ? VISIBLE : GONE);
279        mZenSubheadCollapsed.setVisibility(!mExpanded ? VISIBLE : GONE);
280        mMoreSettings.setVisibility(zenImportant && mExpanded ? VISIBLE : GONE);
281        mZenConditions.setVisibility(!zenOff && mExpanded ? VISIBLE : GONE);
282        mAlarmWarning.setVisibility(zenNone && mExpanded ? VISIBLE : GONE);
283
284        if (zenNone) {
285            mZenSubheadExpanded.setText(R.string.zen_no_interruptions);
286            mZenSubheadCollapsed.setText(mExitConditionText);
287        } else if (zenImportant) {
288            mZenSubheadExpanded.setText(R.string.zen_important_interruptions);
289            mZenSubheadCollapsed.setText(mExitConditionText);
290        }
291    }
292
293    private Condition parseExistingTimeCondition(Uri conditionId) {
294        final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
295        if (time == 0) return null;
296        final long span = time - System.currentTimeMillis();
297        if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null;
298        return timeCondition(time, Math.round(span / (float)MINUTES_MS));
299    }
300
301    private Condition newTimeCondition(int minutesFromNow) {
302        final long now = System.currentTimeMillis();
303        return timeCondition(now + minutesFromNow * MINUTES_MS, minutesFromNow);
304    }
305
306    private Condition timeCondition(long time, int minutes) {
307        final int num = minutes < 60 ? minutes : Math.round(minutes / 60f);
308        final int resId = minutes < 60
309                ? R.plurals.zen_mode_duration_minutes
310                : R.plurals.zen_mode_duration_hours;
311        final String caption = mContext.getResources().getQuantityString(resId, num, num);
312        final Uri id = ZenModeConfig.toCountdownConditionId(time);
313        return new Condition(id, caption, "", "", 0, Condition.STATE_TRUE,
314                Condition.FLAG_RELEVANT_NOW);
315    }
316
317    private void handleUpdateConditions(Condition[] conditions) {
318        final int newCount = conditions == null ? 0 : conditions.length;
319        if (DEBUG) Log.d(mTag, "handleUpdateConditions newCount=" + newCount);
320        for (int i = mZenConditions.getChildCount(); i >= newCount + FIRST_CONDITION_INDEX; i--) {
321            mZenConditions.removeViewAt(i);
322        }
323        bind(null, mZenConditions.getChildAt(FOREVER_CONDITION_INDEX));
324        for (int i = 0; i < newCount; i++) {
325            bind(conditions[i], mZenConditions.getChildAt(FIRST_CONDITION_INDEX + i));
326        }
327    }
328
329    private ConditionTag getConditionTagAt(int index) {
330        return (ConditionTag) mZenConditions.getChildAt(index).getTag();
331    }
332
333    private void checkForDefault() {
334        // are we left without anything selected?  if so, set a default
335        for (int i = 0; i < mZenConditions.getChildCount(); i++) {
336            if (getConditionTagAt(i).rb.isChecked()) {
337                return;
338            }
339        }
340        if (DEBUG) Log.d(mTag, "Selecting a default");
341        final int favoriteIndex = mFavorites.getMinuteIndex();
342        if (favoriteIndex == -1) {
343            getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
344        } else {
345            final Condition c = newTimeCondition(MINUTE_BUCKETS[favoriteIndex]);
346            mBucketIndex = favoriteIndex;
347            bind(c, mZenConditions.getChildAt(TIME_CONDITION_INDEX));
348            getConditionTagAt(TIME_CONDITION_INDEX).rb.setChecked(true);
349        }
350    }
351
352    private void handleExitConditionChanged(Uri exitCondition) {
353        setExitConditionId(exitCondition);
354        if (DEBUG) Log.d(mTag, "handleExitConditionChanged " + mExitConditionId);
355        final int N = mZenConditions.getChildCount();
356        for (int i = 0; i < N; i++) {
357            final ConditionTag tag = getConditionTagAt(i);
358            tag.rb.setChecked(Objects.equals(tag.conditionId, exitCondition));
359        }
360    }
361
362    private void bind(final Condition condition, View convertView) {
363        final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE;
364        final View row;
365        if (convertView == null) {
366            row = mInflater.inflate(R.layout.zen_mode_condition, this, false);
367            if (DEBUG) Log.d(mTag, "Adding new condition view for: " + condition);
368            mZenConditions.addView(row);
369        } else {
370            row = convertView;
371        }
372        final ConditionTag tag =
373                row.getTag() != null ? (ConditionTag) row.getTag() : new ConditionTag();
374        row.setTag(tag);
375        if (tag.rb == null) {
376            tag.rb = (RadioButton) row.findViewById(android.R.id.checkbox);
377        }
378        tag.conditionId = condition != null ? condition.id : null;
379        tag.rb.setEnabled(enabled);
380        tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
381            @Override
382            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
383                if (mExpanded && isChecked) {
384                    if (DEBUG) Log.d(mTag, "onCheckedChanged " + tag.conditionId);
385                    final int N = mZenConditions.getChildCount();
386                    for (int i = 0; i < N; i++) {
387                        ConditionTag childTag = getConditionTagAt(i);
388                        if (childTag == tag) continue;
389                        childTag.rb.setChecked(false);
390                    }
391                    select(tag.conditionId);
392                    fireInteraction();
393                }
394            }
395        });
396        final TextView title = (TextView) row.findViewById(android.R.id.title);
397        if (condition == null) {
398            title.setText(R.string.zen_mode_forever);
399        } else {
400            title.setText(condition.summary);
401        }
402        title.setEnabled(enabled);
403        title.setAlpha(enabled ? 1 : .4f);
404        final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
405        button1.setOnClickListener(new OnClickListener() {
406            @Override
407            public void onClick(View v) {
408                onClickTimeButton(row, tag, false /*down*/);
409            }
410        });
411
412        final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
413        button2.setOnClickListener(new OnClickListener() {
414            @Override
415            public void onClick(View v) {
416                onClickTimeButton(row, tag, true /*up*/);
417            }
418        });
419        title.setOnClickListener(new OnClickListener() {
420            @Override
421            public void onClick(View v) {
422                tag.rb.setChecked(true);
423                fireInteraction();
424            }
425        });
426
427        final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId);
428        if (time > 0) {
429            if (mBucketIndex > -1) {
430                button1.setEnabled(mBucketIndex > 0);
431                button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
432            } else {
433                final long span = time - System.currentTimeMillis();
434                button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
435                final Condition maxCondition = newTimeCondition(MAX_BUCKET_MINUTES);
436                button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
437            }
438
439            button1.setAlpha(button1.isEnabled() ? 1f : .5f);
440            button2.setAlpha(button2.isEnabled() ? 1f : .5f);
441        } else {
442            button1.setVisibility(View.GONE);
443            button2.setVisibility(View.GONE);
444        }
445    }
446
447    private void onClickTimeButton(View row, ConditionTag tag, boolean up) {
448        Condition newCondition = null;
449        final int N = MINUTE_BUCKETS.length;
450        if (mBucketIndex == -1) {
451            // not on a known index, search for the next or prev bucket by time
452            final long time = ZenModeConfig.tryParseCountdownConditionId(tag.conditionId);
453            final long now = System.currentTimeMillis();
454            for (int i = 0; i < N; i++) {
455                int j = up ? i : N - 1 - i;
456                final int bucketMinutes = MINUTE_BUCKETS[j];
457                final long bucketTime = now + bucketMinutes * MINUTES_MS;
458                if (up && bucketTime > time || !up && bucketTime < time) {
459                    mBucketIndex = j;
460                    newCondition = timeCondition(bucketTime, bucketMinutes);
461                    break;
462                }
463            }
464            if (newCondition == null) {
465                mBucketIndex = DEFAULT_BUCKET_INDEX;
466                newCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
467            }
468        } else {
469            // on a known index, simply increment or decrement
470            mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
471            newCondition = newTimeCondition(MINUTE_BUCKETS[mBucketIndex]);
472        }
473        bind(newCondition, row);
474        tag.rb.setChecked(true);
475        select(newCondition.id);
476        fireInteraction();
477    }
478
479    private void select(Uri conditionId) {
480        if (DEBUG) Log.d(mTag, "select " + conditionId);
481        if (mController != null) {
482            mController.setExitConditionId(conditionId);
483        }
484        setExitConditionId(conditionId);
485        if (conditionId == null) {
486            mFavorites.setMinuteIndex(-1);
487        } else if (ZenModeConfig.isValidCountdownConditionId(conditionId) && mBucketIndex != -1) {
488            mFavorites.setMinuteIndex(mBucketIndex);
489        }
490    }
491
492    private void fireMoreSettings() {
493        if (mCallback != null) {
494            mCallback.onMoreSettings();
495        }
496    }
497
498    private void fireInteraction() {
499        if (mCallback != null) {
500            mCallback.onInteraction();
501        }
502    }
503
504    private void fireExpanded() {
505        if (mCallback != null) {
506            mCallback.onExpanded(mExpanded);
507        }
508    }
509
510    private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
511        @Override
512        public void onZenChanged(int zen) {
513            mHandler.obtainMessage(H.UPDATE_ZEN, zen, 0).sendToTarget();
514        }
515        @Override
516        public void onConditionsChanged(Condition[] conditions) {
517            mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget();
518        }
519
520        @Override
521        public void onExitConditionChanged(Uri exitConditionId) {
522            mHandler.obtainMessage(H.EXIT_CONDITION_CHANGED, exitConditionId).sendToTarget();
523        }
524    };
525
526    private final class H extends Handler {
527        private static final int UPDATE_CONDITIONS = 1;
528        private static final int EXIT_CONDITION_CHANGED = 2;
529        private static final int UPDATE_ZEN = 3;
530
531        private H() {
532            super(Looper.getMainLooper());
533        }
534
535        @Override
536        public void handleMessage(Message msg) {
537            if (msg.what == UPDATE_CONDITIONS) {
538                handleUpdateConditions((Condition[])msg.obj);
539                checkForDefault();
540            } else if (msg.what == EXIT_CONDITION_CHANGED) {
541                handleExitConditionChanged((Uri)msg.obj);
542            } else if (msg.what == UPDATE_ZEN) {
543                handleUpdateZen(msg.arg1);
544            }
545        }
546    }
547
548    public interface Callback {
549        void onMoreSettings();
550        void onInteraction();
551        void onExpanded(boolean expanded);
552    }
553
554    // used as the view tag on condition rows
555    private static class ConditionTag {
556        RadioButton rb;
557        Uri conditionId;
558    }
559
560    private final class Favorites implements OnSharedPreferenceChangeListener {
561        private static final String KEY_MINUTE_INDEX = "minuteIndex";
562
563        private int mMinuteIndex;
564
565        private Favorites() {
566            prefs().registerOnSharedPreferenceChangeListener(this);
567            updateMinuteIndex();
568        }
569
570        public int getMinuteIndex() {
571            return mMinuteIndex;
572        }
573
574        public void setMinuteIndex(int minuteIndex) {
575            minuteIndex = clamp(minuteIndex);
576            if (minuteIndex == mMinuteIndex) return;
577            mMinuteIndex = clamp(minuteIndex);
578            if (DEBUG) Log.d(mTag, "Setting favorite minute index: " + mMinuteIndex);
579            prefs().edit().putInt(KEY_MINUTE_INDEX, mMinuteIndex).apply();
580        }
581
582        @Override
583        public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
584            updateMinuteIndex();
585        }
586
587        private SharedPreferences prefs() {
588            return mContext.getSharedPreferences(ZenModePanel.class.getSimpleName(), 0);
589        }
590
591        private void updateMinuteIndex() {
592            mMinuteIndex = clamp(prefs().getInt(KEY_MINUTE_INDEX, DEFAULT_BUCKET_INDEX));
593            if (DEBUG) Log.d(mTag, "Favorite minute index: " + mMinuteIndex);
594        }
595
596        private int clamp(int index) {
597            return Math.max(-1, Math.min(MINUTE_BUCKETS.length - 1, index));
598        }
599    }
600
601    private final SegmentedButtons.Callback mZenButtonsCallback = new SegmentedButtons.Callback() {
602        @Override
603        public void onSelected(Object value) {
604            if (value != null && mZenButtons.isShown()) {
605                if (DEBUG) Log.d(mTag, "mZenButtonsCallback selected=" + value);
606                mController.setZen((Integer) value);
607                mController.setExitConditionId(null);
608            }
609        }
610    };
611}
612