• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.deskclock.stopwatch;
2 
3 import android.app.Notification;
4 import android.app.PendingIntent;
5 import android.app.Service;
6 import android.content.Context;
7 import android.content.Intent;
8 import android.content.SharedPreferences;
9 import android.os.IBinder;
10 import android.preference.PreferenceManager;
11 import android.support.v4.app.NotificationCompat;
12 import android.support.v4.app.NotificationManagerCompat;
13 import android.view.View;
14 import android.widget.RemoteViews;
15 
16 import com.android.deskclock.CircleTimerView;
17 import com.android.deskclock.DeskClock;
18 import com.android.deskclock.HandleDeskClockApiCalls;
19 import com.android.deskclock.R;
20 import com.android.deskclock.Utils;
21 
22 /**
23  * TODO: Insert description here. (generated by sblitz)
24  */
25 public class StopwatchService extends Service {
26     // Member fields
27     private int mNumLaps;
28     private long mElapsedTime;
29     private long mStartTime;
30     private boolean mLoadApp;
31     private NotificationManagerCompat mNotificationManager;
32 
33     // Constants for intent information
34     // Make this a large number to avoid the alarm ID's which seem to be 1, 2, ...
35     // Must also be different than TimerReceiver.IN_USE_NOTIFICATION_ID
36     private static final int NOTIFICATION_ID = Integer.MAX_VALUE - 1;
37 
38     @Override
onBind(Intent intent)39     public IBinder onBind(Intent intent) {
40         return null;
41     }
42 
43     @Override
onCreate()44     public void onCreate() {
45         mNumLaps = 0;
46         mElapsedTime = 0;
47         mStartTime = 0;
48         mLoadApp = false;
49         mNotificationManager = NotificationManagerCompat.from(this);
50     }
51 
52     @Override
onStartCommand(Intent intent, int flags, int startId)53     public int onStartCommand(Intent intent, int flags, int startId) {
54         if (intent == null) {
55             return Service.START_NOT_STICKY;
56         }
57 
58         if (mStartTime == 0 || mElapsedTime == 0 || mNumLaps == 0) {
59             // May not have the most recent values.
60             readFromSharedPrefs();
61         }
62 
63         String actionType = intent.getAction();
64         long actionTime = intent.getLongExtra(Stopwatches.MESSAGE_TIME, Utils.getTimeNow());
65         boolean showNotif = intent.getBooleanExtra(Stopwatches.SHOW_NOTIF, true);
66         // Update the stopwatch circle when the app is open or is being opened.
67         boolean updateCircle = !showNotif
68                 || intent.getAction().equals(Stopwatches.RESET_AND_LAUNCH_STOPWATCH);
69         switch(actionType) {
70             case HandleDeskClockApiCalls.ACTION_START_STOPWATCH:
71                 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this) ;
72                 prefs.edit().putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true).apply();
73 
74                 mStartTime = actionTime;
75                 writeSharedPrefsStarted(mStartTime, updateCircle);
76                 if (showNotif) {
77                     setNotification(mStartTime - mElapsedTime, true, mNumLaps);
78                 } else {
79                     saveNotification(mStartTime - mElapsedTime, true, mNumLaps);
80                 }
81                 break;
82             case HandleDeskClockApiCalls.ACTION_LAP_STOPWATCH:
83                 mNumLaps++;
84                 long lapTimeElapsed = actionTime - mStartTime + mElapsedTime;
85                 writeSharedPrefsLap(lapTimeElapsed, updateCircle);
86                 if (showNotif) {
87                     setNotification(mStartTime - mElapsedTime, true, mNumLaps);
88                 } else {
89                     saveNotification(mStartTime - mElapsedTime, true, mNumLaps);
90                 }
91                 break;
92             case HandleDeskClockApiCalls.ACTION_STOP_STOPWATCH:
93                 prefs = PreferenceManager.getDefaultSharedPreferences(this);
94                 prefs.edit().putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false).apply();
95 
96                 mElapsedTime = mElapsedTime + (actionTime - mStartTime);
97                 writeSharedPrefsStopped(mElapsedTime, updateCircle);
98                 if (showNotif) {
99                     setNotification(actionTime - mElapsedTime, false, mNumLaps);
100                 } else {
101                     saveNotification(mElapsedTime, false, mNumLaps);
102                 }
103                 break;
104             case HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH:
105                 mLoadApp = false;
106                 writeSharedPrefsReset(updateCircle);
107                 clearSavedNotification();
108                 stopSelf();
109                 break;
110             case Stopwatches.RESET_AND_LAUNCH_STOPWATCH:
111                 mLoadApp = true;
112                 writeSharedPrefsReset(updateCircle);
113                 clearSavedNotification();
114                 closeNotificationShade();
115                 stopSelf();
116                 break;
117             case Stopwatches.SHARE_STOPWATCH:
118                 if  (mElapsedTime > 0) {
119                     closeNotificationShade();
120                     Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
121                     shareIntent.setType("text/plain");
122                     shareIntent.putExtra(Intent.EXTRA_SUBJECT, Stopwatches.getShareTitle(
123                             getApplicationContext()));
124                     shareIntent.putExtra(Intent.EXTRA_TEXT, Stopwatches.buildShareResults(
125                             getApplicationContext(), mElapsedTime, readLapsFromPrefs()));
126                     Intent chooserIntent = Intent.createChooser(shareIntent, null);
127                     chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
128                     getApplication().startActivity(chooserIntent);
129                 }
130                 break;
131             case Stopwatches.SHOW_NOTIF:
132                 // SHOW_NOTIF sent from the DeskClock.onPause
133                 // If a notification is not displayed, this service's work is over
134                 if (!showSavedNotification()) {
135                     stopSelf();
136                 }
137                 break;
138             case Stopwatches.KILL_NOTIF:
139                 mNotificationManager.cancel(NOTIFICATION_ID);
140                 break;
141 
142         }
143 
144         // We want this service to continue running until it is explicitly
145         // stopped, so return sticky.
146         return START_STICKY;
147     }
148 
149     @Override
onDestroy()150     public void onDestroy() {
151         mNotificationManager.cancel(NOTIFICATION_ID);
152         clearSavedNotification();
153         mNumLaps = 0;
154         mElapsedTime = 0;
155         mStartTime = 0;
156         if (mLoadApp) {
157             Intent activityIntent = new Intent(getApplicationContext(), DeskClock.class);
158             activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
159             activityIntent.putExtra(
160                     DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
161             startActivity(activityIntent);
162             mLoadApp = false;
163         }
164     }
165 
setNotification(long clockBaseTime, boolean clockRunning, int numLaps)166     private void setNotification(long clockBaseTime, boolean clockRunning, int numLaps) {
167         Context context = getApplicationContext();
168         // Intent to load the app for a non-button click.
169         Intent intent = new Intent(context, DeskClock.class);
170         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
171         intent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.STOPWATCH_TAB_INDEX);
172         // add category to distinguish between stopwatch intents and timer intents
173         intent.addCategory("stopwatch");
174         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
175                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
176 
177         // Set up remoteviews for the notification.
178         RemoteViews remoteViewsCollapsed = new RemoteViews(getPackageName(),
179                 R.layout.stopwatch_notif_collapsed);
180         remoteViewsCollapsed.setOnClickPendingIntent(R.id.swn_collapsed_hitspace, pendingIntent);
181         remoteViewsCollapsed.setChronometer(
182                 R.id.swn_collapsed_chronometer, clockBaseTime, null, clockRunning);
183         remoteViewsCollapsed.
184                 setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
185         RemoteViews remoteViewsExpanded = new RemoteViews(getPackageName(),
186                 R.layout.stopwatch_notif_expanded);
187         remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_expanded_hitspace, pendingIntent);
188         remoteViewsExpanded.setChronometer(
189                 R.id.swn_expanded_chronometer, clockBaseTime, null, clockRunning);
190         remoteViewsExpanded.
191                 setImageViewResource(R.id.notification_icon, R.drawable.stat_notify_stopwatch);
192 
193         if (clockRunning) {
194             // Left button: lap
195             remoteViewsExpanded.setTextViewText(
196                     R.id.swn_left_button, getResources().getText(R.string.sw_lap_button));
197             Intent leftButtonIntent = new Intent(context, StopwatchService.class);
198             leftButtonIntent.setAction(HandleDeskClockApiCalls.ACTION_LAP_STOPWATCH);
199             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_left_button,
200                     PendingIntent.getService(context, 0, leftButtonIntent, 0));
201             remoteViewsExpanded.
202                     setTextViewCompoundDrawablesRelative(R.id.swn_left_button,
203                             R.drawable.ic_lap_24dp, 0, 0, 0);
204 
205             // Right button: stop clock
206             remoteViewsExpanded.setTextViewText(
207                     R.id.swn_right_button, getResources().getText(R.string.sw_stop_button));
208             Intent rightButtonIntent = new Intent(context, StopwatchService.class);
209             rightButtonIntent.setAction(HandleDeskClockApiCalls.ACTION_STOP_STOPWATCH);
210             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_right_button,
211                     PendingIntent.getService(context, 0, rightButtonIntent, 0));
212             remoteViewsExpanded.
213                     setTextViewCompoundDrawablesRelative(R.id.swn_right_button,
214                             R.drawable.ic_stop_24dp, 0, 0, 0);
215 
216             // Show the laps if applicable.
217             if (numLaps > 0) {
218                 String lapText = String.format(
219                         context.getString(R.string.sw_notification_lap_number), numLaps);
220                 remoteViewsCollapsed.setTextViewText(R.id.swn_collapsed_laps, lapText);
221                 remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.VISIBLE);
222                 remoteViewsExpanded.setTextViewText(R.id.swn_expanded_laps, lapText);
223                 remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.VISIBLE);
224             } else {
225                 remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.GONE);
226                 remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.GONE);
227             }
228         } else {
229             // Left button: reset clock
230             remoteViewsExpanded.setTextViewText(
231                     R.id.swn_left_button, getResources().getText(R.string.sw_reset_button));
232             Intent leftButtonIntent = new Intent(context, StopwatchService.class);
233             leftButtonIntent.setAction(Stopwatches.RESET_AND_LAUNCH_STOPWATCH);
234             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_left_button,
235                     PendingIntent.getService(context, 0, leftButtonIntent, 0));
236             remoteViewsExpanded.
237                     setTextViewCompoundDrawablesRelative(R.id.swn_left_button,
238                             R.drawable.ic_reset_24dp, 0, 0, 0);
239 
240             // Right button: start clock
241             remoteViewsExpanded.setTextViewText(
242                     R.id.swn_right_button, getResources().getText(R.string.sw_start_button));
243             Intent rightButtonIntent = new Intent(context, StopwatchService.class);
244             rightButtonIntent.setAction(HandleDeskClockApiCalls.ACTION_START_STOPWATCH);
245             remoteViewsExpanded.setOnClickPendingIntent(R.id.swn_right_button,
246                     PendingIntent.getService(context, 0, rightButtonIntent, 0));
247             remoteViewsExpanded.
248                     setTextViewCompoundDrawablesRelative(R.id.swn_right_button,
249                             R.drawable.ic_start_24dp, 0, 0, 0);
250 
251             // Show stopped string.
252             remoteViewsCollapsed.
253                     setTextViewText(R.id.swn_collapsed_laps, getString(R.string.swn_stopped));
254             remoteViewsCollapsed.setViewVisibility(R.id.swn_collapsed_laps, View.VISIBLE);
255             remoteViewsExpanded.
256                     setTextViewText(R.id.swn_expanded_laps, getString(R.string.swn_stopped));
257             remoteViewsExpanded.setViewVisibility(R.id.swn_expanded_laps, View.VISIBLE);
258         }
259 
260         Intent dismissIntent = new Intent(context, StopwatchService.class);
261         dismissIntent.setAction(HandleDeskClockApiCalls.ACTION_RESET_STOPWATCH);
262 
263         Notification notification = new NotificationCompat.Builder(context)
264                 .setAutoCancel(!clockRunning)
265                 .setContent(remoteViewsCollapsed)
266                 .setOngoing(clockRunning)
267                 .setDeleteIntent(PendingIntent.getService(context, 0, dismissIntent, 0))
268                 .setSmallIcon(R.drawable.ic_tab_stopwatch_activated)
269                 .setPriority(Notification.PRIORITY_MAX)
270                 .setLocalOnly(true)
271                 .build();
272         notification.bigContentView = remoteViewsExpanded;
273         mNotificationManager.notify(NOTIFICATION_ID, notification);
274     }
275 
276     /** Save the notification to be shown when the app is closed. **/
saveNotification(long clockTime, boolean clockRunning, int numLaps)277     private void saveNotification(long clockTime, boolean clockRunning, int numLaps) {
278         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
279                 getApplicationContext());
280         SharedPreferences.Editor editor = prefs.edit();
281         if (clockRunning) {
282             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, clockTime);
283             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
284             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, true);
285         } else {
286             editor.putLong(Stopwatches.NOTIF_CLOCK_ELAPSED, clockTime);
287             editor.putLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
288             editor.putBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
289         }
290         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, false);
291         editor.apply();
292     }
293 
294     /** Show the most recently saved notification. **/
showSavedNotification()295     private boolean showSavedNotification() {
296         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
297                 getApplicationContext());
298         long clockBaseTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_BASE, -1);
299         long clockElapsedTime = prefs.getLong(Stopwatches.NOTIF_CLOCK_ELAPSED, -1);
300         boolean clockRunning = prefs.getBoolean(Stopwatches.NOTIF_CLOCK_RUNNING, false);
301         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, -1);
302         if (clockBaseTime == -1) {
303             if (clockElapsedTime == -1) {
304                 return false;
305             } else {
306                 // We don't have a clock base time, so the clock is stopped.
307                 // Use the elapsed time to figure out what time to show.
308                 mElapsedTime = clockElapsedTime;
309                 clockBaseTime = Utils.getTimeNow() - clockElapsedTime;
310             }
311         }
312         setNotification(clockBaseTime, clockRunning, numLaps);
313         return true;
314     }
315 
clearSavedNotification()316     private void clearSavedNotification() {
317         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
318                 getApplicationContext());
319         SharedPreferences.Editor editor = prefs.edit();
320         editor.remove(Stopwatches.NOTIF_CLOCK_BASE);
321         editor.remove(Stopwatches.NOTIF_CLOCK_RUNNING);
322         editor.remove(Stopwatches.NOTIF_CLOCK_ELAPSED);
323         editor.apply();
324     }
325 
closeNotificationShade()326     private void closeNotificationShade() {
327         Intent intent = new Intent();
328         intent.setAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
329         sendBroadcast(intent);
330     }
331 
readFromSharedPrefs()332     private void readFromSharedPrefs() {
333         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
334                 getApplicationContext());
335         mStartTime = prefs.getLong(Stopwatches.PREF_START_TIME, 0);
336         mElapsedTime = prefs.getLong(Stopwatches.PREF_ACCUM_TIME, 0);
337         mNumLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
338     }
339 
readLapsFromPrefs()340     private long[] readLapsFromPrefs() {
341         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
342                 getApplicationContext());
343         int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, Stopwatches.STOPWATCH_RESET);
344         long[] laps = new long[numLaps];
345         long prevLapElapsedTime = 0;
346         for (int lap_i = 0; lap_i < numLaps; lap_i++) {
347             String key = Stopwatches.PREF_LAP_TIME + Integer.toString(lap_i + 1);
348             long lap = prefs.getLong(key, 0);
349             if (lap == prevLapElapsedTime && lap_i == numLaps - 1) {
350                 lap = mElapsedTime;
351             }
352             laps[numLaps - lap_i - 1] = lap - prevLapElapsedTime;
353             prevLapElapsedTime = lap;
354         }
355         return laps;
356     }
357 
writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime, Integer state, boolean updateCircle)358     private void writeToSharedPrefs(Long startTime, Long lapTimeElapsed, Long elapsedTime,
359             Integer state, boolean updateCircle) {
360         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
361                 getApplicationContext());
362         SharedPreferences.Editor editor = prefs.edit();
363         if (startTime != null) {
364             editor.putLong(Stopwatches.PREF_START_TIME, startTime);
365             mStartTime = startTime;
366         }
367         if (lapTimeElapsed != null) {
368             int numLaps = prefs.getInt(Stopwatches.PREF_LAP_NUM, 0);
369             if (numLaps == 0) {
370                 mNumLaps++;
371                 numLaps++;
372             }
373             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
374             numLaps++;
375             editor.putLong(Stopwatches.PREF_LAP_TIME + Integer.toString(numLaps), lapTimeElapsed);
376             editor.putInt(Stopwatches.PREF_LAP_NUM, numLaps);
377         }
378         if (elapsedTime != null) {
379             editor.putLong(Stopwatches.PREF_ACCUM_TIME, elapsedTime);
380             mElapsedTime = elapsedTime;
381         }
382         if (state != null) {
383             if (state == Stopwatches.STOPWATCH_RESET) {
384                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RESET);
385             } else if (state == Stopwatches.STOPWATCH_RUNNING) {
386                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_RUNNING);
387             } else if (state == Stopwatches.STOPWATCH_STOPPED) {
388                 editor.putInt(Stopwatches.PREF_STATE, Stopwatches.STOPWATCH_STOPPED);
389             }
390         }
391         editor.putBoolean(Stopwatches.PREF_UPDATE_CIRCLE, updateCircle);
392         editor.apply();
393     }
394 
writeSharedPrefsStarted(long startTime, boolean updateCircle)395     private void writeSharedPrefsStarted(long startTime, boolean updateCircle) {
396         writeToSharedPrefs(startTime, null, null, Stopwatches.STOPWATCH_RUNNING, updateCircle);
397         if (updateCircle) {
398             long time = Utils.getTimeNow();
399             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
400                     getApplicationContext());
401             long intervalStartTime = prefs.getLong(
402                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
403             if (intervalStartTime != -1) {
404                 intervalStartTime = time;
405                 SharedPreferences.Editor editor = prefs.edit();
406                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START,
407                         intervalStartTime);
408                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
409                 editor.apply();
410             }
411         }
412     }
413 
writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle)414     private void writeSharedPrefsLap(long lapTimeElapsed, boolean updateCircle) {
415         writeToSharedPrefs(null, lapTimeElapsed, null, null, updateCircle);
416         if (updateCircle) {
417             long time = Utils.getTimeNow();
418             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
419                     getApplicationContext());
420             SharedPreferences.Editor editor = prefs.edit();
421             long laps[] = readLapsFromPrefs();
422             int numLaps = laps.length;
423             long lapTime = laps[1];
424             if (numLaps == 2) { // Have only hit lap once.
425                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL, lapTime);
426             } else {
427                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_MARKER_TIME, lapTime);
428             }
429             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
430             if (numLaps < Stopwatches.MAX_LAPS) {
431                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, time);
432                 editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, false);
433             } else {
434                 editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
435             }
436             editor.apply();
437         }
438     }
439 
writeSharedPrefsStopped(long elapsedTime, boolean updateCircle)440     private void writeSharedPrefsStopped(long elapsedTime, boolean updateCircle) {
441         writeToSharedPrefs(null, null, elapsedTime, Stopwatches.STOPWATCH_STOPPED, updateCircle);
442         if (updateCircle) {
443             long time = Utils.getTimeNow();
444             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
445                     getApplicationContext());
446             long accumulatedTime = prefs.getLong(
447                     Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, 0);
448             long intervalStartTime = prefs.getLong(
449                     Stopwatches.KEY + CircleTimerView.PREF_CTV_INTERVAL_START, -1);
450             accumulatedTime += time - intervalStartTime;
451             SharedPreferences.Editor editor = prefs.edit();
452             editor.putLong(Stopwatches.KEY + CircleTimerView.PREF_CTV_ACCUM_TIME, accumulatedTime);
453             editor.putBoolean(Stopwatches.KEY + CircleTimerView.PREF_CTV_PAUSED, true);
454             editor.putLong(
455                     Stopwatches.KEY + CircleTimerView.PREF_CTV_CURRENT_INTERVAL, accumulatedTime);
456             editor.apply();
457         }
458     }
459 
writeSharedPrefsReset(boolean updateCircle)460     private void writeSharedPrefsReset(boolean updateCircle) {
461         writeToSharedPrefs(null, null, null, Stopwatches.STOPWATCH_RESET, updateCircle);
462     }
463 }
464