Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58a2b7b9 authored by Shijian Li's avatar Shijian Li Committed by Android (Google) Code Review
Browse files

Merge "Add wear customization for permission review activity."

parents cd5a5a60 7db8592a
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->

<TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+android:id/title"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/wear_permission_review_pref_padding"
        android:paddingBottom="@dimen/wear_permission_review_pref_padding"
        android:textAppearance="@android:style/TextAppearance.Material.Title" />
+38 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="@dimen/wear_permission_review_pref_padding"
        android:orientation="vertical">
        <ImageView android:id="@+android:id/icon"
                   android:adjustViewBounds="true"
                   android:maxHeight="@dimen/wear_permission_review_icon_size"
                   android:maxWidth="@dimen/wear_permission_review_icon_size"
                   android:layout_gravity="center_horizontal"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:scaleType="centerInside" />
        <TextView android:id="@+android:id/title"
                  android:gravity="center"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:paddingLeft="@dimen/diag_preferred_padding"
                  android:paddingRight="@dimen/diag_preferred_padding"
                  android:textAppearance="@android:style/TextAppearance.Material.Title" />
</LinearLayout>
+2 −0
Original line number Diff line number Diff line
@@ -39,4 +39,6 @@

    <dimen name="headerElevation">8dp</dimen>

    <dimen name="wear_permission_review_pref_padding">8dp</dimen>
    <dimen name="wear_permission_review_icon_size">24dp</dimen>
</resources>
+12 −4
Original line number Diff line number Diff line
@@ -44,11 +44,13 @@ import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.utils.Utils;
import com.android.packageinstaller.permission.ui.ConfirmActionDialogFragment.OnActionConfirmedListener;
import com.android.packageinstaller.permission.ui.wear.ReviewPermissionsWearFragment;

import java.util.List;

@@ -65,12 +67,18 @@ public final class ReviewPermissionsActivity extends Activity
            return;
        }

        if (DeviceUtils.isWear(this)) {
            Fragment fragment = ReviewPermissionsWearFragment.newInstance(packageInfo);
            getFragmentManager().beginTransaction()
                    .replace(android.R.id.content, fragment).commit();
        } else {
            setContentView(R.layout.review_permissions);
            if (getFragmentManager().findFragmentById(R.id.preferences_frame) == null) {
                getFragmentManager().beginTransaction().add(R.id.preferences_frame,
                        ReviewPermissionsFragment.newInstance(packageInfo)).commit();
            }
        }
    }

    @Override
    public void onActionConfirmed(String action) {
+363 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.packageinstaller.permission.ui.wear;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.preference.TwoStatePreference;
import android.support.wearable.view.WearableDialogHelper;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.util.TypedValue;
import com.android.packageinstaller.R;
import com.android.packageinstaller.permission.model.AppPermissionGroup;
import com.android.packageinstaller.permission.model.AppPermissions;
import com.android.packageinstaller.permission.utils.Utils;

import java.util.List;

public class ReviewPermissionsWearFragment extends PreferenceFragment
        implements Preference.OnPreferenceChangeListener {
    private static final String TAG = "ReviewPermWear";

    private static final int ORDER_TITLE = 0;
    private static final int ORDER_NEW_PERMS = 1;
    private static final int ORDER_CURRENT_PERMS = 2;
    // Category for showing actions should be displayed last.
    private static final int ORDER_ACTION = 100000;
    private static final int ORDER_PERM_OFFSET_START = 100;

    private static final String EXTRA_PACKAGE_INFO =
        "com.android.packageinstaller.permission.ui.extra.PACKAGE_INFO";

    public static ReviewPermissionsWearFragment newInstance(PackageInfo packageInfo) {
        Bundle arguments = new Bundle();
        arguments.putParcelable(EXTRA_PACKAGE_INFO, packageInfo);
        ReviewPermissionsWearFragment instance = new ReviewPermissionsWearFragment();
        instance.setArguments(arguments);
        instance.setRetainInstance(true);
        return instance;
    }

    private AppPermissions mAppPermissions;

    private PreferenceCategory mNewPermissionsCategory;

    private boolean mHasConfirmedRevoke;

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

        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        PackageInfo packageInfo = getArguments().getParcelable(EXTRA_PACKAGE_INFO);
        if (packageInfo == null) {
            activity.finish();
            return;
        }

        mAppPermissions = new AppPermissions(activity, packageInfo, null, false,
                new Runnable() {
                    @Override
                    public void run() {
                        getActivity().finish();
                    }
                });

        if (mAppPermissions.getPermissionGroups().isEmpty()) {
            activity.finish();
            return;
        }

        boolean reviewRequired = false;
        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
            if (group.isReviewRequired()) {
                reviewRequired = true;
                break;
            }
        }

        if (!reviewRequired) {
            activity.finish();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mAppPermissions.refresh();
        loadPreferences();
    }

    private void loadPreferences() {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        PreferenceScreen screen = getPreferenceScreen();
        if (screen == null) {
            screen = getPreferenceManager().createPreferenceScreen(getActivity());
            setPreferenceScreen(screen);
        } else {
            screen.removeAll();
        }

        PreferenceGroup currentPermissionsCategory = null;
        PreferenceGroup oldNewPermissionsCategory = mNewPermissionsCategory;
        mNewPermissionsCategory = null;

        final boolean isPackageUpdated = isPackageUpdated();
        int permOrder = ORDER_PERM_OFFSET_START;

        for (AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
            if (!Utils.shouldShowPermission(group, mAppPermissions.getPackageInfo().packageName)
                    || !Utils.OS_PKG.equals(group.getDeclaringPackage())) {
                continue;
            }

            final SwitchPreference preference;
            Preference cachedPreference = oldNewPermissionsCategory != null
                    ? oldNewPermissionsCategory.findPreference(group.getName()) : null;
            if (cachedPreference instanceof SwitchPreference) {
                preference = (SwitchPreference) cachedPreference;
            } else {
                preference = new SwitchPreference(getActivity());

                preference.setKey(group.getName());
                preference.setTitle(group.getLabel());
                preference.setPersistent(false);
                preference.setOrder(permOrder++);

                preference.setOnPreferenceChangeListener(this);
            }

            preference.setChecked(group.areRuntimePermissionsGranted());

            // Mutable state
            if (group.isPolicyFixed()) {
                preference.setEnabled(false);
            } else {
                preference.setEnabled(true);
            }

            if (group.isReviewRequired()) {
                if (!isPackageUpdated) {
                    // An app just being installed, which means all groups requiring reviews.
                    screen.addPreference(preference);
                } else {
                    if (mNewPermissionsCategory == null) {
                        mNewPermissionsCategory = new PreferenceCategory(activity);
                        mNewPermissionsCategory.setTitle(R.string.new_permissions_category);
                        mNewPermissionsCategory.setOrder(ORDER_NEW_PERMS);
                        screen.addPreference(mNewPermissionsCategory);
                    }
                    mNewPermissionsCategory.addPreference(preference);
                }
            } else {
                if (currentPermissionsCategory == null) {
                    currentPermissionsCategory = new PreferenceCategory(activity);
                    currentPermissionsCategory.setTitle(R.string.current_permissions_category);
                    currentPermissionsCategory.setOrder(ORDER_CURRENT_PERMS);
                    screen.addPreference(currentPermissionsCategory);
                }
                currentPermissionsCategory.addPreference(preference);
            }
        }

        addTitlePreferenceToScreen(screen);
        addActionPreferencesToScreen(screen);
    }

    private boolean isPackageUpdated() {
        List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
        final int groupCount = groups.size();
        for (int i = 0; i < groupCount; i++) {
            AppPermissionGroup group = groups.get(i);
            if (!group.isReviewRequired()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
      Log.d(TAG, "onPreferenceChange " + preference.getTitle());
        if (mHasConfirmedRevoke) {
            return true;
        }
        if (preference instanceof SwitchPreference) {
            SwitchPreference switchPreference = (SwitchPreference) preference;
            if (switchPreference.isChecked()) {
                showWarnRevokeDialog(switchPreference);
            } else {
                return true;
            }
        }
        return false;
    }

    private void showWarnRevokeDialog(final SwitchPreference preference) {
        // When revoking, we set "confirm" as the negative icon to be shown at the bottom.
        new WearableDialogHelper.DialogBuilder(getContext())
                .setPositiveIcon(R.drawable.cancel_button)
                .setNegativeIcon(R.drawable.confirm_button)
                .setPositiveButton(R.string.cancel, null)
                .setNegativeButton(R.string.grant_dialog_button_deny_anyway,
                        (dialog, which) -> {
                            preference.setChecked(false);
                            mHasConfirmedRevoke = true;
                        })
                .setMessage(R.string.old_sdk_deny_warning)
                .show();
    }

    private void confirmPermissionsReview() {
        PreferenceGroup preferenceGroup = mNewPermissionsCategory != null
                ? mNewPermissionsCategory : getPreferenceScreen();

        final int preferenceCount = preferenceGroup.getPreferenceCount();
        for (int i = 0; i < preferenceCount; i++) {
            Preference preference = preferenceGroup.getPreference(i);
            if (preference instanceof TwoStatePreference) {
                TwoStatePreference twoStatePreference = (TwoStatePreference) preference;
                String groupName = preference.getKey();
                AppPermissionGroup group = mAppPermissions.getPermissionGroup(groupName);
                if (twoStatePreference.isChecked()) {
                    group.grantRuntimePermissions(false);
                } else {
                    group.revokeRuntimePermissions(false);
                }
                group.resetReviewRequired();
            }
        }
    }

    private void addTitlePreferenceToScreen(PreferenceScreen screen) {
        Activity activity = getActivity();
        Preference titlePref = new Preference(activity);
        screen.addPreference(titlePref);

        // Set icon
        Drawable icon = mAppPermissions.getPackageInfo().applicationInfo.loadIcon(
              activity.getPackageManager());
        titlePref.setIcon(icon);

        // Set message
        String appLabel = mAppPermissions.getAppLabel().toString();
        final int labelTemplateResId = isPackageUpdated()
                ?  R.string.permission_review_title_template_update
                :  R.string.permission_review_title_template_install;
        SpannableString message = new SpannableString(getString(labelTemplateResId, appLabel));

        // Color the app name.
        final int appLabelStart = message.toString().indexOf(appLabel, 0);
        final int appLabelLength = appLabel.length();

        TypedValue typedValue = new TypedValue();
        activity.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
        final int color = activity.getColor(typedValue.resourceId);

        message.setSpan(new ForegroundColorSpan(color), appLabelStart,
                appLabelStart + appLabelLength, 0);

        titlePref.setTitle(message);

        titlePref.setSelectable(false);
        titlePref.setLayoutResource(R.layout.wear_review_permission_title_pref);
    }

    private void addActionPreferencesToScreen(PreferenceScreen screen) {
        final Activity activity = getActivity();

        Preference cancelPref = new Preference(activity);
        cancelPref.setTitle(R.string.review_button_cancel);
        cancelPref.setOrder(ORDER_ACTION);
        cancelPref.setEnabled(true);
        cancelPref.setLayoutResource(R.layout.wear_review_permission_action_pref);
        cancelPref.setOnPreferenceClickListener(p -> {
            executeCallback(false);
            activity.setResult(Activity.RESULT_CANCELED);
            activity.finish();
            return true;
        });
        screen.addPreference(cancelPref);

        Preference continuePref = new Preference(activity);
        continuePref.setTitle(R.string.review_button_continue);
        continuePref.setOrder(ORDER_ACTION + 1);
        continuePref.setEnabled(true);
        continuePref.setLayoutResource(R.layout.wear_review_permission_action_pref);
        continuePref.setOnPreferenceClickListener(p -> {
            confirmPermissionsReview();
            executeCallback(true);
            getActivity().finish();
            return true;
        });
        screen.addPreference(continuePref);
    }

    private void executeCallback(boolean success) {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }
        if (success) {
            IntentSender intent = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
            if (intent != null) {
                try {
                    int flagMask = 0;
                    int flagValues = 0;
                    if (activity.getIntent().getBooleanExtra(
                            Intent.EXTRA_RESULT_NEEDED, false)) {
                        flagMask = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
                        flagValues = Intent.FLAG_ACTIVITY_FORWARD_RESULT;
                    }
                    activity.startIntentSenderForResult(intent, -1, null,
                            flagMask, flagValues, 0);
                } catch (IntentSender.SendIntentException e) {
                    /* ignore */
                }
                return;
            }
        }
        RemoteCallback callback = activity.getIntent().getParcelableExtra(
                Intent.EXTRA_REMOTE_CALLBACK);
        if (callback != null) {
            Bundle result = new Bundle();
            result.putBoolean(Intent.EXTRA_RETURN_RESULT, success);
            callback.sendResult(result);
        }
    }
}