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