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