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 package com.android.systemui.logcat;
18 
19 import android.annotation.StyleRes;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.os.Build;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.RemoteException;
30 import android.os.UserHandle;
31 import android.text.SpannableString;
32 import android.text.Spanned;
33 import android.text.TextUtils;
34 import android.text.method.LinkMovementMethod;
35 import android.text.style.URLSpan;
36 import android.util.Slog;
37 import android.view.ContextThemeWrapper;
38 import android.view.InflateException;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.widget.Button;
42 import android.widget.TextView;
43 
44 import com.android.internal.app.ILogAccessDialogCallback;
45 import com.android.systemui.R;
46 
47 
48 /**
49  * Dialog responsible for obtaining user consent per-use log access
50  */
51 public class LogAccessDialogActivity extends Activity implements
52         View.OnClickListener {
53     private static final String TAG = LogAccessDialogActivity.class.getSimpleName();
54     public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK";
55 
56 
57     private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000;
58     private static final int MSG_DISMISS_DIALOG = 0;
59 
60     private String mPackageName;
61     private int mUid;
62     private ILogAccessDialogCallback mCallback;
63 
64     private String mAlertTitle;
65     private String mAlertBody;
66 
67     private boolean mAlertLearnMoreLink;
68     private SpannableString mAlertLearnMore;
69 
70     private AlertDialog.Builder mAlertDialog;
71     private AlertDialog mAlert;
72     private View mAlertView;
73 
74     @Override
onCreate(Bundle savedInstanceState)75     protected void onCreate(Bundle savedInstanceState) {
76         super.onCreate(savedInstanceState);
77 
78         // retrieve Intent extra information
79         if (!readIntentInfo(getIntent())) {
80             Slog.e(TAG, "Invalid Intent extras, finishing");
81             finish();
82             return;
83         }
84 
85         // retrieve the title string from passed intent extra
86         try {
87             mAlertTitle = getTitleString(this, mPackageName, mUid);
88         } catch (NameNotFoundException e) {
89             Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e);
90             declineLogAccess();
91             finish();
92             return;
93         }
94 
95         mAlertBody = getString(R.string.log_access_confirmation_body);
96         mAlertLearnMoreLink = this.getResources()
97             .getBoolean(R.bool.log_access_confirmation_learn_more_as_link);
98         if (mAlertLearnMoreLink) {
99             mAlertLearnMore = new SpannableString(
100                     getString(R.string.log_access_confirmation_learn_more));
101             mAlertLearnMore.setSpan(new URLSpan(
102                     getString(R.string.log_access_confirmation_learn_more_url)),
103                     0, mAlertLearnMore.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
104         } else {
105             mAlertLearnMore = new SpannableString(
106                 getString(R.string.log_access_confirmation_learn_more_at,
107                 getString(R.string.log_access_confirmation_learn_more_url)));
108         }
109 
110         // create View
111         int themeId = R.style.LogAccessDialogTheme;
112         mAlertView = createView(themeId);
113 
114         // create AlertDialog
115         mAlertDialog = new AlertDialog.Builder(this, themeId);
116         mAlertDialog.setView(mAlertView);
117         mAlertDialog.setOnCancelListener(dialog -> declineLogAccess());
118         mAlertDialog.setOnDismissListener(dialog -> finish());
119 
120         // show Alert
121         mAlert = mAlertDialog.create();
122         mAlert.getWindow().setHideOverlayWindows(true);
123         mAlert.show();
124 
125         // set Alert Timeout
126         mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT);
127     }
128 
129     @Override
onDestroy()130     protected void onDestroy() {
131         super.onDestroy();
132         if (!isChangingConfigurations() && mAlert != null && mAlert.isShowing()) {
133             mAlert.dismiss();
134         }
135         mAlert = null;
136     }
137 
readIntentInfo(Intent intent)138     private boolean readIntentInfo(Intent intent) {
139         if (intent == null) {
140             Slog.e(TAG, "Intent is null");
141             return false;
142         }
143 
144         mCallback = ILogAccessDialogCallback.Stub.asInterface(
145                 intent.getExtras().getBinder(EXTRA_CALLBACK));
146         if (mCallback == null) {
147             Slog.e(TAG, "Missing callback");
148             return false;
149         }
150 
151         mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
152         if (mPackageName == null || mPackageName.length() == 0) {
153             Slog.e(TAG, "Missing package name extra");
154             return false;
155         }
156 
157         if (!intent.hasExtra(Intent.EXTRA_UID)) {
158             Slog.e(TAG, "Missing EXTRA_UID");
159             return false;
160         }
161 
162         mUid = intent.getIntExtra(Intent.EXTRA_UID, 0);
163 
164         return true;
165     }
166 
167     private Handler mHandler = new Handler() {
168         public void handleMessage(android.os.Message msg) {
169             switch (msg.what) {
170                 case MSG_DISMISS_DIALOG:
171                     if (mAlert != null) {
172                         mAlert.dismiss();
173                         mAlert = null;
174                         declineLogAccess();
175                     }
176                     break;
177 
178                 default:
179                     break;
180             }
181         }
182     };
183 
getTitleString(Context context, String callingPackage, int uid)184     private String getTitleString(Context context, String callingPackage, int uid)
185             throws NameNotFoundException {
186         PackageManager pm = context.getPackageManager();
187 
188         CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage,
189                 PackageManager.MATCH_DIRECT_BOOT_AUTO,
190                 UserHandle.getUserId(uid)).loadLabel(pm);
191 
192         String titleString = context.getString(R.string.log_access_confirmation_title, appLabel);
193 
194         return titleString;
195     }
196 
197     /**
198      * Returns the dialog view.
199      * If we cannot retrieve the package name, it returns null and we decline the full device log
200      * access
201      */
createView(@tyleRes int themeId)202     private View createView(@StyleRes int themeId) {
203         Context themedContext = new ContextThemeWrapper(this, themeId);
204         final View view = LayoutInflater.from(themedContext).inflate(
205                 R.layout.log_access_user_consent_dialog_permission, null /*root*/);
206 
207         if (view == null) {
208             throw new InflateException();
209         }
210 
211         ((TextView) view.findViewById(R.id.log_access_dialog_title))
212             .setText(mAlertTitle);
213 
214         if (!TextUtils.isEmpty(mAlertLearnMore)) {
215             ((TextView) view.findViewById(R.id.log_access_dialog_body))
216                 .setText(TextUtils.concat(mAlertBody, "\n\n", mAlertLearnMore));
217             if (mAlertLearnMoreLink) {
218                 ((TextView) view.findViewById(R.id.log_access_dialog_body))
219                     .setMovementMethod(LinkMovementMethod.getInstance());
220             }
221         } else {
222             ((TextView) view.findViewById(R.id.log_access_dialog_body))
223                     .setText(mAlertBody);
224         }
225 
226         Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button);
227         button_allow.setOnClickListener(this);
228 
229         Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button);
230         button_deny.setOnClickListener(this);
231 
232         return view;
233 
234     }
235 
236     @Override
onClick(View view)237     public void onClick(View view) {
238         try {
239             if (view.getId() == R.id.log_access_dialog_allow_button) {
240                 mCallback.approveAccessForClient(mUid, mPackageName);
241                 finish();
242             } else if (view.getId() == R.id.log_access_dialog_deny_button) {
243                 declineLogAccess();
244                 finish();
245             }
246         } catch (RemoteException e) {
247             finish();
248         }
249     }
250 
declineLogAccess()251     private void declineLogAccess() {
252         try {
253             mCallback.declineAccessForClient(mUid, mPackageName);
254         } catch (RemoteException e) {
255             finish();
256         }
257     }
258 }
259