• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
4 import static android.os.Build.VERSION_CODES.M;
5 import static android.os.Build.VERSION_CODES.O;
6 import static android.os.Build.VERSION_CODES.P;
7 import static android.os.Build.VERSION_CODES.R;
8 import static java.util.stream.Collectors.toCollection;
9 import static org.robolectric.util.reflector.Reflector.reflector;
10 
11 import android.annotation.RequiresApi;
12 import android.annotation.RequiresPermission;
13 import android.app.ActivityManager;
14 import android.app.ApplicationExitInfo;
15 import android.app.IActivityManager;
16 import android.content.Context;
17 import android.content.pm.ConfigurationInfo;
18 import android.content.pm.IPackageDataObserver;
19 import android.content.pm.PackageManager;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.os.Build.VERSION_CODES;
22 import android.os.Handler;
23 import android.os.LocaleList;
24 import android.os.Process;
25 import android.os.UserHandle;
26 import android.util.ArrayMap;
27 import android.util.SparseIntArray;
28 import com.google.common.base.Preconditions;
29 import com.google.errorprone.annotations.CanIgnoreReturnValue;
30 import java.io.InputStream;
31 import java.util.ArrayDeque;
32 import java.util.ArrayList;
33 import java.util.Deque;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.concurrent.CopyOnWriteArrayList;
37 import org.robolectric.RuntimeEnvironment;
38 import org.robolectric.annotation.ClassName;
39 import org.robolectric.annotation.HiddenApi;
40 import org.robolectric.annotation.Implementation;
41 import org.robolectric.annotation.Implements;
42 import org.robolectric.annotation.RealObject;
43 import org.robolectric.annotation.Resetter;
44 import org.robolectric.shadow.api.Shadow;
45 import org.robolectric.util.ReflectionHelpers;
46 import org.robolectric.util.ReflectionHelpers.ClassParameter;
47 import org.robolectric.util.reflector.Direct;
48 import org.robolectric.util.reflector.ForType;
49 
50 /** Shadow for {@link android.app.ActivityManager} */
51 @Implements(value = ActivityManager.class)
52 public class ShadowActivityManager {
53   private int memoryClass = 16;
54   private static String backgroundPackage;
55   private static ActivityManager.MemoryInfo memoryInfo;
56   private static final List<ActivityManager.AppTask> appTasks = new CopyOnWriteArrayList<>();
57   private static final List<ActivityManager.RecentTaskInfo> recentTasks =
58       new CopyOnWriteArrayList<>();
59   private static final List<ActivityManager.RunningTaskInfo> tasks = new CopyOnWriteArrayList<>();
60   private static final List<ActivityManager.RunningServiceInfo> services =
61       new CopyOnWriteArrayList<>();
62   private static final List<ActivityManager.RunningAppProcessInfo> processes =
63       new CopyOnWriteArrayList<>();
64   private static final List<ImportanceListener> importanceListeners = new CopyOnWriteArrayList<>();
65   private static final SparseIntArray uidImportances = new SparseIntArray();
66   @RealObject private ActivityManager realObject;
67   private static Boolean isLowRamDeviceOverride = null;
68   private int lockTaskModeState = ActivityManager.LOCK_TASK_MODE_NONE;
69   private boolean isBackgroundRestricted;
70   private static final Deque<Object> appExitInfoList = new ArrayDeque<>();
71   private ConfigurationInfo configurationInfo;
72   private Context context;
73   private static final ArrayList<Locale> supportedLocales = new ArrayList<>();
74   private LocaleList deviceLocales;
75 
76   @Implementation
__constructor__(Context context, Handler handler)77   protected void __constructor__(Context context, Handler handler) {
78     Shadow.invokeConstructor(
79         ActivityManager.class,
80         realObject,
81         ClassParameter.from(Context.class, context),
82         ClassParameter.from(Handler.class, handler));
83     this.context = context;
84     ActivityManager.RunningAppProcessInfo processInfo = new ActivityManager.RunningAppProcessInfo();
85     fillInProcessInfo(processInfo);
86     processInfo.processName = context.getPackageName();
87     processInfo.pkgList = new String[] {context.getPackageName()};
88     processes.add(processInfo);
89   }
90 
91   @Implementation
getMemoryClass()92   protected int getMemoryClass() {
93     return memoryClass;
94   }
95 
96   @Implementation
isUserAMonkey()97   protected static boolean isUserAMonkey() {
98     return false;
99   }
100 
101   @Implementation
102   @HiddenApi
103   @RequiresPermission(
104       anyOf = {
105         "android.permission.INTERACT_ACROSS_USERS",
106         "android.permission.INTERACT_ACROSS_USERS_FULL"
107       })
getCurrentUser()108   protected static int getCurrentUser() {
109     return UserHandle.myUserId();
110   }
111 
112   @Implementation
getRunningTasks(int maxNum)113   protected List<ActivityManager.RunningTaskInfo> getRunningTasks(int maxNum) {
114     return tasks;
115   }
116 
117   /**
118    * For tests, returns the list of {@link android.app.ActivityManager.AppTask} set using {@link
119    * #setAppTasks(List)}. Returns empty list if nothing is set.
120    *
121    * @see #setAppTasks(List)
122    * @return List of current AppTask.
123    */
124   @Implementation
getAppTasks()125   protected List<ActivityManager.AppTask> getAppTasks() {
126     return appTasks;
127   }
128 
129   /**
130    * For tests, returns the list of {@link android.app.ActivityManager.RecentTaskInfo} set using
131    * {@link #setAppTasks(List)} with at most {@code maxNum} tasks. Returns empty list if nothing is
132    * set {@code flags} is ignored.
133    *
134    * @see #setAppTasks(List)
135    * @return List of current AppTask.
136    */
137   @Implementation
getRecentTasks(int maxNum, int flags)138   protected List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags) {
139     return recentTasks.size() > maxNum ? recentTasks.subList(0, maxNum) : recentTasks;
140   }
141 
142   /**
143    * Sets the current locales of the device. If the input is {@code null}, sets the {@link
144    * Locale#ENGLISH} as default locale.
145    */
146   @Implementation(minSdk = VERSION_CODES.Q)
setDeviceLocales(LocaleList locales)147   protected void setDeviceLocales(LocaleList locales) {
148     deviceLocales =
149         locales == null ? LocaleList.forLanguageTags(Locale.ENGLISH.toLanguageTag()) : locales;
150   }
151 
152   /**
153    * Gets the values set by {@link #setDeviceLocales(LocaleList)}.
154    *
155    * @return an {@link LocaleList} object contains the current locales.
156    */
getDeviceLocales()157   public LocaleList getDeviceLocales() {
158     return deviceLocales;
159   }
160 
161   @Implementation
getRunningServices(int maxNum)162   protected List<ActivityManager.RunningServiceInfo> getRunningServices(int maxNum) {
163     return services;
164   }
165 
166   @Implementation
getRunningAppProcesses()167   protected List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
168     // This method is explicitly documented not to return an empty list
169     if (processes.isEmpty()) {
170       return null;
171     }
172     return processes;
173   }
174 
175   /** Returns information seeded by {@link #setProcesses}. */
176   @Implementation
getMyMemoryState(ActivityManager.RunningAppProcessInfo inState)177   protected static void getMyMemoryState(ActivityManager.RunningAppProcessInfo inState) {
178     fillInProcessInfo(inState);
179     for (ActivityManager.RunningAppProcessInfo info : processes) {
180       if (info.pid == Process.myPid()) {
181         inState.importance = info.importance;
182         inState.lru = info.lru;
183         inState.importanceReasonCode = info.importanceReasonCode;
184         inState.importanceReasonPid = info.importanceReasonPid;
185         inState.lastTrimLevel = info.lastTrimLevel;
186         inState.pkgList = info.pkgList;
187         inState.processName = info.processName;
188       }
189     }
190   }
191 
fillInProcessInfo(ActivityManager.RunningAppProcessInfo processInfo)192   private static void fillInProcessInfo(ActivityManager.RunningAppProcessInfo processInfo) {
193     processInfo.pid = Process.myPid();
194     processInfo.uid = Process.myUid();
195   }
196 
197   @HiddenApi
198   @Implementation
switchUser(int userid)199   protected boolean switchUser(int userid) {
200     ShadowUserManager shadowUserManager =
201         Shadow.extract(context.getSystemService(Context.USER_SERVICE));
202     shadowUserManager.switchUser(userid);
203     return true;
204   }
205 
206   @Implementation(minSdk = VERSION_CODES.Q)
switchUser(UserHandle userHandle)207   protected boolean switchUser(UserHandle userHandle) {
208     return switchUser(userHandle.getIdentifier());
209   }
210 
211   @Implementation
killBackgroundProcesses(String packageName)212   protected void killBackgroundProcesses(String packageName) {
213     backgroundPackage = packageName;
214   }
215 
216   @Implementation
getMemoryInfo(ActivityManager.MemoryInfo outInfo)217   protected void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
218     if (memoryInfo != null) {
219       outInfo.availMem = memoryInfo.availMem;
220       outInfo.lowMemory = memoryInfo.lowMemory;
221       outInfo.threshold = memoryInfo.threshold;
222       outInfo.totalMem = memoryInfo.totalMem;
223     }
224   }
225 
226   @Implementation
getDeviceConfigurationInfo()227   protected android.content.pm.ConfigurationInfo getDeviceConfigurationInfo() {
228     return configurationInfo == null ? new ConfigurationInfo() : configurationInfo;
229   }
230 
231   /**
232    * Sets the {@link android.content.pm.ConfigurationInfo} returned by {@link
233    * ActivityManager#getDeviceConfigurationInfo()}, but has no effect otherwise.
234    */
setDeviceConfigurationInfo(ConfigurationInfo configurationInfo)235   public void setDeviceConfigurationInfo(ConfigurationInfo configurationInfo) {
236     this.configurationInfo = configurationInfo;
237   }
238 
239   /**
240    * @param tasks List of running tasks.
241    */
setTasks(List<ActivityManager.RunningTaskInfo> tasks)242   public void setTasks(List<ActivityManager.RunningTaskInfo> tasks) {
243     this.tasks.clear();
244     this.tasks.addAll(tasks);
245   }
246 
247   /**
248    * Sets the values to be returned by {@link #getAppTasks()}.
249    *
250    * @see #getAppTasks()
251    * @param appTasks List of app tasks.
252    */
setAppTasks(List<ActivityManager.AppTask> appTasks)253   public void setAppTasks(List<ActivityManager.AppTask> appTasks) {
254     this.appTasks.clear();
255     this.appTasks.addAll(appTasks);
256   }
257 
258   /**
259    * Sets the values to be returned by {@link #getRecentTasks()}.
260    *
261    * @see #getRecentTasks()
262    * @param recentTasks List of recent tasks.
263    */
setRecentTasks(List<ActivityManager.RecentTaskInfo> recentTasks)264   public void setRecentTasks(List<ActivityManager.RecentTaskInfo> recentTasks) {
265     this.recentTasks.clear();
266     this.recentTasks.addAll(recentTasks);
267   }
268 
269   /**
270    * @param services List of running services.
271    */
setServices(List<ActivityManager.RunningServiceInfo> services)272   public void setServices(List<ActivityManager.RunningServiceInfo> services) {
273     this.services.clear();
274     this.services.addAll(services);
275   }
276 
277   /**
278    * @param processes List of running processes.
279    */
setProcesses(List<ActivityManager.RunningAppProcessInfo> processes)280   public void setProcesses(List<ActivityManager.RunningAppProcessInfo> processes) {
281     ShadowActivityManager.processes.clear();
282     ShadowActivityManager.processes.addAll(processes);
283   }
284 
285   /**
286    * @return Get the package name of the last background processes killed.
287    */
getBackgroundPackage()288   public String getBackgroundPackage() {
289     return backgroundPackage;
290   }
291 
292   /**
293    * @param memoryClass Set the application's memory class.
294    */
setMemoryClass(int memoryClass)295   public void setMemoryClass(int memoryClass) {
296     this.memoryClass = memoryClass;
297   }
298 
299   /**
300    * @param memoryInfo Set the application's memory info.
301    */
setMemoryInfo(ActivityManager.MemoryInfo memoryInfo)302   public void setMemoryInfo(ActivityManager.MemoryInfo memoryInfo) {
303     this.memoryInfo = memoryInfo;
304   }
305 
306   @Implementation(minSdk = O)
getService()307   protected static IActivityManager getService() {
308     return ReflectionHelpers.createNullProxy(IActivityManager.class);
309   }
310 
311   @Implementation
isLowRamDevice()312   protected boolean isLowRamDevice() {
313     if (isLowRamDeviceOverride != null) {
314       return isLowRamDeviceOverride;
315     }
316     return reflector(ActivityManagerReflector.class, realObject).isLowRamDevice();
317   }
318 
319   /** Override the return value of isLowRamDevice(). */
setIsLowRamDevice(boolean isLowRamDevice)320   public void setIsLowRamDevice(boolean isLowRamDevice) {
321     isLowRamDeviceOverride = isLowRamDevice;
322   }
323 
324   @Implementation(minSdk = O)
addOnUidImportanceListener( @lassName"android.app.ActivityManager$OnUidImportanceListener") Object listener, int importanceCutpoint)325   protected void addOnUidImportanceListener(
326       @ClassName("android.app.ActivityManager$OnUidImportanceListener") Object listener,
327       int importanceCutpoint) {
328     importanceListeners.add(new ImportanceListener(listener, (Integer) importanceCutpoint));
329   }
330 
331   @Implementation(minSdk = O)
removeOnUidImportanceListener( @lassName"android.app.ActivityManager$OnUidImportanceListener") Object listener)332   protected void removeOnUidImportanceListener(
333       @ClassName("android.app.ActivityManager$OnUidImportanceListener") Object listener) {
334     importanceListeners.remove(new ImportanceListener(listener));
335   }
336 
337   @Implementation(minSdk = M)
getPackageImportance(String packageName)338   protected int getPackageImportance(String packageName) {
339     try {
340       return uidImportances.get(
341           context.getPackageManager().getPackageUid(packageName, 0), IMPORTANCE_GONE);
342     } catch (NameNotFoundException e) {
343       return IMPORTANCE_GONE;
344     }
345   }
346 
347   @Implementation(minSdk = O)
getUidImportance(int uid)348   protected int getUidImportance(int uid) {
349     return uidImportances.get(uid, IMPORTANCE_GONE);
350   }
351 
setUidImportance(int uid, int importance)352   public void setUidImportance(int uid, int importance) {
353     uidImportances.put(uid, importance);
354     for (ImportanceListener listener : importanceListeners) {
355       listener.onUidImportanceChanged(uid, importance);
356     }
357   }
358 
359   @Implementation(minSdk = VERSION_CODES.M)
getLockTaskModeState()360   protected int getLockTaskModeState() {
361     return lockTaskModeState;
362   }
363 
364   @Implementation
isInLockTaskMode()365   protected boolean isInLockTaskMode() {
366     return getLockTaskModeState() != ActivityManager.LOCK_TASK_MODE_NONE;
367   }
368 
369   /**
370    * Sets lock task mode state to be reported by {@link ActivityManager#getLockTaskModeState}, but
371    * has no effect otherwise.
372    */
setLockTaskModeState(int lockTaskModeState)373   public void setLockTaskModeState(int lockTaskModeState) {
374     this.lockTaskModeState = lockTaskModeState;
375   }
376 
377   @Resetter
reset()378   public static void reset() {
379     backgroundPackage = null;
380     memoryInfo = null;
381     appTasks.clear();
382     recentTasks.clear();
383     tasks.clear();
384     services.clear();
385     processes.clear();
386     importanceListeners.clear();
387     uidImportances.clear();
388     appExitInfoList.clear();
389     isLowRamDeviceOverride = null;
390     supportedLocales.clear();
391   }
392 
393   /** Returns the background restriction state set by {@link #setBackgroundRestricted}. */
394   @Implementation(minSdk = P)
isBackgroundRestricted()395   protected boolean isBackgroundRestricted() {
396     return isBackgroundRestricted;
397   }
398 
399   /**
400    * Sets the background restriction state reported by {@link
401    * ActivityManager#isBackgroundRestricted}, but has no effect otherwise.
402    */
setBackgroundRestricted(boolean isBackgroundRestricted)403   public void setBackgroundRestricted(boolean isBackgroundRestricted) {
404     this.isBackgroundRestricted = isBackgroundRestricted;
405   }
406 
407   /**
408    * Returns the matched {@link ApplicationExitInfo} added by {@link #addApplicationExitInfo}.
409    * {@code packageName} is ignored.
410    */
411   @Implementation(minSdk = R)
getHistoricalProcessExitReasons( String packageName, int pid, int maxNum)412   protected List</*android.app.ApplicationExitInfo*/ ?> getHistoricalProcessExitReasons(
413       String packageName, int pid, int maxNum) {
414     return appExitInfoList.stream()
415         .filter(
416             appExitInfo ->
417                 (int) pid == 0 || ((ApplicationExitInfo) appExitInfo).getPid() == (int) pid)
418         .limit((int) maxNum == 0 ? appExitInfoList.size() : (int) maxNum)
419         .collect(toCollection(ArrayList::new));
420   }
421 
422   /**
423    * Adds an {@link ApplicationExitInfo} with the given information
424    *
425    * @deprecated Prefer using overload with {@link ApplicationExitInfoBuilder}
426    */
427   @Deprecated
428   @RequiresApi(api = R)
addApplicationExitInfo(String processName, int pid, int reason, int status)429   public void addApplicationExitInfo(String processName, int pid, int reason, int status) {
430     ApplicationExitInfo info =
431         ApplicationExitInfoBuilder.newBuilder()
432             .setProcessName(processName)
433             .setPid(pid)
434             .setReason(reason)
435             .setStatus(status)
436             .build();
437     addApplicationExitInfo(info);
438   }
439 
440   /** Adds given {@link ApplicationExitInfo}, see {@link ApplicationExitInfoBuilder} */
441   @RequiresApi(api = R)
addApplicationExitInfo(Object info)442   public void addApplicationExitInfo(Object info) {
443     Preconditions.checkArgument(info instanceof ApplicationExitInfo);
444     appExitInfoList.addFirst(info);
445   }
446 
447   @Implementation
clearApplicationUserData(String packageName, IPackageDataObserver observer)448   protected boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
449     // The real ActivityManager calls clearApplicationUserData on the ActivityManagerService that
450     // calls PackageManager#clearApplicationUserData.
451     context.getPackageManager().clearApplicationUserData(packageName, observer);
452     return true;
453   }
454 
455   /**
456    * Returns true after clearing application user data was requested by calling {@link
457    * ActivityManager#clearApplicationUserData()}.
458    */
isApplicationUserDataCleared()459   public boolean isApplicationUserDataCleared() {
460     PackageManager packageManager = RuntimeEnvironment.getApplication().getPackageManager();
461     return Shadow.<ShadowApplicationPackageManager>extract(packageManager)
462         .getClearedApplicationUserDataPackages()
463         .contains(RuntimeEnvironment.getApplication().getPackageName());
464   }
465 
466   /** Builder class for {@link ApplicationExitInfo} */
467   @RequiresApi(api = R)
468   public static class ApplicationExitInfoBuilder {
469 
470     private final ApplicationExitInfo instance;
471     private final ShadowApplicationExitInfo shadow;
472 
newBuilder()473     public static ApplicationExitInfoBuilder newBuilder() {
474       return new ApplicationExitInfoBuilder();
475     }
476 
477     @CanIgnoreReturnValue
setDefiningUid(int uid)478     public ApplicationExitInfoBuilder setDefiningUid(int uid) {
479       instance.setDefiningUid(uid);
480       return this;
481     }
482 
483     @CanIgnoreReturnValue
setDescription(String description)484     public ApplicationExitInfoBuilder setDescription(String description) {
485       instance.setDescription(description);
486       return this;
487     }
488 
489     @CanIgnoreReturnValue
setImportance(int importance)490     public ApplicationExitInfoBuilder setImportance(int importance) {
491       instance.setImportance(importance);
492       return this;
493     }
494 
495     @CanIgnoreReturnValue
setPackageUid(int packageUid)496     public ApplicationExitInfoBuilder setPackageUid(int packageUid) {
497       instance.setPackageUid(packageUid);
498       return this;
499     }
500 
501     @CanIgnoreReturnValue
setPid(int pid)502     public ApplicationExitInfoBuilder setPid(int pid) {
503       instance.setPid(pid);
504       return this;
505     }
506 
507     @CanIgnoreReturnValue
setProcessName(String processName)508     public ApplicationExitInfoBuilder setProcessName(String processName) {
509       instance.setProcessName(processName);
510       return this;
511     }
512 
513     @CanIgnoreReturnValue
setProcessStateSummary(byte[] processStateSummary)514     public ApplicationExitInfoBuilder setProcessStateSummary(byte[] processStateSummary) {
515       instance.setProcessStateSummary(processStateSummary);
516       return this;
517     }
518 
519     @CanIgnoreReturnValue
setPss(long pss)520     public ApplicationExitInfoBuilder setPss(long pss) {
521       instance.setPss(pss);
522       return this;
523     }
524 
525     @CanIgnoreReturnValue
setRealUid(int realUid)526     public ApplicationExitInfoBuilder setRealUid(int realUid) {
527       instance.setRealUid(realUid);
528       return this;
529     }
530 
531     @CanIgnoreReturnValue
setReason(int reason)532     public ApplicationExitInfoBuilder setReason(int reason) {
533       instance.setReason(reason);
534       return this;
535     }
536 
537     @CanIgnoreReturnValue
setRss(long rss)538     public ApplicationExitInfoBuilder setRss(long rss) {
539       instance.setRss(rss);
540       return this;
541     }
542 
543     @CanIgnoreReturnValue
setStatus(int status)544     public ApplicationExitInfoBuilder setStatus(int status) {
545       instance.setStatus(status);
546       return this;
547     }
548 
549     @CanIgnoreReturnValue
setTimestamp(long timestamp)550     public ApplicationExitInfoBuilder setTimestamp(long timestamp) {
551       instance.setTimestamp(timestamp);
552       return this;
553     }
554 
555     @CanIgnoreReturnValue
setTraceInputStream(InputStream in)556     public ApplicationExitInfoBuilder setTraceInputStream(InputStream in) {
557       shadow.setTraceInputStream(in);
558       return this;
559     }
560 
build()561     public ApplicationExitInfo build() {
562       return instance;
563     }
564 
ApplicationExitInfoBuilder()565     private ApplicationExitInfoBuilder() {
566       this.instance = new ApplicationExitInfo();
567       this.shadow = Shadow.extract(instance);
568     }
569   }
570 
571   @ForType(ActivityManager.class)
572   interface ActivityManagerReflector {
573 
574     @Direct
isLowRamDevice()575     boolean isLowRamDevice();
576   }
577 
578   /**
579    * Helper class mimicking the package-private UidObserver class inside {@link ActivityManager}.
580    *
581    * <p>This class is responsible for maintaining the cutpoint of the corresponding {@link
582    * ActivityManager.OnUidImportanceListener} and invoking the listener only when the importance of
583    * a given UID crosses the cutpoint.
584    */
585   private static class ImportanceListener {
586 
587     private final ActivityManager.OnUidImportanceListener listener;
588     private final int importanceCutpoint;
589 
590     private final ArrayMap<Integer, Boolean> lastAboveCuts = new ArrayMap<>();
591 
ImportanceListener(Object listener)592     ImportanceListener(Object listener) {
593       this(listener, 0);
594     }
595 
ImportanceListener(Object listener, int importanceCutpoint)596     ImportanceListener(Object listener, int importanceCutpoint) {
597       this.listener = (ActivityManager.OnUidImportanceListener) listener;
598       this.importanceCutpoint = importanceCutpoint;
599     }
600 
onUidImportanceChanged(int uid, int importance)601     void onUidImportanceChanged(int uid, int importance) {
602       Boolean isAboveCut = importance > importanceCutpoint;
603       if (!isAboveCut.equals(lastAboveCuts.get(uid))) {
604         lastAboveCuts.put(uid, isAboveCut);
605         listener.onUidImportance(uid, importance);
606       }
607     }
608 
609     @Override
equals(Object o)610     public boolean equals(Object o) {
611       if (this == o) {
612         return true;
613       }
614       if (!(o instanceof ImportanceListener)) {
615         return false;
616       }
617 
618       ImportanceListener that = (ImportanceListener) o;
619       return listener.equals(that.listener);
620     }
621 
622     @Override
hashCode()623     public int hashCode() {
624       return listener.hashCode();
625     }
626   }
627 }
628