SlicePermissionActivity.java

/*
 * Copyright 2018 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 androidx.slice.compat;

import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;

import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.BidiFormatter;
import androidx.slice.core.R;

/**
 * Dialog that grants slice permissions for an app.
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY)
@RequiresApi(19)
public class SlicePermissionActivity extends AppCompatActivity implements OnClickListener,
        OnDismissListener {

    private static final float MAX_LABEL_SIZE_PX = 500f;

    private static final String TAG = "SlicePermissionActivity";

    private Uri mUri;
    private String mCallingPkg;
    private String mProviderPkg;
    private AlertDialog mDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mUri = getIntent().getParcelableExtra(SliceProviderCompat.EXTRA_BIND_URI);
        mCallingPkg = getIntent().getStringExtra(SliceProviderCompat.EXTRA_PKG);
        mProviderPkg = getIntent().getStringExtra(SliceProviderCompat.EXTRA_PROVIDER_PKG);

        try {
            PackageManager pm = getPackageManager();
            CharSequence app1 = BidiFormatter.getInstance().unicodeWrap(
                    loadSafeLabel(pm, pm.getApplicationInfo(mCallingPkg, 0))
                    .toString());
            CharSequence app2 = BidiFormatter.getInstance().unicodeWrap(
                    loadSafeLabel(pm, pm.getApplicationInfo(mProviderPkg, 0))
                    .toString());
            mDialog = new AlertDialog.Builder(this)
                    .setTitle(getString(R.string.abc_slice_permission_title, app1, app2))
                    .setView(R.layout.abc_slice_permission_request)
                    .setNegativeButton(R.string.abc_slice_permission_deny, this)
                    .setPositiveButton(R.string.abc_slice_permission_allow, this)
                    .setOnDismissListener(this)
                    .show();
            TextView t1 = mDialog.getWindow().getDecorView().findViewById(R.id.text1);
            t1.setText(getString(R.string.abc_slice_permission_text_1, app2));
            TextView t2 = mDialog.getWindow().getDecorView().findViewById(R.id.text2);
            t2.setText(getString(R.string.abc_slice_permission_text_2, app2));
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Couldn't find package", e);
            finish();
        }
    }

    // Based on loadSafeLabel in PackageitemInfo
    private CharSequence loadSafeLabel(PackageManager pm, ApplicationInfo appInfo) {
        // loadLabel() always returns non-null
        String label = appInfo.loadLabel(pm).toString();
        // strip HTML tags to avoid <br> and other tags overwriting original message
        String labelStr = Html.fromHtml(label).toString();

        // If the label contains new line characters it may push the UI
        // down to hide a part of it. Labels shouldn't have new line
        // characters, so just truncate at the first time one is seen.
        final int labelLength = labelStr.length();
        int offset = 0;
        while (offset < labelLength) {
            final int codePoint = labelStr.codePointAt(offset);
            final int type = Character.getType(codePoint);
            if (type == Character.LINE_SEPARATOR
                    || type == Character.CONTROL
                    || type == Character.PARAGRAPH_SEPARATOR) {
                labelStr = labelStr.substring(0, offset);
                break;
            }
            // replace all non-break space to " " in order to be trimmed
            if (type == Character.SPACE_SEPARATOR) {
                labelStr = labelStr.substring(0, offset) + " " + labelStr.substring(offset
                        + Character.charCount(codePoint));
            }
            offset += Character.charCount(codePoint);
        }

        labelStr = labelStr.trim();
        if (labelStr.isEmpty()) {
            return appInfo.packageName;
        }
        TextPaint paint = new TextPaint();
        paint.setTextSize(42);

        return TextUtils.ellipsize(labelStr, paint, MAX_LABEL_SIZE_PX, TextUtils.TruncateAt.END);
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == DialogInterface.BUTTON_POSITIVE) {
            SliceProviderCompat.grantSlicePermission(this, getPackageName(), mCallingPkg,
                    mUri.buildUpon().path("").build());
        }
        finish();
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        finish();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mDialog != null && mDialog.isShowing()) {
            mDialog.cancel();
        }
    }
}