1 /*
2  * Copyright (C) 2022 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 #include "src/traced/probes/android_game_intervention_list/android_game_intervention_list_data_source.h"
18 
19 #include <stddef.h>
20 #include <optional>
21 
22 #include "perfetto/base/logging.h"
23 #include "perfetto/ext/base/scoped_file.h"
24 #include "perfetto/ext/base/string_splitter.h"
25 #include "perfetto/ext/base/string_utils.h"
26 #include "perfetto/ext/tracing/core/trace_writer.h"
27 
28 #include "perfetto/tracing/core/data_source_config.h"
29 #include "protos/perfetto/config/android/android_game_intervention_list_config.pbzero.h"
30 #include "protos/perfetto/trace/android/android_game_intervention_list.pbzero.h"
31 #include "protos/perfetto/trace/trace_packet.pbzero.h"
32 
33 namespace perfetto {
34 
35 const char kAndroidGameInterventionListFileName[] =
36     "/data/system/game_mode_intervention.list";
37 
38 // making the descriptor static
39 const ProbesDataSource::Descriptor
40     AndroidGameInterventionListDataSource::descriptor = {
41         /* name */ "android.game_interventions",
42         /* flags */ Descriptor::kFlagsNone,
43         /* fill_descriptor_func */ nullptr,
44 };
45 
AndroidGameInterventionListDataSource(const DataSourceConfig & ds_config,TracingSessionID session_id,std::unique_ptr<TraceWriter> trace_writer)46 AndroidGameInterventionListDataSource::AndroidGameInterventionListDataSource(
47     const DataSourceConfig& ds_config,
48     TracingSessionID session_id,
49     std::unique_ptr<TraceWriter> trace_writer)
50     : ProbesDataSource(session_id, &descriptor),
51       trace_writer_(std::move(trace_writer)) {
52   perfetto::protos::pbzero::AndroidGameInterventionListConfig::Decoder cfg(
53       ds_config.android_game_intervention_list_config_raw());
54   for (auto name = cfg.package_name_filter(); name; ++name) {
55     package_name_filter_.emplace_back((*name).ToStdString());
56   }
57 }
58 
59 AndroidGameInterventionListDataSource::
60     ~AndroidGameInterventionListDataSource() = default;
61 
Start()62 void AndroidGameInterventionListDataSource::Start() {
63   auto trace_packet = trace_writer_->NewTracePacket();
64   auto* android_game_intervention_list_packet =
65       trace_packet->set_android_game_intervention_list();
66 
67   base::ScopedFstream fs(fopen(kAndroidGameInterventionListFileName, "r"));
68   if (!fs) {
69     PERFETTO_ELOG("Failed to open %s", kAndroidGameInterventionListFileName);
70     android_game_intervention_list_packet->set_read_error(true);
71   } else {
72     bool is_parsed_fully = ParseAndroidGameInterventionListStream(
73         android_game_intervention_list_packet, fs, package_name_filter_);
74     if (!is_parsed_fully) {
75       android_game_intervention_list_packet->set_parse_error(true);
76     }
77     if (ferror(*fs)) {
78       android_game_intervention_list_packet->set_read_error(true);
79     }
80   }
81 
82   trace_packet->Finalize();
83   trace_writer_->Flush();
84 }
85 
Flush(FlushRequestID,std::function<void ()> callback)86 void AndroidGameInterventionListDataSource::Flush(
87     FlushRequestID,
88     std::function<void()> callback) {
89   callback();
90 }
91 
92 bool AndroidGameInterventionListDataSource::
ParseAndroidGameInterventionListStream(protos::pbzero::AndroidGameInterventionList * packet,const base::ScopedFstream & fs,const std::vector<std::string> & package_name_filter)93     ParseAndroidGameInterventionListStream(
94         protos::pbzero::AndroidGameInterventionList* packet,
95         const base::ScopedFstream& fs,
96         const std::vector<std::string>& package_name_filter) {
97   bool is_parsed_fully = true;
98   char line_buf[2048];
99   while (fgets(line_buf, sizeof(line_buf), *fs) != nullptr) {
100     // removing trailing '\n'
101     // for processing fields with CStringTo* functions
102     line_buf[strlen(line_buf) - 1] = '\0';
103 
104     if (!ParseAndroidGameInterventionListLine(line_buf, package_name_filter,
105                                               packet)) {
106       // marking parsed with error and continue with this line skipped
107       is_parsed_fully = false;
108     }
109   }
110   return is_parsed_fully;
111 }
112 
113 bool AndroidGameInterventionListDataSource::
ParseAndroidGameInterventionListLine(char * line,const std::vector<std::string> & package_name_filter,protos::pbzero::AndroidGameInterventionList * packet)114     ParseAndroidGameInterventionListLine(
115         char* line,
116         const std::vector<std::string>& package_name_filter,
117         protos::pbzero::AndroidGameInterventionList* packet) {
118   size_t idx = 0;
119   perfetto::protos::pbzero::AndroidGameInterventionList_GamePackageInfo*
120       package = nullptr;
121   perfetto::protos::pbzero::AndroidGameInterventionList_GameModeInfo*
122       game_mode_info = nullptr;
123   for (base::StringSplitter string_splitter(line, '\t'); string_splitter.Next();
124        ++idx) {
125     // check if package name is in the name filter
126     // if not we skip parsing this line.
127     if (idx == 0) {
128       if (!package_name_filter.empty() &&
129           std::count(package_name_filter.begin(), package_name_filter.end(),
130                      string_splitter.cur_token()) == 0) {
131         return true;
132       }
133       package = packet->add_game_packages();
134     }
135 
136     switch (idx) {
137       case 0: {
138         package->set_name(string_splitter.cur_token(),
139                           string_splitter.cur_token_size());
140         break;
141       }
142       case 1: {
143         std::optional<uint64_t> uid =
144             base::CStringToUInt64(string_splitter.cur_token());
145         if (uid == std::nullopt) {
146           PERFETTO_DLOG("Failed to parse game_mode_intervention.list uid.");
147           return false;
148         }
149         package->set_uid(uid.value());
150         break;
151       }
152       case 2: {
153         std::optional<uint32_t> cur_mode =
154             base::CStringToUInt32(string_splitter.cur_token());
155         if (cur_mode == std::nullopt) {
156           PERFETTO_DLOG(
157               "Failed to parse game_mode_intervention.list cur_mode.");
158           return false;
159         }
160         package->set_current_mode(cur_mode.value());
161         break;
162       }
163       case 3:
164       case 5:
165       case 7: {
166         std::optional<uint32_t> game_mode =
167             base::CStringToUInt32(string_splitter.cur_token());
168         if (game_mode == std::nullopt) {
169           PERFETTO_DLOG(
170               "Failed to parse game_mode_intervention.list game_mode.");
171           return false;
172         }
173         game_mode_info = package->add_game_mode_info();
174         game_mode_info->set_mode(game_mode.value());
175         break;
176       }
177       case 4:
178       case 6:
179       case 8: {
180         for (base::StringSplitter intervention_splitter(
181                  string_splitter.cur_token(), ',');
182              intervention_splitter.Next();) {
183           base::StringSplitter value_splitter(intervention_splitter.cur_token(),
184                                               '=');
185           value_splitter.Next();
186           char* key = value_splitter.cur_token();
187           if (strcmp(key, "angle") == 0) {
188             value_splitter.Next();
189             std::optional<uint32_t> use_angle =
190                 base::CStringToUInt32(value_splitter.cur_token());
191             if (use_angle == std::nullopt) {
192               PERFETTO_DLOG(
193                   "Failed to parse game_mode_intervention.list use_angle.");
194               return false;
195             }
196             game_mode_info->set_use_angle(use_angle.value());
197           } else if (strcmp(key, "scaling") == 0) {
198             value_splitter.Next();
199             std::optional<double> resolution_downscale =
200                 base::CStringToDouble(value_splitter.cur_token());
201             if (resolution_downscale == std::nullopt) {
202               PERFETTO_DLOG(
203                   "Failed to parse game_mode_intervention.list "
204                   "resolution_downscale.");
205               return false;
206             }
207             game_mode_info->set_resolution_downscale(
208                 static_cast<float>(resolution_downscale.value()));
209           } else if (strcmp(key, "fps") == 0) {
210             value_splitter.Next();
211             std::optional<double> fps =
212                 base::CStringToDouble(value_splitter.cur_token());
213             if (fps == std::nullopt) {
214               PERFETTO_DLOG("Failed to parse game_mode_intervention.list fps.");
215               return false;
216             }
217             game_mode_info->set_fps(static_cast<float>(fps.value()));
218           }
219         }
220         break;
221       }
222     }
223   }
224   return true;
225 }
226 
227 }  // namespace perfetto
228