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         updateDoNotAskCheckBox();
    120     }
    121 
    122     @Override
    123     public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
    124             CharSequence message, boolean showDonNotAsk) {
    125         mGroupName = groupName;
    126         mGroupCount = groupCount;
    127         mGroupIndex = groupIndex;
    128         mGroupIcon = icon;
    129         mGroupMessage = message;
    130         mShowDonNotAsk = showDonNotAsk;
    131         mDoNotAskChecked = false;
    132         // If this is a second (or later) permission and the views exist, then animate.
    133         if (mIconView != null) {
    134             if (mGroupIndex > 0) {
    135                 animateToPermission();
    136             } else {
    137                 updateDescription();
    138                 updateGroup();
    139                 updateDoNotAskCheckBox();
    140             }
    141         }
    142     }
    143 
    144     public void onConfigurationChanged() {
    145         mRootView.onConfigurationChanged();
    146     }
    147 
    148     private void animateOldContent(Runnable callback) {
    149         // Fade out old description group and scale out the icon for it.
    150         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    151                 android.R.interpolator.fast_out_linear_in);
    152 
    153         // Icon scale to zero
    154         mIconView.animate()
    155                 .scaleX(0)
    156                 .scaleY(0)
    157                 .setDuration(OUT_DURATION)
    158                 .setInterpolator(interpolator)
    159                 .start();
    160 
    161         // Description fade out
    162         mCurrentDesc.animate()
    163                 .alpha(0)
    164                 .setDuration(OUT_DURATION)
    165                 .setInterpolator(interpolator)
    166                 .withEndAction(callback)
    167                 .start();
    168 
    169         // Checkbox fade out if needed
    170         if (!mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE) {
    171             mDoNotAskCheckbox.animate()
    172                     .alpha(0)
    173                     .setDuration(OUT_DURATION)
    174                     .setInterpolator(interpolator)
    175                     .start();
    176         }
    177     }
    178 
    179     private void attachNewContent(final Runnable callback) {
    180         mCurrentDesc = (ViewGroup) LayoutInflater.from(mActivity).inflate(
    181                 R.layout.permission_description, mDescContainer, false);
    182         mDescContainer.removeAllViews();
    183         mDescContainer.addView(mCurrentDesc);
    184 
    185         mDialogContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    186                 @Override
    187                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
    188                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
    189                     mDialogContainer.removeOnLayoutChangeListener(this);
    190 
    191                     // Prepare new content to the right to be moved in
    192                     final int containerWidth = mDescContainer.getWidth();
    193                     mCurrentDesc.setTranslationX(containerWidth);
    194 
    195                     // How much scale for the dialog to appear the same?
    196                     final int oldDynamicHeight = oldBottom - oldTop - mButtonBar.getHeight();
    197                     final float scaleY = (float) oldDynamicHeight / mDescContainer.getHeight();
    198 
    199                     // How much to translate for the dialog to appear the same?
    200                     final int translationCompensatingScale = (int) (scaleY
    201                             * mDescContainer.getHeight() - mDescContainer.getHeight()) / 2;
    202                     final int translationY = (oldTop - top) + translationCompensatingScale;
    203 
    204                     // Animate to the current layout
    205                     mDescContainer.setScaleY(scaleY);
    206                     mDescContainer.setTranslationY(translationY);
    207                     mDescContainer.animate()
    208                             .translationY(0)
    209                             .scaleY(1.0f)
    210                             .setInterpolator(AnimationUtils.loadInterpolator(mActivity,
    211                                     android.R.interpolator.linear_out_slow_in))
    212                             .setDuration(IN_DURATION)
    213                             .withEndAction(callback)
    214                             .start();
    215                 }
    216             }
    217         );
    218 
    219         mMessageView = (TextView) mCurrentDesc.findViewById(R.id.permission_message);
    220         mIconView = (ImageView) mCurrentDesc.findViewById(R.id.permission_icon);
    221 
    222         final boolean doNotAskWasShown = mDoNotAskCheckbox.getVisibility() == View.VISIBLE;
    223 
    224         updateDescription();
    225         updateGroup();
    226         updateDoNotAskCheckBox();
    227 
    228         if (!doNotAskWasShown && mShowDonNotAsk) {
    229             mDoNotAskCheckbox.setAlpha(0);
    230         }
    231     }
    232 
    233     private void animateNewContent() {
    234         Interpolator interpolator = AnimationUtils.loadInterpolator(mActivity,
    235                 android.R.interpolator.linear_out_slow_in);
    236 
    237         // Description slide in
    238         mCurrentDesc.animate()
    239                 .translationX(0)
    240                 .setDuration(IN_DURATION)
    241                 .setInterpolator(interpolator)
    242                 .start();
    243 
    244         // Checkbox fade in if needed
    245         if (mShowDonNotAsk && mDoNotAskCheckbox.getVisibility() == View.VISIBLE
    246                 && mDoNotAskCheckbox.getAlpha() < 1.0f) {
    247             mDoNotAskCheckbox.setAlpha(0);
    248             mDoNotAskCheckbox.animate()
    249                     .alpha(1.0f)
    250                     .setDuration(IN_DURATION)
    251                     .setInterpolator(interpolator)
    252                     .start();
    253         }
    254     }
    255 
    256     private void animateToPermission() {
    257         // Remove the old content
    258         animateOldContent(new Runnable() {
    259             @Override
    260             public void run() {
    261                 // Add the new content
    262                 attachNewContent(new Runnable() {
    263                     @Override
    264                     public void run() {
    265                         // Animate the new content
    266                         animateNewContent();
    267                     }
    268                 });
    269             }
    270         });
    271     }
    272 
    273     @Override
    274     public View createView() {
    275         mRootView = (ManualLayoutFrame) LayoutInflater.from(mActivity)
    276                 .inflate(R.layout.grant_permissions, null);
    277         mButtonBar = (ButtonBarLayout) mRootView.findViewById(R.id.button_group);
    278         mButtonBar.setAllowStacking(true);
    279         mMessageView = (TextView) mRootView.findViewById(R.id.permission_message);
    280         mIconView = (ImageView) mRootView.findViewById(R.id.permission_icon);
    281         mCurrentGroupView = (TextView) mRootView.findViewById(R.id.current_page_text);
    282         mDoNotAskCheckbox = (CheckBox) mRootView.findViewById(R.id.do_not_ask_checkbox);
    283 
    284         mAllowButton = (Button) mRootView.findViewById(R.id.permission_allow_button);
    285         mAllowButton.setOnClickListener(this);
    286 
    287         if (mPermissionReviewRequired) {
    288             mMoreInfoButton = (Button) mRootView.findViewById(R.id.permission_more_info_button);
    289             mMoreInfoButton.setVisibility(View.VISIBLE);
    290             mMoreInfoButton.setOnClickListener(this);
    291         }
    292 
    293         mDialogContainer = (ViewGroup) mRootView.findViewById(R.id.dialog_container);
    294         mDescContainer = (ViewGroup) mRootView.findViewById(R.id.desc_container);
    295         mCurrentDesc = (ViewGroup) mRootView.findViewById(R.id.perm_desc_root);
    296 
    297         mRootView.findViewById(R.id.permission_deny_button).setOnClickListener(this);
    298         mDoNotAskCheckbox.setOnClickListener(this);
    299 
    300         if (mGroupName != null) {
    301             updateDescription();
    302             updateGroup();
    303             updateDoNotAskCheckBox();
    304         }
    305 
    306         return mRootView;
    307     }
    308 
    309     @Override
    310     public void updateWindowAttributes(LayoutParams outLayoutParams) {
    311         // No-op
    312     }
    313 
    314     private void updateDescription() {
    315         if (mGroupIcon != null) {
    316             mIconView.setImageDrawable(mGroupIcon.loadDrawable(mActivity));
    317         }
    318         mMessageView.setText(mGroupMessage);
    319     }
    320 
    321     private void updateGroup() {
    322         if (mGroupCount > 1) {
    323             mCurrentGroupView.setVisibility(View.VISIBLE);
    324             mCurrentGroupView.setText(mActivity.getString(R.string.current_permission_template,
    325                     mGroupIndex + 1, mGroupCount));
    326         } else {
    327             mCurrentGroupView.setVisibility(View.GONE);
    328         }
    329     }
    330 
    331     private void updateDoNotAskCheckBox() {
    332         if (mShowDonNotAsk) {
    333             mDoNotAskCheckbox.setVisibility(View.VISIBLE);
    334             mDoNotAskCheckbox.setOnClickListener(this);
    335             mDoNotAskCheckbox.setChecked(mDoNotAskChecked);
    336             mAllowButton.setEnabled(!mDoNotAskChecked);
    337         } else {
    338             mDoNotAskCheckbox.setVisibility(View.GONE);
    339             mDoNotAskCheckbox.setOnClickListener(null);
    340             mAllowButton.setEnabled(true);
    341         }
    342     }
    343 
    344     @Override
    345     public void onClick(View view) {
    346         switch (view.getId()) {
    347             case R.id.permission_allow_button:
    348                 if (mResultListener != null) {
    349                     view.performAccessibilityAction(
    350                             AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
    351                     mResultListener.onPermissionGrantResult(mGroupName, true, false);
    352                 }
    353                 break;
    354             case R.id.permission_deny_button:
    355                 mAllowButton.setEnabled(true);
    356                 if (mResultListener != null) {
    357                     view.performAccessibilityAction(
    358                             AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
    359                     mResultListener.onPermissionGrantResult(mGroupName, false,
    360                             mShowDonNotAsk && mDoNotAskCheckbox.isChecked());
    361                 }
    362                 break;
    363             case R.id.permission_more_info_button:
    364                 Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
    365                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mAppPackageName);
    366                 intent.putExtra(ManagePermissionsActivity.EXTRA_ALL_PERMISSIONS, true);
    367                 mActivity.startActivity(intent);
    368                 break;
    369             case R.id.do_not_ask_checkbox:
    370                 mAllowButton.setEnabled(!mDoNotAskCheckbox.isChecked());
    371                 break;
    372         }
    373     }
    374 
    375     @Override
    376     public void onBackPressed() {
    377         if (mResultListener != null) {
    378             final boolean doNotAskAgain = mDoNotAskCheckbox.isChecked();
    379             mResultListener.onPermissionGrantResult(mGroupName, false, doNotAskAgain);
    380         }
    381     }
    382 }
    383