Home | History | Annotate | Download | only in handheld
      1 /*
      2  * Copyright (C) 2015 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.packageinstaller.permission.ui.handheld;
     18 
     19 import android.app.Activity;
     20 import android.content.Intent;
     21 import android.graphics.drawable.Icon;
     22 import android.os.Bundle;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.view.View.OnClickListener;
     26 import android.view.ViewGroup;
     27 import android.view.WindowManager.LayoutParams;
     28 import android.view.accessibility.AccessibilityNodeInfo;
     29 import android.view.animation.AnimationUtils;
     30 import android.view.animation.Interpolator;
     31 import android.widget.Button;
     32 import android.widget.CheckBox;
     33 import android.widget.ImageView;
     34 import android.widget.TextView;
     35 
     36 import com.android.packageinstaller.R;
     37 import com.android.packageinstaller.permission.ui.ButtonBarLayout;
     38 import com.android.packageinstaller.permission.ui.GrantPermissionsViewHandler;
     39 import com.android.packageinstaller.permission.ui.ManagePermissionsActivity;
     40 import com.android.packageinstaller.permission.ui.ManualLayoutFrame;
     41 
     42 public class GrantPermissionsViewHandlerImpl implements GrantPermissionsViewHandler,
     43         OnClickListener {
     44 
     45     public static final String ARG_GROUP_NAME = "ARG_GROUP_NAME";
     46     public static final String ARG_GROUP_COUNT = "ARG_GROUP_COUNT";
     47     public static final String ARG_GROUP_INDEX = "ARG_GROUP_INDEX";
     48     public static final String ARG_GROUP_ICON = "ARG_GROUP_ICON";
     49     public static final String ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE";
     50     public static final String ARG_GROUP_SHOW_DO_NOT_ASK = "ARG_GROUP_SHOW_DO_NOT_ASK";
     51     public static final String ARG_GROUP_DO_NOT_ASK_CHECKED = "ARG_GROUP_DO_NOT_ASK_CHECKED";
     52 
     53     // Animation parameters.
     54     private static final long OUT_DURATION = 200;
     55     private static final long IN_DURATION = 300;
     56 
     57     private final Activity mActivity;
     58     private final String mAppPackageName;
     59     private final boolean mPermissionReviewRequired;
     60 
     61     private ResultListener mResultListener;
     62 
     63     private String mGroupName;
     64     private int mGroupCount;
     65     private int mGroupIndex;
     66     private Icon mGroupIcon;
     67     private CharSequence mGroupMessage;
     68     private boolean mShowDonNotAsk;
     69     private boolean mDoNotAskChecked;
     70 
     71     private ImageView mIconView;
     72     private TextView mCurrentGroupView;
     73     private TextView mMessageView;
     74     private CheckBox mDoNotAskCheckbox;
     75     private Button mAllowButton;
     76     private Button mMoreInfoButton;
     77 
     78     private ManualLayoutFrame mRootView;
     79 
     80     // Needed for animation
     81     private ViewGroup mDescContainer;
     82     private ViewGroup mCurrentDesc;
     83     private ViewGroup mDialogContainer;
     84     private ButtonBarLayout mButtonBar;
     85 
     86     public GrantPermissionsViewHandlerImpl(Activity activity, String appPackageName) {
     87         mActivity = activity;
     88         mAppPackageName = appPackageName;
     89         mPermissionReviewRequired = activity.getPackageManager().isPermissionReviewModeEnabled();
     90     }
     91 
     92     @Override
     93     public GrantPermissionsViewHandlerImpl setResultListener(ResultListener listener) {
     94         mResultListener = listener;
     95         return this;
     96     }
     97 
     98     @Override
     99     public void saveInstanceState(Bundle arguments) {
    100         arguments.putString(ARG_GROUP_NAME, mGroupName);
    101         arguments.putInt(ARG_GROUP_COUNT, mGroupCount);
    102         arguments.putInt(ARG_GROUP_INDEX, mGroupIndex);
    103         arguments.putParcelable(ARG_GROUP_ICON, mGroupIcon);
    104         arguments.putCharSequence(ARG_GROUP_MESSAGE, mGroupMessage);
    105         arguments.putBoolean(ARG_GROUP_SHOW_DO_NOT_ASK, mShowDonNotAsk);
    106         arguments.putBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED, mDoNotAskCheckbox.isChecked());
    107     }
    108 
    109     @Override
    110     public void loadInstanceState(Bundle savedInstanceState) {
    111         mGroupName = savedInstanceState.getString(ARG_GROUP_NAME);
    112         mGroupMessage = savedInstanceState.getCharSequence(ARG_GROUP_MESSAGE);
    113         mGroupIcon = savedInstanceState.getParcelable(ARG_GROUP_ICON);
    114         mGroupCount = savedInstanceState.getInt(ARG_GROUP_COUNT);
    115         mGroupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX);
    116         mShowDonNotAsk = savedInstanceState.getBoolean(ARG_GROUP_SHOW_DO_NOT_ASK);
    117         mDoNotAskChecked = savedInstanceState.getBoolean(ARG_GROUP_DO_NOT_ASK_CHECKED);
    118     }
    119 
    120     @Override
    121     public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
    122             CharSequence message, boolean showDonNotAsk) {
    123         mGroupName = groupName;
    124         mGroupCount = groupCount;
    125         mGroupIndex = groupIndex;
    126         mGroupIcon = icon;
    127         mGroupMessage = message;
    128         mShowDonNotAsk = showDonNotAsk;
    129         mDoNotAskChecked = false;
    130         // If this is a second (or later) permission and the views exist, then animate.
    131         if (mIconView != null) {
    132             if (mGroupIndex > 0) {
    133                 animateToPermission();
    134             } else {
    135                 updateDescription();
    136                 updateGroup();
    137                 updateDoNotAskCheckBox();
    138             }
    139         }
    140     }
    141 
    142     public void onConfigurationChanged() {
    143         mRootView.onConfigurationChanged();
    144     }
    145 
    146     private void animateOldContent(Runnable callback) {
    147         // Fade out old description group and scale out the icon for it.
    148         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    149                 android.R.interpolator.fast_out_linear_in);
    150 
    151         // Icon scale to zero
    152         mIconView.animate()
    153                 .scaleX(0)
    154                 .scaleY(0)
    155                 .setDuration(OUT_DURATION)
    156                 .setInterpolator(interpolator)
    157                 .start();
    158 
    159         // Description fade out
    160         mCurrentDesc.animate()
    161                 .alpha(0)
    162                 .setDuration(OUT_DURATION)
    163                 .setInterpolator(interpolator)
    164                 .withEndAction(callback)
    165                 .start();
    166 
    167         // Checkbox fade out if needed
    168         if (!mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE) {
    169             mDoNotAskCheckbox.animate()
    170                     .alpha(0)
    171                     .setDuration(OUT_DURATION)
    172                     .setInterpolator(interpolator)
    173                     .start();
    174         }
    175     }
    176 
    177     private void attachNewContent(final Runnable callback) {
    178         mCurrentDesc = (ViewGroup) LayoutInflater.from(mActivity).inflate(
    179                 R.layout.permission_description, mDescContainer, false);
    180         mDescContainer.removeAllViews();
    181         mDescContainer.addView(mCurrentDesc);
    182 
    183         mDialogContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    184                 @Override
    185                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
    186                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
    187                     mDialogContainer.removeOnLayoutChangeListener(this);
    188 
    189                     // Prepare new content to the right to be moved in
    190                     final int containerWidth = mDescContainer.getWidth();
    191                     mCurrentDesc.setTranslationX(containerWidth);
    192 
    193                     // How much scale for the dialog to appear the same?
    194                     final int oldDynamicHeight = oldBottom - oldTop - mButtonBar.getHeight();
    195                     final float scaleY = (float) oldDynamicHeight / mDescContainer.getHeight();
    196 
    197                     // How much to translate for the dialog to appear the same?
    198                     final int translationCompensatingScale = (int) (scaleY
    199                             * mDescContainer.getHeight() - mDescContainer.getHeight()) / 2;
    200                     final int translationY = (oldTop - top) + translationCompensatingScale;
    201 
    202                     // Animate to the current layout
    203                     mDescContainer.setScaleY(scaleY);
    204                     mDescContainer.setTranslationY(translationY);
    205                     mDescContainer.animate()
    206                             .translationY(0)
    207                             .scaleY(1.0f)
    208                             .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
    209                                     android.R.interpolator.linear_out_slow_in))
    210                             .setDuration(IN_DURATION)
    211                             .withEndAction(callback)
    212                             .start();
    213                 }
    214             }
    215         );
    216 
    217         mMessageView = (TextView) mCurrentDesc.findViewById(R.id.permission_message);
    218         mIconView = (ImageView) mCurrentDesc.findViewById(R.id.permission_icon);
    219 
    220         final boolean doNotAskWasShown = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
    221 
    222         updateDescription();
    223         updateGroup();
    224         updateDoNotAskCheckBox();
    225 
    226         if (!doNotAskWasShown && mShowDonNotAsk) {
    227             mDoNotAskCheckbox.setAlpha(0);
    228         }
    229     }
    230 
    231     private void animateNewContent() {
    232         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    233                 android.R.interpolator.linear_out_slow_in);
    234 
    235         // Description slide in
    236         mCurrentDesc.animate()
    237                 .translationX(0)
    238                 .setDuration(IN_DURATION)
    239                 .setInterpolator(interpolator)
    240                 .start();
    241 
    242         // Checkbox fade in if needed
    243         if (mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE
    244                 && mDoNotAskCheckbox.getAlpha() < 1.0f) {
    245             mDoNotAskCheckbox.setAlpha(0);
    246             mDoNotAskCheckbox.animate()
    247                     .alpha(1.0f)
    248                     .setDuration(IN_DURATION)
    249                     .setInterpolator(interpolator)
    250                     .start();
    251         }
    252     }
    253 
    254     private void animateToPermission() {
    255         // Remove the old content
    256         animateOldContent(new Runnable() {
    257             @Override
    258             public void run() {
    259                 // Add the new content
    260                 attachNewContent(new Runnable() {
    261                     @Override
    262                     public void run() {
    263                         // Animate the new content
    264                         animateNewContent();
    265                     }
    266                 });
    267             }
    268         });
    269     }
    270 
    271     @Override
    272     public View createView() {
    273         mRootView = (ManualLayoutFrame) LayoutInflater.from(mActivity)
    274                 .inflate(R.layout.grant_permissions, null);
    275         mButtonBar = (ButtonBarLayout) mRootView.findViewById(R.id.button_group);
    276         mButtonBar.setAllowStacking(true);
    277         mMessageView = (TextView) mRootView.findViewById(R.id.permission_message);
    278         mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon);
    279         mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text);
    280         mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox);
    281 
    282         mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button);
    283         mAllowButton.setOnClickListener(this);
    284 
    285         if (mPermissionReviewRequired) {
    286             mMoreInfoButton = (Button) mRootView.findViewById(R.id.permission_more_info_button);
    287             mMoreInfoButton.setVisibility(View.VISIBLE);
    288             mMoreInfoButton.setOnClickListener(this);
    289         }
    290 
    291         mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
    292         mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container);
    293         mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root);
    294 
    295         mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this);
    296         mDoNotAskCheckbox.setOnClickListener(this);
    297 
    298         if (mGroupName != null) {
    299             updateDescription();
    300             updateGroup();
    301             updateDoNotAskCheckBox();
    302         }
    303 
    304         return mRootView;
    305     }
    306 
    307     @Override
    308     public void updateWindowAttributes(LayoutParams outLayoutParams) {
    309         // No-op
    310     }
    311 
    312     private void updateDescription() {
    313         mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity));
    314         mMessageView.setText(mGroupMessage);
    315     }
    316 
    317     private void updateGroup() {
    318         if (mGroupCount > 1) {
    319             mCurrentGroupView.setVisibility(View.VISIBLE);
    320             mCurrentGroupView.setText(mActivity.getString(R.string.current_permission_template,
    321                     mGroupIndex + 1, mGroupCount));
    322         } else {
    323             mCurrentGroupView.setVisibility(View.GONE);
    324         }
    325     }
    326 
    327     private void updateDoNotAskCheckBox() {
    328         if (mShowDonNotAsk) {
    329             mDoNotAskCheckbox.setVisibility(View.VISIBLE);
    330             mDoNotAskCheckbox.setOnClickListener(this);
    331             mDoNotAskCheckbox.setChecked(mDoNotAskChecked);
    332         } else {
    333             mDoNotAskCheckbox.setVisibility(View.GONE);
    334             mDoNotAskCheckbox.setOnClickListener(null);
    335         }
    336     }
    337 
    338     @Override
    339     public void onClick(View view) {
    340         switch (view.getId()) {
    341             case R.id.permission_allow_button:
    342                 if (mResultListener != null) {
    343                     view.performAccessibilityAction(
    344                             AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
    345                     mResultListener.onPermissionGrantResult(mGroupName, true, false);
    346                 }
    347                 break;
    348             case R.id.permission_deny_button:
    349                 mAllowButton.setEnabled(true);
    350                 if (mResultListener != null) {
    351                     view.performAccessibilityAction(
    352                             AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
    353                     mResultListener.onPermissionGrantResult(mGroupName, false,
    354                             mShowDonNotAsk && mDoNotAskCheckbox.isChecked());
    355                 }
    356                 break;
    357             case R.id.permission_more_info_button:
    358                 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
    359                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPackageName);
    360                 intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
    361                 mActivity.startActivity(intent);
    362                 break;
    363             case R.id.do_not_ask_checkbox:
    364                 mAllowButton.setEnabled(!mDoNotAskCheckbox.isChecked());
    365                 break;
    366         }
    367     }
    368 
    369     @Override
    370     public void onBackPressed() {
    371         if (mResultListener != null) {
    372             final boolean doNotAskAgain = mDoNotAskCheckbox.isChecked();
    373             mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain);
    374         }
    375     }
    376 }
    377