StatsCompanionService.java revision 6bc51d737b81689567162f3c49226ca234cb7e91
1/* 2 * Copyright (C) 2017 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 */ 16package com.android.server.stats; 17 18import android.app.AlarmManager; 19import android.app.PendingIntent; 20import android.content.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.os.Binder; 24import android.os.IBinder; 25import android.os.IStatsCompanionService; 26import android.os.IStatsManager; 27import android.os.Process; 28import android.os.RemoteException; 29import android.os.ServiceManager; 30import android.util.Slog; 31 32import com.android.internal.annotations.GuardedBy; 33import com.android.server.SystemService; 34 35/** 36 * Helper service for statsd (the native stats management service in cmds/statsd/). 37 * Used for registering and receiving alarms on behalf of statsd. 38 * @hide 39 */ 40public class StatsCompanionService extends IStatsCompanionService.Stub { 41 static final String TAG = "StatsCompanionService"; 42 static final boolean DEBUG = true; 43 44 private final Context mContext; 45 private final AlarmManager mAlarmManager; 46 @GuardedBy("sStatsdLock") 47 private static IStatsManager sStatsd; 48 private static final Object sStatsdLock = new Object(); 49 50 private final PendingIntent mAnomalyAlarmIntent; 51 private final PendingIntent mPollingAlarmIntent; 52 53 public StatsCompanionService(Context context) { 54 super(); 55 mContext = context; 56 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 57 58 mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0, 59 new Intent(mContext, AnomalyAlarmReceiver.class), 0); 60 mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0, 61 new Intent(mContext, PollingAlarmReceiver.class), 0); 62 } 63 64 public final static class AnomalyAlarmReceiver extends BroadcastReceiver { 65 @Override 66 public void onReceive(Context context, Intent intent) { 67 Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred."); 68 synchronized (sStatsdLock) { 69 if (sStatsd == null) { 70 Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); 71 return; 72 } 73 try { 74 // Two-way call to statsd to retain AlarmManager wakelock 75 sStatsd.informAnomalyAlarmFired(); 76 } catch (RemoteException e) { 77 Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); 78 } 79 } 80 // AlarmManager releases its own wakelock here. 81 } 82 }; 83 84 public final static class PollingAlarmReceiver extends BroadcastReceiver { 85 @Override 86 public void onReceive(Context context, Intent intent) { 87 if (DEBUG) Slog.d(TAG, "Time to poll something."); 88 synchronized (sStatsdLock) { 89 if (sStatsd == null) { 90 Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing"); 91 return; 92 } 93 try { 94 // Two-way call to statsd to retain AlarmManager wakelock 95 sStatsd.informPollAlarmFired(); 96 } catch (RemoteException e) { 97 Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e); 98 } 99 } 100 // AlarmManager releases its own wakelock here. 101 } 102 }; 103 104 @Override // Binder call 105 public void setAnomalyAlarm(long timestampMs) { 106 enforceCallingPermission(); 107 if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); 108 final long callingToken = Binder.clearCallingIdentity(); 109 try { 110 // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens. 111 // This alarm is inexact, leaving its exactness completely up to the OS optimizations. 112 // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm. 113 mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent); 114 } finally { 115 Binder.restoreCallingIdentity(callingToken); 116 } 117 } 118 119 @Override // Binder call 120 public void cancelAnomalyAlarm() { 121 enforceCallingPermission(); 122 if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); 123 final long callingToken = Binder.clearCallingIdentity(); 124 try { 125 mAlarmManager.cancel(mAnomalyAlarmIntent); 126 } finally { 127 Binder.restoreCallingIdentity(callingToken); 128 } 129 } 130 131 @Override // Binder call 132 public void setPollingAlarms(long timestampMs, long intervalMs) { 133 enforceCallingPermission(); 134 if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs 135 + " every " + intervalMs + "ms"); 136 final long callingToken = Binder.clearCallingIdentity(); 137 try { 138 // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens. 139 // This alarm is inexact, leaving its exactness completely up to the OS optimizations. 140 // TODO: totally inexact means that stats per bucket could be quite off. Is this okay? 141 mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, 142 mPollingAlarmIntent); 143 } finally { 144 Binder.restoreCallingIdentity(callingToken); 145 } 146 } 147 148 @Override // Binder call 149 public void cancelPollingAlarms() { 150 enforceCallingPermission(); 151 if (DEBUG) Slog.d(TAG, "Cancelling polling alarm"); 152 final long callingToken = Binder.clearCallingIdentity(); 153 try { 154 mAlarmManager.cancel(mPollingAlarmIntent); 155 } finally { 156 Binder.restoreCallingIdentity(callingToken); 157 } 158 } 159 160 @Override 161 public void statsdReady() { 162 enforceCallingPermission(); 163 if (DEBUG) Slog.d(TAG, "learned that statsdReady"); 164 sayHiToStatsd(); // tell statsd that we're ready too and link to it 165 } 166 167 private void enforceCallingPermission() { 168 if (Binder.getCallingPid() == Process.myPid()) { 169 return; 170 } 171 mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); 172 } 173 174 // Lifecycle and related code 175 176 /** Fetches the statsd IBinder service */ 177 private static IStatsManager fetchStatsdService() { 178 return IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); 179 } 180 181 public static final class Lifecycle extends SystemService { 182 private StatsCompanionService mStatsCompanionService; 183 184 public Lifecycle(Context context) { 185 super(context); 186 } 187 188 @Override 189 public void onStart() { 190 mStatsCompanionService = new StatsCompanionService(getContext()); 191 try { 192 publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService); 193 if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); 194 } catch (Exception e) { 195 Slog.e(TAG, "Failed to publishBinderService", e); 196 } 197 } 198 199 @Override 200 public void onBootPhase(int phase) { 201 super.onBootPhase(phase); 202 if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 203 mStatsCompanionService.systemReady(); 204 } 205 } 206 } 207 208 /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */ 209 private void systemReady() { 210 if (DEBUG) Slog.d(TAG, "Learned that systemReady"); 211 sayHiToStatsd(); 212 } 213 214 /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */ 215 private void sayHiToStatsd() { 216 synchronized (sStatsdLock) { 217 if (sStatsd != null) { 218 Slog.e(TAG, "Trying to fetch statsd, but it was already fetched", 219 new IllegalStateException("sStatsd is not null when being fetched")); 220 return; 221 } 222 sStatsd = fetchStatsdService(); 223 if (sStatsd == null) { 224 Slog.w(TAG, "Could not access statsd"); 225 return; 226 } 227 if (DEBUG) Slog.d(TAG, "Saying hi to statsd"); 228 try { 229 sStatsd.statsCompanionReady(); 230 // If the statsCompanionReady two-way binder call returns, link to statsd. 231 try { 232 sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); 233 } catch (RemoteException e) { 234 Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); 235 forgetEverything(); 236 } 237 } catch (RemoteException e) { 238 Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); 239 forgetEverything(); 240 } 241 } 242 } 243 244 private class StatsdDeathRecipient implements IBinder.DeathRecipient { 245 @Override 246 public void binderDied() { 247 Slog.i(TAG, "Statsd is dead - erase all my knowledge."); 248 forgetEverything(); 249 } 250 } 251 252 private void forgetEverything() { 253 synchronized (sStatsdLock) { 254 sStatsd = null; 255 cancelAnomalyAlarm(); 256 cancelPollingAlarms(); 257 } 258 } 259 260} 261