1 /* 2 * Copyright (C) 2019 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 17 package com.android.server.stats; 18 19 import android.app.PendingIntent; 20 import android.app.StatsManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.Binder; 24 import android.os.IPendingIntentRef; 25 import android.os.Process; 26 import android.os.StatsDimensionsValue; 27 import android.os.StatsDimensionsValueParcel; 28 import android.util.Log; 29 30 import com.android.server.SystemService; 31 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 35 /** 36 * @hide 37 */ 38 public class StatsCompanion { 39 private static final String TAG = "StatsCompanion"; 40 private static final boolean DEBUG = false; 41 42 private static final int AID_STATSD = 1066; 43 44 private static final String STATS_COMPANION_SERVICE = "statscompanion"; 45 private static final String STATS_MANAGER_SERVICE = "statsmanager"; 46 enforceStatsdCallingUid()47 static void enforceStatsdCallingUid() { 48 if (Binder.getCallingPid() == Process.myPid()) { 49 return; 50 } 51 if (Binder.getCallingUid() != AID_STATSD) { 52 throw new SecurityException("Not allowed to access StatsCompanion"); 53 } 54 } 55 56 /** 57 * Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}. 58 */ 59 public static final class Lifecycle extends SystemService { 60 private StatsCompanionService mStatsCompanionService; 61 private StatsManagerService mStatsManagerService; 62 Lifecycle(Context context)63 public Lifecycle(Context context) { 64 super(context); 65 } 66 67 @Override onStart()68 public void onStart() { 69 mStatsCompanionService = new StatsCompanionService(getContext()); 70 mStatsManagerService = new StatsManagerService(getContext()); 71 mStatsCompanionService.setStatsManagerService(mStatsManagerService); 72 mStatsManagerService.setStatsCompanionService(mStatsCompanionService); 73 74 try { 75 publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService); 76 if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE); 77 publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService); 78 if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE); 79 } catch (Exception e) { 80 Log.e(TAG, "Failed to publishBinderService", e); 81 } 82 } 83 84 @Override onBootPhase(int phase)85 public void onBootPhase(int phase) { 86 super.onBootPhase(phase); 87 if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { 88 mStatsCompanionService.systemReady(); 89 } 90 if (phase == PHASE_BOOT_COMPLETED) { 91 mStatsCompanionService.bootCompleted(); 92 } 93 } 94 } 95 96 /** 97 * Wrapper for {@link PendingIntent}. Allows Statsd to send PendingIntents. 98 */ 99 public static class PendingIntentRef extends IPendingIntentRef.Stub { 100 101 private static final String TAG = "PendingIntentRef"; 102 103 /** 104 * The last report time is provided with each intent registered to 105 * StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if 106 * statsd is requesting the client to retrieve the same statsd data. The last report time 107 * corresponds to the last_report_elapsed_nanos that will provided in the current 108 * ConfigMetricsReport, and this timestamp also corresponds to the 109 * current_report_elapsed_nanos of the most recently obtained ConfigMetricsReport. 110 */ 111 private static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME"; 112 private static final int CODE_DATA_BROADCAST = 1; 113 private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1; 114 private static final int CODE_SUBSCRIBER_BROADCAST = 1; 115 private static final int CODE_RESTRICTED_METRICS_BROADCAST = 1; 116 117 private final PendingIntent mPendingIntent; 118 private final Context mContext; 119 PendingIntentRef(PendingIntent pendingIntent, Context context)120 public PendingIntentRef(PendingIntent pendingIntent, Context context) { 121 mPendingIntent = pendingIntent; 122 mContext = context; 123 } 124 125 @Override sendDataBroadcast(long lastReportTimeNs)126 public void sendDataBroadcast(long lastReportTimeNs) { 127 enforceStatsdCallingUid(); 128 Intent intent = new Intent(); 129 intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs); 130 try { 131 mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null); 132 } catch (PendingIntent.CanceledException e) { 133 Log.w(TAG, "Unable to send PendingIntent"); 134 } 135 } 136 137 @Override sendActiveConfigsChangedBroadcast(long[] configIds)138 public void sendActiveConfigsChangedBroadcast(long[] configIds) { 139 enforceStatsdCallingUid(); 140 Intent intent = new Intent(); 141 intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); 142 try { 143 mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); 144 if (DEBUG) { 145 Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); 146 } 147 } catch (PendingIntent.CanceledException e) { 148 Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); 149 } 150 } 151 152 @Override sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, String[] cookies, StatsDimensionsValueParcel dimensionsValueParcel)153 public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, 154 long subscriptionRuleId, String[] cookies, 155 StatsDimensionsValueParcel dimensionsValueParcel) { 156 enforceStatsdCallingUid(); 157 StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel); 158 Intent intent = 159 new Intent() 160 .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) 161 .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configId) 162 .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId) 163 .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, 164 subscriptionRuleId) 165 .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue); 166 167 ArrayList<String> cookieList = new ArrayList<>(cookies.length); 168 cookieList.addAll(Arrays.asList(cookies)); 169 intent.putStringArrayListExtra( 170 StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); 171 172 if (DEBUG) { 173 Log.d(TAG, 174 String.format( 175 "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", 176 configUid, configId, subscriptionId, subscriptionRuleId, 177 Arrays.toString(cookies), 178 dimensionsValue)); 179 } 180 try { 181 mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); 182 } catch (PendingIntent.CanceledException e) { 183 Log.w(TAG, 184 "Unable to send using PendingIntent from uid " + configUid 185 + "; presumably it had been cancelled."); 186 } 187 } 188 189 @Override sendRestrictedMetricsChangedBroadcast(long[] metricIds)190 public void sendRestrictedMetricsChangedBroadcast(long[] metricIds) { 191 enforceStatsdCallingUid(); 192 Intent intent = new Intent(); 193 intent.putExtra(StatsManager.EXTRA_STATS_RESTRICTED_METRIC_IDS, metricIds); 194 try { 195 mPendingIntent.send(mContext, CODE_RESTRICTED_METRICS_BROADCAST, intent, null, 196 null); 197 if (DEBUG) { 198 Log.d(TAG, 199 "Sent restricted metrics broadcast with metric ids " + Arrays.toString( 200 metricIds)); 201 } 202 } catch (PendingIntent.CanceledException e) { 203 Log.w(TAG, 204 "Unable to send restricted metrics changed broadcast using PendingIntent"); 205 } 206 } 207 } 208 } 209