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
17package com.android.contacts.common.activity;
18
19import com.android.contacts.common.R;
20import com.android.contacts.common.model.AccountTypeManager;
21
22import android.app.Activity;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.os.Bundle;
27import android.os.Trace;
28import android.widget.Toast;
29
30import java.util.ArrayList;
31import java.util.Arrays;
32
33/**
34 * Activity that asks the user for all {@link #getDesiredPermissions} if any of
35 * {@link #getRequiredPermissions} are missing.
36 *
37 * NOTE: As a result of b/22095159, this can behave oddly in the case where the final permission
38 * you are requesting causes an application restart.
39 */
40public abstract class RequestPermissionsActivityBase extends Activity {
41    public static final String PREVIOUS_ACTIVITY_INTENT = "previous_intent";
42    private static final int PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1;
43
44    /**
45     * @return list of permissions that are needed in order for {@link #PREVIOUS_ACTIVITY_INTENT} to
46     * operate. You only need to return a single permission per permission group you care about.
47     */
48    protected abstract String[] getRequiredPermissions();
49
50    /**
51     * @return list of permissions that would be useful for {@link #PREVIOUS_ACTIVITY_INTENT} to
52     * operate. You only need to return a single permission per permission group you care about.
53     */
54    protected abstract String[] getDesiredPermissions();
55
56    private Intent mPreviousActivityIntent;
57
58    @Override
59    protected void onCreate(Bundle savedInstanceState) {
60        super.onCreate(savedInstanceState);
61        mPreviousActivityIntent = (Intent) getIntent().getExtras().get(PREVIOUS_ACTIVITY_INTENT);
62
63        // Only start a requestPermissions() flow when first starting this activity the first time.
64        // The process is likely to be restarted during the permission flow (necessary to enable
65        // permissions) so this is important to track.
66        if (savedInstanceState == null) {
67            requestPermissions();
68        }
69    }
70
71    /**
72     * If any permissions the Contacts app needs are missing, open an Activity
73     * to prompt the user for these permissions. Moreover, finish the current activity.
74     *
75     * This is designed to be called inside {@link android.app.Activity#onCreate}
76     */
77    protected static boolean startPermissionActivity(Activity activity,
78            String[] requiredPermissions, Class<?> newActivityClass) {
79        if (!RequestPermissionsActivity.hasPermissions(activity, requiredPermissions)) {
80            final Intent intent = new Intent(activity,  newActivityClass);
81            intent.putExtra(PREVIOUS_ACTIVITY_INTENT, activity.getIntent());
82            activity.startActivity(intent);
83            activity.finish();
84            return true;
85        }
86
87        // Account type initialization must be delayed until the Contacts permission group
88        // has been granted (since GET_ACCOUNTS) falls under that groups.  Previously it
89        // was initialized in ContactApplication which would cause problems as
90        // AccountManager.getAccounts would return an empty array. See b/22690336
91        AccountTypeManager.getInstance(activity);
92
93        return false;
94    }
95
96    @Override
97    public void onRequestPermissionsResult(int requestCode, String permissions[],
98            int[] grantResults) {
99        if (permissions != null && permissions.length > 0
100                && isAllGranted(permissions, grantResults)) {
101            mPreviousActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
102            startActivity(mPreviousActivityIntent);
103            finish();
104            overridePendingTransition(0, 0);
105        } else {
106            Toast.makeText(this, R.string.missing_required_permission, Toast.LENGTH_SHORT).show();
107            finish();
108        }
109    }
110
111    private boolean isAllGranted(String permissions[], int[] grantResult) {
112        for (int i = 0; i < permissions.length; i++) {
113            if (grantResult[i] != PackageManager.PERMISSION_GRANTED
114                    && isPermissionRequired(permissions[i])) {
115                return false;
116            }
117        }
118        return true;
119    }
120
121    private boolean isPermissionRequired(String p) {
122        return Arrays.asList(getRequiredPermissions()).contains(p);
123    }
124
125    private void requestPermissions() {
126        Trace.beginSection("requestPermissions");
127        try {
128            // Construct a list of missing permissions
129            final ArrayList<String> unsatisfiedPermissions = new ArrayList<>();
130            for (String permission : getDesiredPermissions()) {
131                if (checkSelfPermission(permission)
132                        != PackageManager.PERMISSION_GRANTED) {
133                    unsatisfiedPermissions.add(permission);
134                }
135            }
136            if (unsatisfiedPermissions.size() == 0) {
137                throw new RuntimeException("Request permission activity was called even"
138                        + " though all permissions are satisfied.");
139            }
140            requestPermissions(
141                    unsatisfiedPermissions.toArray(new String[unsatisfiedPermissions.size()]),
142                    PERMISSIONS_REQUEST_ALL_PERMISSIONS);
143        } finally {
144            Trace.endSection();
145        }
146    }
147
148    protected static boolean hasPermissions(Context context, String[] permissions) {
149        Trace.beginSection("hasPermission");
150        try {
151            for (String permission : permissions) {
152                if (context.checkSelfPermission(permission)
153                        != PackageManager.PERMISSION_GRANTED) {
154                    return false;
155                }
156            }
157            return true;
158        } finally {
159            Trace.endSection();
160        }
161    }
162}
163