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 116 private final PendingIntent mPendingIntent; 117 private final Context mContext; 118 PendingIntentRef(PendingIntent pendingIntent, Context context)119 public PendingIntentRef(PendingIntent pendingIntent, Context context) { 120 mPendingIntent = pendingIntent; 121 mContext = context; 122 } 123 124 @Override sendDataBroadcast(long lastReportTimeNs)125 public void sendDataBroadcast(long lastReportTimeNs) { 126 enforceStatsdCallingUid(); 127 Intent intent = new Intent(); 128 intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs); 129 try { 130 mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null); 131 } catch (PendingIntent.CanceledException e) { 132 Log.w(TAG, "Unable to send PendingIntent"); 133 } 134 } 135 136 @Override sendActiveConfigsChangedBroadcast(long[] configIds)137 public void sendActiveConfigsChangedBroadcast(long[] configIds) { 138 enforceStatsdCallingUid(); 139 Intent intent = new Intent(); 140 intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); 141 try { 142 mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); 143 if (DEBUG) { 144 Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); 145 } 146 } catch (PendingIntent.CanceledException e) { 147 Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); 148 } 149 } 150 151 @Override sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, String[] cookies, StatsDimensionsValueParcel dimensionsValueParcel)152 public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, 153 long subscriptionRuleId, String[] cookies, 154 StatsDimensionsValueParcel dimensionsValueParcel) { 155 enforceStatsdCallingUid(); 156 StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel); 157 Intent intent = 158 new Intent() 159 .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) 160 .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configId) 161 .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId) 162 .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, 163 subscriptionRuleId) 164 .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue); 165 166 ArrayList<String> cookieList = new ArrayList<>(cookies.length); 167 cookieList.addAll(Arrays.asList(cookies)); 168 intent.putStringArrayListExtra( 169 StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); 170 171 if (DEBUG) { 172 Log.d(TAG, 173 String.format( 174 "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", 175 configUid, configId, subscriptionId, subscriptionRuleId, 176 Arrays.toString(cookies), 177 dimensionsValue)); 178 } 179 try { 180 mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); 181 } catch (PendingIntent.CanceledException e) { 182 Log.w(TAG, 183 "Unable to send using PendingIntent from uid " + configUid 184 + "; presumably it had been cancelled."); 185 } 186 } 187 } 188 } 189