/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.logcat; import android.annotation.StyleRes; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.style.URLSpan; import android.util.Slog; import android.view.ContextThemeWrapper; import android.view.InflateException; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ILogAccessDialogCallback; import com.android.systemui.res.R; /** * Dialog responsible for obtaining user consent per-use log access */ public class LogAccessDialogActivity extends Activity implements View.OnClickListener { private static final String TAG = LogAccessDialogActivity.class.getSimpleName(); public static final String EXTRA_CALLBACK = "EXTRA_CALLBACK"; private static final int DIALOG_TIME_OUT = Build.IS_DEBUGGABLE ? 60000 : 300000; private static final int MSG_DISMISS_DIALOG = 0; private String mPackageName; private int mUid; private ILogAccessDialogCallback mCallback; private String mAlertTitle; private String mAlertBody; private boolean mAlertLearnMoreLink; private SpannableString mAlertLearnMore; private AlertDialog.Builder mAlertDialog; private AlertDialog mAlert; @VisibleForTesting protected View mAlertView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // retrieve Intent extra information if (!readIntentInfo(getIntent())) { Slog.e(TAG, "Invalid Intent extras, finishing"); finish(); return; } // retrieve the title string from passed intent extra try { mAlertTitle = getTitleString(this, mPackageName, mUid); } catch (NameNotFoundException e) { Slog.e(TAG, "Unable to fetch label of package " + mPackageName, e); declineLogAccess(); finish(); return; } mAlertBody = getString(R.string.log_access_confirmation_body); mAlertLearnMoreLink = this.getResources() .getBoolean(R.bool.log_access_confirmation_learn_more_as_link); if (mAlertLearnMoreLink) { mAlertLearnMore = new SpannableString( getString(R.string.log_access_confirmation_learn_more)); mAlertLearnMore.setSpan(new URLSpan( getString(R.string.log_access_confirmation_learn_more_url)), 0, mAlertLearnMore.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { mAlertLearnMore = new SpannableString( getString(R.string.log_access_confirmation_learn_more_at, getString(R.string.log_access_confirmation_learn_more_url))); } // create View int themeId = R.style.LogAccessDialogTheme; mAlertView = createView(themeId); // create AlertDialog mAlertDialog = new AlertDialog.Builder(this, themeId); mAlertDialog.setView(mAlertView); mAlertDialog.setOnCancelListener(dialog -> declineLogAccess()); mAlertDialog.setOnDismissListener(dialog -> { mAlert = null; finish(); }); // show Alert mAlert = mAlertDialog.create(); mAlert.getWindow().setHideOverlayWindows(true); mAlert.show(); // set Alert Timeout mHandler.sendEmptyMessageDelayed(MSG_DISMISS_DIALOG, DIALOG_TIME_OUT); } @Override protected void onStop() { super.onStop(); if (!isChangingConfigurations() && mAlert != null) { mAlert.cancel(); } } private boolean readIntentInfo(Intent intent) { if (intent == null) { Slog.e(TAG, "Intent is null"); return false; } mCallback = ILogAccessDialogCallback.Stub.asInterface( intent.getExtras().getBinder(EXTRA_CALLBACK)); if (mCallback == null) { Slog.e(TAG, "Missing callback"); return false; } mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); if (mPackageName == null || mPackageName.length() == 0) { Slog.e(TAG, "Missing package name extra"); return false; } if (!intent.hasExtra(Intent.EXTRA_UID)) { Slog.e(TAG, "Missing EXTRA_UID"); return false; } mUid = intent.getIntExtra(Intent.EXTRA_UID, 0); return true; } private Handler mHandler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case MSG_DISMISS_DIALOG: if (mAlert != null) { mAlert.dismiss(); declineLogAccess(); } break; default: break; } } }; protected String getTitleString(Context context, String callingPackage, int uid) throws NameNotFoundException { PackageManager pm = context.getPackageManager(); CharSequence appLabel = pm.getApplicationInfoAsUser(callingPackage, PackageManager.MATCH_DIRECT_BOOT_AUTO, UserHandle.getUserId(uid)).loadLabel(pm); String titleString = context.getString(R.string.log_access_confirmation_title, appLabel); return titleString; } /** * Returns the dialog view. * If we cannot retrieve the package name, it returns null and we decline the full device log * access */ private View createView(@StyleRes int themeId) { Context themedContext = new ContextThemeWrapper(this, themeId); final View view = LayoutInflater.from(themedContext).inflate( R.layout.log_access_user_consent_dialog_permission, null /*root*/); if (view == null) { throw new InflateException(); } ((TextView) view.findViewById(R.id.log_access_dialog_title)) .setText(mAlertTitle); if (!TextUtils.isEmpty(mAlertLearnMore)) { ((TextView) view.findViewById(R.id.log_access_dialog_body)) .setText(TextUtils.concat(mAlertBody, "\n\n", mAlertLearnMore)); if (mAlertLearnMoreLink) { ((TextView) view.findViewById(R.id.log_access_dialog_body)) .setMovementMethod(LinkMovementMethod.getInstance()); } } else { ((TextView) view.findViewById(R.id.log_access_dialog_body)) .setText(mAlertBody); } Button button_allow = (Button) view.findViewById(R.id.log_access_dialog_allow_button); button_allow.setOnClickListener(this); Button button_deny = (Button) view.findViewById(R.id.log_access_dialog_deny_button); button_deny.setOnClickListener(this); return view; } @Override public void onClick(View view) { try { if (view.getId() == R.id.log_access_dialog_allow_button) { mCallback.approveAccessForClient(mUid, mPackageName); } else if (view.getId() == R.id.log_access_dialog_deny_button) { declineLogAccess(); } } catch (RemoteException ignored) { // Do nothing. } finally { mAlert.dismiss(); } } private void declineLogAccess() { try { mCallback.declineAccessForClient(mUid, mPackageName); } catch (RemoteException e) { finish(); } } }