1 /*
2  * Copyright 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 package com.android.angle.common;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.provider.Settings;
27 import android.util.Log;
28 
29 import androidx.preference.PreferenceManager;
30 
31 import org.json.JSONArray;
32 import org.json.JSONException;
33 import org.json.JSONObject;
34 
35 import java.io.IOException;
36 import java.io.InputStream;
37 
38 public class Receiver extends BroadcastReceiver
39 {
40 
41     private static final String TAG              = "AngleReceiver";
42     private static final String ANGLE_RULES_FILE = "a4a_rules.json";
43 
44     @Override
onReceive(Context context, Intent intent)45     public void onReceive(Context context, Intent intent)
46     {
47         String action = intent.getAction();
48         Log.v(TAG, "Received intent: '" + action + "'");
49 
50         if (action.equals(context.getString(R.string.intent_angle_for_android_toast_message)))
51         {
52             Bundle results = getResultExtras(true);
53             results.putString(context.getString(R.string.intent_key_a4a_toast_message),
54                     context.getString(R.string.angle_in_use_toast_message));
55         }
56         else
57         {
58             String jsonStr      = loadRules(context);
59             String packageNames = parsePackageNames(jsonStr);
60 
61             // Update the ANGLE allowlist
62             if (packageNames != null)
63             {
64                 GlobalSettings.updateAngleAllowlist(context, packageNames);
65             }
66 
67             updateDeveloperOptionsWatcher(context);
68         }
69     }
70 
71     /*
72      * Open the rules file and pull all the JSON into a string
73      */
loadRules(Context context)74     private String loadRules(Context context)
75     {
76         String jsonStr = null;
77 
78         try
79         {
80             InputStream rulesStream = context.getAssets().open(ANGLE_RULES_FILE);
81             int size                = rulesStream.available();
82             byte[] buffer           = new byte[size];
83             rulesStream.read(buffer);
84             rulesStream.close();
85             jsonStr = new String(buffer, "UTF-8");
86         }
87         catch (IOException ioe)
88         {
89             Log.e(TAG, "Failed to open " + ANGLE_RULES_FILE + ": ", ioe);
90         }
91 
92         return jsonStr;
93     }
94 
95     /*
96      * Extract all app package names from the json file and return them comma separated
97      */
parsePackageNames(String rulesJSON)98     private String parsePackageNames(String rulesJSON)
99     {
100         StringBuilder packageNames = new StringBuilder();
101 
102         try
103         {
104             JSONObject jsonObj = new JSONObject(rulesJSON);
105             JSONArray rules    = jsonObj.getJSONArray("Rules");
106             if (rules == null)
107             {
108                 Log.e(TAG, "No Rules in " + ANGLE_RULES_FILE);
109                 return null;
110             }
111             for (int i = 0; i < rules.length(); i++)
112             {
113                 JSONObject rule = rules.getJSONObject(i);
114                 JSONArray apps  = rule.optJSONArray("Applications");
115                 if (apps == null)
116                 {
117                     Log.v(TAG, "Skipping Rules entry with no Applications");
118                     continue;
119                 }
120                 for (int j = 0; j < apps.length(); j++)
121                 {
122                     JSONObject app = apps.optJSONObject(j);
123                     String appName = app.optString("AppName");
124                     if ((appName == null) || appName.isEmpty())
125                     {
126                         Log.e(TAG, "Invalid AppName: '" + appName + "'");
127                     }
128                     if (!packageNames.toString().isEmpty())
129                     {
130                         packageNames.append(",");
131                     }
132                     packageNames.append(appName);
133                 }
134             }
135             Log.v(TAG, "Parsed the following package names from " + ANGLE_RULES_FILE + ": "
136                                + packageNames);
137         }
138         catch (JSONException je)
139         {
140             Log.e(TAG, "Error when parsing angle JSON: ", je);
141             return null;
142         }
143 
144         return packageNames.toString();
145     }
146 
updateAllUseAngle(Context context)147     static void updateAllUseAngle(Context context)
148     {
149         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
150         String allUseAngleKey   = context.getString(R.string.pref_key_all_angle);
151         boolean allUseAngle     = prefs.getBoolean(allUseAngleKey, false);
152 
153         GlobalSettings.updateAllUseAngle(context, allUseAngle);
154 
155         Log.v(TAG, "All PKGs use ANGLE set to: " + allUseAngle);
156     }
157 
updateShowAngleInUseDialogBox(Context context)158     static void updateShowAngleInUseDialogBox(Context context)
159     {
160         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
161         String showAngleInUseDialogBoxKey =
162                 context.getString(R.string.pref_key_angle_in_use_dialog);
163         boolean showAngleInUseDialogBox = prefs.getBoolean(showAngleInUseDialogBoxKey, false);
164 
165         GlobalSettings.updateShowAngleInUseDialog(context, showAngleInUseDialogBox);
166 
167         Log.v(TAG, "Show 'ANGLE In Use' dialog box set to: " + showAngleInUseDialogBox);
168     }
169 
170     /**
171      * When Developer Options are disabled, reset all of the global settings back to their defaults.
172      */
updateDeveloperOptionsWatcher(Context context)173     private static void updateDeveloperOptionsWatcher(Context context)
174     {
175         Uri settingUri = Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
176 
177         ContentObserver developerOptionsObserver = new ContentObserver(new Handler()) {
178             @Override
179             public void onChange(boolean selfChange)
180             {
181                 super.onChange(selfChange);
182 
183                 boolean developerOptionsEnabled =
184                         (1
185                                 == Settings.Global.getInt(context.getContentResolver(),
186                                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0));
187 
188                 Log.v(TAG, "Developer Options enabled value changed: "
189                                    + "developerOptionsEnabled = " + developerOptionsEnabled);
190 
191                 if (!developerOptionsEnabled)
192                 {
193                     // Reset the necessary settings to their defaults.
194                     SharedPreferences.Editor editor =
195                             PreferenceManager.getDefaultSharedPreferences(context).edit();
196                     editor.clear();
197                     editor.apply();
198                     GlobalSettings.clearAllGlobalSettings(context);
199                 }
200             }
201         };
202 
203         context.getContentResolver().registerContentObserver(
204                 settingUri, false, developerOptionsObserver);
205         developerOptionsObserver.onChange(true);
206     }
207 }
208