Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file |
| 5 | * except in compliance with the License. You may obtain a copy of the License at |
| 6 | * |
| 7 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | * |
| 9 | * Unless required by applicable law or agreed to in writing, software distributed under the |
| 10 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 11 | * KIND, either express or implied. See the License for the specific language governing |
| 12 | * permissions and limitations under the License. |
| 13 | */ |
| 14 | |
| 15 | package com.android.egg.neko; |
| 16 | |
| 17 | import android.app.Notification; |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 18 | import android.app.NotificationChannel; |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 19 | import android.app.NotificationManager; |
| 20 | import android.app.job.JobInfo; |
| 21 | import android.app.job.JobParameters; |
| 22 | import android.app.job.JobScheduler; |
| 23 | import android.app.job.JobService; |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 24 | import android.content.ComponentName; |
| 25 | import android.content.Context; |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 26 | import android.net.Uri; |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 27 | import android.os.Bundle; |
| 28 | |
| 29 | import java.util.List; |
| 30 | import android.util.Log; |
| 31 | |
| 32 | import com.android.egg.R; |
| 33 | |
| 34 | import java.util.Random; |
| 35 | |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 36 | import static com.android.egg.neko.Cat.PURR; |
| 37 | import static com.android.egg.neko.NekoLand.CHAN_ID; |
| 38 | |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 39 | public class NekoService extends JobService { |
| 40 | |
| 41 | private static final String TAG = "NekoService"; |
| 42 | |
| 43 | public static int JOB_ID = 42; |
| 44 | |
| 45 | public static int CAT_NOTIFICATION = 1; |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 46 | public static int DEBUG_NOTIFICATION = 1234; |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 47 | |
| 48 | public static float CAT_CAPTURE_PROB = 1.0f; // generous |
| 49 | |
| 50 | public static long SECONDS = 1000; |
| 51 | public static long MINUTES = 60 * SECONDS; |
| 52 | |
| 53 | public static long INTERVAL_FLEX = 5 * MINUTES; |
| 54 | |
| 55 | public static float INTERVAL_JITTER_FRAC = 0.25f; |
| 56 | |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 57 | private static void setupNotificationChannels(Context context) { |
| 58 | NotificationManager noman = context.getSystemService(NotificationManager.class); |
| 59 | NotificationChannel eggChan = new NotificationChannel(CHAN_ID, |
| 60 | context.getString(R.string.notification_channel_name), |
| 61 | NotificationManager.IMPORTANCE_DEFAULT); |
| 62 | eggChan.setSound(Uri.EMPTY, Notification.AUDIO_ATTRIBUTES_DEFAULT); // cats are quiet |
| 63 | eggChan.setVibrationPattern(PURR); // not totally quiet though |
| 64 | eggChan.setBlockableSystem(true); // unlike a real cat, you can push this one off your lap |
| 65 | eggChan.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); // cats sit in the window |
| 66 | noman.createNotificationChannel(eggChan); |
| 67 | } |
| 68 | |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 69 | @Override |
| 70 | public boolean onStartJob(JobParameters params) { |
| 71 | Log.v(TAG, "Starting job: " + String.valueOf(params)); |
| 72 | |
| 73 | NotificationManager noman = getSystemService(NotificationManager.class); |
| 74 | if (NekoLand.DEBUG_NOTIFICATIONS) { |
| 75 | final Bundle extras = new Bundle(); |
| 76 | extras.putString("android.substName", getString(R.string.notification_name)); |
| 77 | final int size = getResources() |
| 78 | .getDimensionPixelSize(android.R.dimen.notification_large_icon_width); |
| 79 | final Cat cat = Cat.create(this); |
| 80 | final Notification.Builder builder |
| 81 | = cat.buildNotification(this) |
| 82 | .setContentTitle("DEBUG") |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 83 | .setChannel(NekoLand.CHAN_ID) |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 84 | .setContentText("Ran job: " + params); |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 85 | noman.notify(DEBUG_NOTIFICATION, builder.build()); |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | final PrefState prefs = new PrefState(this); |
| 89 | int food = prefs.getFoodState(); |
| 90 | if (food != 0) { |
| 91 | prefs.setFoodState(0); // nom |
| 92 | final Random rng = new Random(); |
| 93 | if (rng.nextFloat() <= CAT_CAPTURE_PROB) { |
| 94 | Cat cat; |
| 95 | List<Cat> cats = prefs.getCats(); |
| 96 | final int[] probs = getResources().getIntArray(R.array.food_new_cat_prob); |
| 97 | final float new_cat_prob = (float)((food < probs.length) ? probs[food] : 50) / 100f; |
| 98 | |
| 99 | if (cats.size() == 0 || rng.nextFloat() <= new_cat_prob) { |
| 100 | cat = Cat.create(this); |
| 101 | prefs.addCat(cat); |
Chris Wren | a801d40 | 2016-06-29 11:30:59 -0400 | [diff] [blame] | 102 | cat.logAdd(this); |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 103 | Log.v(TAG, "A new cat is here: " + cat.getName()); |
| 104 | } else { |
| 105 | cat = cats.get(rng.nextInt(cats.size())); |
| 106 | Log.v(TAG, "A cat has returned: " + cat.getName()); |
| 107 | } |
| 108 | |
| 109 | final Notification.Builder builder = cat.buildNotification(this); |
| 110 | noman.notify(CAT_NOTIFICATION, builder.build()); |
| 111 | } |
| 112 | } |
| 113 | cancelJob(this); |
| 114 | return false; |
| 115 | } |
| 116 | |
| 117 | @Override |
| 118 | public boolean onStopJob(JobParameters jobParameters) { |
| 119 | return false; |
| 120 | } |
| 121 | |
Jason Monk | 946c2b8 | 2016-08-23 12:48:31 -0400 | [diff] [blame] | 122 | public static void registerJobIfNeeded(Context context, long intervalMinutes) { |
| 123 | JobScheduler jss = context.getSystemService(JobScheduler.class); |
| 124 | JobInfo info = jss.getPendingJob(JOB_ID); |
| 125 | if (info == null) { |
| 126 | registerJob(context, intervalMinutes); |
| 127 | } |
| 128 | } |
| 129 | |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 130 | public static void registerJob(Context context, long intervalMinutes) { |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 131 | setupNotificationChannels(context); |
| 132 | |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 133 | JobScheduler jss = context.getSystemService(JobScheduler.class); |
| 134 | jss.cancel(JOB_ID); |
| 135 | long interval = intervalMinutes * MINUTES; |
| 136 | long jitter = (long)(INTERVAL_JITTER_FRAC * interval); |
| 137 | interval += (long)(Math.random() * (2 * jitter)) - jitter; |
| 138 | final JobInfo jobInfo = new JobInfo.Builder(JOB_ID, |
| 139 | new ComponentName(context, NekoService.class)) |
| 140 | .setPeriodic(interval, INTERVAL_FLEX) |
| 141 | .build(); |
| 142 | |
| 143 | Log.v(TAG, "A cat will visit in " + interval + "ms: " + String.valueOf(jobInfo)); |
| 144 | jss.schedule(jobInfo); |
| 145 | |
| 146 | if (NekoLand.DEBUG_NOTIFICATIONS) { |
| 147 | NotificationManager noman = context.getSystemService(NotificationManager.class); |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 148 | noman.notify(DEBUG_NOTIFICATION, new Notification.Builder(context) |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 149 | .setSmallIcon(R.drawable.stat_icon) |
| 150 | .setContentTitle(String.format("Job scheduled in %d min", (interval / MINUTES))) |
| 151 | .setContentText(String.valueOf(jobInfo)) |
| 152 | .setPriority(Notification.PRIORITY_MIN) |
| 153 | .setCategory(Notification.CATEGORY_SERVICE) |
Dan Sandler | 7bcd6aa | 2017-09-04 21:36:01 -0400 | [diff] [blame] | 154 | .setChannel(NekoLand.CHAN_ID) |
Dan Sandler | 27a9fcc | 2016-06-22 00:05:11 -0400 | [diff] [blame] | 155 | .setShowWhen(true) |
| 156 | .build()); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | public static void cancelJob(Context context) { |
| 161 | JobScheduler jss = context.getSystemService(JobScheduler.class); |
| 162 | Log.v(TAG, "Canceling job"); |
| 163 | jss.cancel(JOB_ID); |
| 164 | } |
| 165 | } |