Home | History | Annotate | Download | only in neko
      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;
     18 import android.app.NotificationChannel;
     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;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.net.Uri;
     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 
     36 import static com.android.egg.neko.Cat.PURR;
     37 import static com.android.egg.neko.NekoLand.CHAN_ID;
     38 
     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;
     46     public static int DEBUG_NOTIFICATION = 1234;
     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 
     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 
     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")
     83                         .setChannel(NekoLand.CHAN_ID)
     84                         .setContentText("Ran job: " + params);
     85             noman.notify(DEBUG_NOTIFICATION, builder.build());
     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);
    102                     cat.logAdd(this);
    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 
    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 
    130     public static void registerJob(Context context, long intervalMinutes) {
    131         setupNotificationChannels(context);
    132 
    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);
    148             noman.notify(DEBUG_NOTIFICATION, new Notification.Builder(context)
    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)
    154                     .setChannel(NekoLand.CHAN_ID)
    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 }
    166