1 /* 2 * Copyright (C) 2017 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 package com.android.settings.applications; 17 18 import static android.app.AppOpsManager.MODE_ALLOWED; 19 import static android.app.AppOpsManager.MODE_ERRORED; 20 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; 21 22 import android.app.AlertDialog; 23 import android.app.AppOpsManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.os.Bundle; 27 import android.provider.Settings; 28 import android.support.v14.preference.SwitchPreference; 29 import android.support.v7.preference.Preference; 30 import android.support.v7.preference.Preference.OnPreferenceChangeListener; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 34 import com.android.settings.R; 35 import com.android.settings.overlay.FeatureFactory; 36 37 public class PictureInPictureDetails extends AppInfoWithHeader 38 implements OnPreferenceChangeListener { 39 40 private static final String KEY_APP_OPS_SETTINGS_SWITCH = "app_ops_settings_switch"; 41 private static final String KEY_APP_OPS_SETTINGS_PREFS = "app_ops_settings_preference"; 42 private static final String KEY_APP_OPS_SETTINGS_DESC = "app_ops_settings_description"; 43 private static final String LOG_TAG = "PictureInPictureDetails"; 44 45 private SwitchPreference mSwitchPref; 46 private Preference mOverlayDesc; 47 private Intent mSettingsIntent; 48 49 @Override 50 public void onCreate(Bundle savedInstanceState) { 51 super.onCreate(savedInstanceState); 52 53 // find preferences 54 addPreferencesFromResource(R.xml.app_ops_permissions_details); 55 mSwitchPref = (SwitchPreference) findPreference(KEY_APP_OPS_SETTINGS_SWITCH); 56 mOverlayDesc = findPreference(KEY_APP_OPS_SETTINGS_DESC); 57 getPreferenceScreen().removePreference(findPreference(KEY_APP_OPS_SETTINGS_PREFS)); 58 59 // set title/summary for all of them 60 getPreferenceScreen().setTitle(R.string.picture_in_picture_app_detail_title); 61 mSwitchPref.setTitle(R.string.picture_in_picture_app_detail_switch); 62 mOverlayDesc.setSummary(R.string.picture_in_picture_app_detail_summary); 63 64 // install event listeners 65 mSwitchPref.setOnPreferenceChangeListener(this); 66 67 mSettingsIntent = new Intent(Intent.ACTION_MAIN) 68 .setAction(Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS); 69 } 70 71 @Override 72 public boolean onPreferenceChange(Preference preference, Object newValue) { 73 if (preference == mSwitchPref) { 74 logSpecialPermissionChange((Boolean) newValue, mPackageName); 75 setEnterPipStateForPackage(getActivity(), mPackageInfo.applicationInfo.uid, mPackageName, 76 (Boolean) newValue); 77 return true; 78 } 79 return false; 80 } 81 82 @Override 83 protected boolean refreshUi() { 84 boolean isAllowed = getEnterPipStateForPackage(getActivity(), 85 mPackageInfo.applicationInfo.uid, mPackageName); 86 mSwitchPref.setChecked(isAllowed); 87 return true; 88 } 89 90 @Override 91 protected AlertDialog createDialog(int id, int errorCode) { 92 return null; 93 } 94 95 @Override 96 public int getMetricsCategory() { 97 return MetricsEvent.SETTINGS_MANAGE_PICTURE_IN_PICTURE; 98 } 99 100 /** 101 * Sets whether the app associated with the given {@param packageName} is allowed to enter 102 * picture-in-picture. 103 */ 104 static void setEnterPipStateForPackage(Context context, int uid, String packageName, 105 boolean value) { 106 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 107 final int newMode = value ? MODE_ALLOWED : MODE_ERRORED; 108 appOps.setMode(OP_PICTURE_IN_PICTURE, uid, packageName, newMode); 109 } 110 111 /** 112 * @return whether the app associated with the given {@param packageName} is allowed to enter 113 * picture-in-picture. 114 */ 115 static boolean getEnterPipStateForPackage(Context context, int uid, String packageName) { 116 final AppOpsManager appOps = context.getSystemService(AppOpsManager.class); 117 return appOps.checkOpNoThrow(OP_PICTURE_IN_PICTURE, uid, packageName) == MODE_ALLOWED; 118 } 119 120 /** 121 * @return the summary for the current state of whether the app associated with the given 122 * {@param packageName} is allowed to enter picture-in-picture. 123 */ 124 static int getPreferenceSummary(Context context, int uid, String packageName) { 125 final boolean enabled = PictureInPictureDetails.getEnterPipStateForPackage(context, uid, 126 packageName); 127 return enabled ? R.string.picture_in_picture_on : R.string.picture_in_picture_off; 128 } 129 130 @VisibleForTesting 131 void logSpecialPermissionChange(boolean newState, String packageName) { 132 int logCategory = newState 133 ? MetricsEvent.APP_PICTURE_IN_PICTURE_ALLOW 134 : MetricsEvent.APP_PICTURE_IN_PICTURE_DENY; 135 FeatureFactory.getFactory(getContext()) 136 .getMetricsFeatureProvider().action(getContext(), logCategory, packageName); 137 } 138 } 139