1 /* 2 * Copyright 2018 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 androidx.core.view; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.util.Log; 23 import android.view.MenuItem; 24 import android.view.SubMenu; 25 import android.view.View; 26 27 import androidx.annotation.RestrictTo; 28 29 /** 30 * This class is a mediator for accomplishing a given task, for example sharing a file. It is 31 * responsible for creating a view that performs an action that accomplishes the task. This class 32 * also implements other functions such a performing a default action. 33 * 34 * <p class="note"><strong>Note:</strong> This class is included in the <a 35 * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility 36 * with API level 4 and higher. If you're developing your app for API level 14 and higher 37 * <em>only</em>, you should instead use the framework {@link android.view.ActionProvider} 38 * class.</p> 39 * 40 * <p>An ActionProvider can be 41 * optionally specified for a {@link android.view.MenuItem} and in such a case it will be 42 * responsible for 43 * creating the action view that appears in the {@link android.app.ActionBar} as a substitute for 44 * the menu item when the item is displayed as an action item. Also the provider is responsible for 45 * performing a default action if a menu item placed on the overflow menu of the ActionBar is 46 * selected and none of the menu item callbacks has handled the selection. For this case the 47 * provider can also optionally provide a sub-menu for accomplishing the task at hand. 48 * 49 * <p>There are two ways for using an action provider for creating and handling of action views: 50 * 51 * <ul><li> Setting the action provider on a {@link android.view.MenuItem} directly by 52 * calling {@link 53 * androidx.core.view.MenuItemCompat#setActionProvider(android.view.MenuItem, ActionProvider)}. 54 * </li> 55 * 56 * <li>Declaring the action provider in the menu XML resource. For example: 57 * 58 * <pre><code> 59 * <item android:id="@+id/my_menu_item" 60 * android:title="@string/my_menu_item_title" 61 * android:icon="@drawable/my_menu_item_icon" 62 * android:showAsAction="ifRoom" 63 * android:actionProviderClass="foo.bar.SomeActionProvider" /> 64 * </code></pre> 65 * </li></ul></p> 66 * 67 * <h3>Creating a custom action provider</h3> 68 * 69 * <p>To create a custom action provider, extend ActionProvider and implement 70 * its callback methods as necessary. In particular, implement the following 71 * methods:</p> 72 * 73 * <dl> 74 * <dt>{@link #ActionProvider ActionProvider()} constructor</dt> 75 * <dd>This constructor is passed the application context. You should 76 * save the context in a member field to use in the other callback methods.</dd> 77 * 78 * <dt>{@link #onCreateActionView onCreateActionView(MenuItem)}</dt> 79 * <dd>The system calls this method when the action provider is created. 80 * You define the action provider's layout through the implementation of this 81 * method. Use the context acquired 82 * from the constructor to instantiate a {@link android.view.LayoutInflater} and 83 * inflate your action provider's layout from an XML resource, then hook up 84 * event listeners for the view's components. For example: 85 * 86 *<pre> 87 * public View onCreateActionView(MenuItem forItem) { 88 * // Inflate the action provider to be shown on the action bar. 89 * LayoutInflater layoutInflater = LayoutInflater.from(mContext); 90 * View providerView = 91 * layoutInflater.inflate(R.layout.my_action_provider, null); 92 * ImageButton button = 93 * (ImageButton) providerView.findViewById(R.id.button); 94 * button.setOnClickListener(new View.OnClickListener() { 95 * @Override 96 * public void onClick(View v) { 97 * // Do something... 98 * } 99 * }); 100 * return providerView; 101 * }</pre> 102 * </dd> 103 * 104 * <dt>{@link #onPerformDefaultAction onPerformDefaultAction()}</dt> 105 * <dd><p>The system calls this method when the user selects a menu item from the action 106 * overflow. The action provider should perform a default action for the 107 * menu item. The system does not call this method if the menu item opens a submenu.</p> 108 * 109 * <p>If your action provider presents a submenu through the 110 * {@link #onPrepareSubMenu onPrepareSubMenu()} callback, the submenu 111 * appears even if the action provider is in the overflow menu. 112 * Thus, the system never calls {@link #onPerformDefaultAction 113 * onPerformDefaultAction()} if there is a submenu.</p> 114 * 115 * <p class="note"> <strong>Note:</strong> An activity or a fragment that 116 * implements <code>onOptionsItemSelected()</code> can override the action 117 * provider's default behavior (unless it uses a submenu) by handling the 118 * item-selected event and returning <code>true</code>. In this case, the 119 * system does not call 120 * {@link #onPerformDefaultAction onPerformDefaultAction()}.</p></dd> 121 * </dl> 122 * 123 * 124 * @see androidx.core.view.MenuItemCompat#setActionProvider(android.view.MenuItem, ActionProvider) 125 * @see androidx.core.view.MenuItemCompat#getActionProvider(android.view.MenuItem) 126 */ 127 public abstract class ActionProvider { 128 private static final String TAG = "ActionProvider(support)"; 129 private final Context mContext; 130 131 private SubUiVisibilityListener mSubUiVisibilityListener; 132 private VisibilityListener mVisibilityListener; 133 134 /** 135 * Creates a new instance. 136 * 137 * @param context Context for accessing resources. 138 */ ActionProvider(Context context)139 public ActionProvider(Context context) { 140 mContext = context; 141 } 142 143 /** 144 * Gets the context associated with this action provider. 145 */ getContext()146 public Context getContext() { 147 return mContext; 148 } 149 150 /** 151 * Factory method for creating new action views. 152 * 153 * @return A new action view. 154 */ onCreateActionView()155 public abstract View onCreateActionView(); 156 157 /** 158 * Factory method called by the Android framework to create new action views. 159 * This method returns a new action view for the given MenuItem. 160 * 161 * <p>If your ActionProvider implementation overrides the deprecated no-argument overload 162 * {@link #onCreateActionView()}, overriding this method for devices running API 16 or later 163 * is recommended but optional. The default implementation calls {@link #onCreateActionView()} 164 * for compatibility with applications written for older platform versions.</p> 165 * 166 * @param forItem MenuItem to create the action view for 167 * @return the new action view 168 */ onCreateActionView(MenuItem forItem)169 public View onCreateActionView(MenuItem forItem) { 170 return onCreateActionView(); 171 } 172 173 /** 174 * The result of this method determines whether or not {@link #isVisible()} will be used 175 * by the {@link MenuItem} this ActionProvider is bound to help determine its visibility. 176 * 177 * @return true if this ActionProvider overrides the visibility of the MenuItem 178 * it is bound to, false otherwise. The default implementation returns false. 179 * @see #isVisible() 180 */ overridesItemVisibility()181 public boolean overridesItemVisibility() { 182 return false; 183 } 184 185 /** 186 * If {@link #overridesItemVisibility()} returns true, the return value of this method 187 * will help determine the visibility of the {@link MenuItem} this ActionProvider is bound to. 188 * 189 * <p>If the MenuItem's visibility is explicitly set to false by the application, 190 * the MenuItem will not be shown, even if this method returns true.</p> 191 * 192 * @return true if the MenuItem this ActionProvider is bound to is visible, false if 193 * it is invisible. The default implementation returns true. 194 */ isVisible()195 public boolean isVisible() { 196 return true; 197 } 198 199 /** 200 * If this ActionProvider is associated with an item in a menu, 201 * refresh the visibility of the item based on {@link #overridesItemVisibility()} and 202 * {@link #isVisible()}. If {@link #overridesItemVisibility()} returns false, this call 203 * will have no effect. 204 */ refreshVisibility()205 public void refreshVisibility() { 206 if (mVisibilityListener != null && overridesItemVisibility()) { 207 mVisibilityListener.onActionProviderVisibilityChanged(isVisible()); 208 } 209 } 210 211 /** 212 * Performs an optional default action. 213 * 214 * <p>For the case of an action provider placed in a menu 215 * item not shown as an action this method is invoked if previous callbacks for processing menu 216 * selection has handled the event. 217 * 218 * <p> A menu item selection is processed in the following order: 219 * 220 * <ul><li>Receiving a call to 221 * {@link android.view.MenuItem.OnMenuItemClickListener#onMenuItemClick 222 * MenuItem.OnMenuItemClickListener.onMenuItemClick}.</li> 223 * 224 * <li>Receiving a call to 225 * {@link android.app.Activity#onOptionsItemSelected(android.view.MenuItem)} 226 * FragmentActivity.onOptionsItemSelected(MenuItem)} 227 * </li> 228 * 229 * <li>Receiving a call to 230 * {@link androidx.fragment.app.Fragment#onOptionsItemSelected(android.view.MenuItem)} 231 * Fragment.onOptionsItemSelected(MenuItem)}</li> 232 * 233 * <li>Launching the {@link android.content.Intent} set via 234 * {@link android.view.MenuItem#setIntent(android.content.Intent) 235 * MenuItem.setIntent(android.content.Intent)} 236 * </li> 237 * 238 * <li>Invoking this method.</li></ul> 239 * 240 * <p>The default implementation does not perform any action and returns false. 241 */ onPerformDefaultAction()242 public boolean onPerformDefaultAction() { 243 return false; 244 } 245 246 /** 247 * Determines if this ActionProvider has a submenu associated with it. 248 * 249 * <p>Associated submenus will be shown when an action view is not. This provider instance will 250 * receive a call to {@link #onPrepareSubMenu(SubMenu)} after the call to {@link 251 * #onPerformDefaultAction()} and before a submenu is displayed to the user. 252 * 253 * @return true if the item backed by this provider should have an associated submenu 254 */ hasSubMenu()255 public boolean hasSubMenu() { 256 return false; 257 } 258 259 /** 260 * Called to prepare an associated submenu for the menu item backed by this ActionProvider. 261 * 262 * <p>if {@link #hasSubMenu()} returns true, this method will be called when the menu item is 263 * selected to prepare the submenu for presentation to the user. Apps may use this to create or 264 * alter submenu content right before display. 265 * 266 * @param subMenu Submenu that will be displayed 267 */ onPrepareSubMenu(SubMenu subMenu)268 public void onPrepareSubMenu(SubMenu subMenu) { 269 } 270 271 /** 272 * Notify the system that the visibility of an action view's sub-UI such as an anchored popup 273 * has changed. This will affect how other system visibility notifications occur. 274 * 275 * @hide Pending future API approval 276 */ 277 @RestrictTo(LIBRARY_GROUP) subUiVisibilityChanged(boolean isVisible)278 public void subUiVisibilityChanged(boolean isVisible) { 279 if (mSubUiVisibilityListener != null) { 280 mSubUiVisibilityListener.onSubUiVisibilityChanged(isVisible); 281 } 282 } 283 284 /** 285 * @hide Internal use only 286 */ 287 @RestrictTo(LIBRARY_GROUP) setSubUiVisibilityListener(SubUiVisibilityListener listener)288 public void setSubUiVisibilityListener(SubUiVisibilityListener listener) { 289 mSubUiVisibilityListener = listener; 290 } 291 292 /** 293 * Set a listener to be notified when this ActionProvider's overridden visibility changes. 294 * This should only be used by MenuItem implementations. 295 * 296 * @param listener listener to set 297 */ setVisibilityListener(VisibilityListener listener)298 public void setVisibilityListener(VisibilityListener listener) { 299 if (mVisibilityListener != null && listener != null) { 300 Log.w(TAG, "setVisibilityListener: Setting a new ActionProvider.VisibilityListener " + 301 "when one is already set. Are you reusing this " + getClass().getSimpleName() + 302 " instance while it is still in use somewhere else?"); 303 } 304 mVisibilityListener = listener; 305 } 306 307 /** 308 * @hide 309 */ 310 @RestrictTo(LIBRARY_GROUP) reset()311 public void reset() { 312 mVisibilityListener = null; 313 mSubUiVisibilityListener = null; 314 } 315 316 /** 317 * @hide Internal use only 318 */ 319 @RestrictTo(LIBRARY_GROUP) 320 public interface SubUiVisibilityListener { 321 onSubUiVisibilityChanged(boolean isVisible)322 void onSubUiVisibilityChanged(boolean isVisible); 323 } 324 325 /** 326 * Listens to changes in visibility as reported by {@link ActionProvider#refreshVisibility()}. 327 * 328 * @see ActionProvider#overridesItemVisibility() 329 * @see ActionProvider#isVisible() 330 */ 331 public interface VisibilityListener { onActionProviderVisibilityChanged(boolean isVisible)332 void onActionProviderVisibilityChanged(boolean isVisible); 333 } 334 } 335