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