• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.server.wm;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.annotation.UserIdInt;
23 import android.car.app.CarActivityManager;
24 import android.car.builtin.os.UserManagerHelper;
25 import android.car.builtin.util.Slogf;
26 import android.car.builtin.view.DisplayHelper;
27 import android.car.builtin.window.DisplayAreaOrganizerHelper;
28 import android.content.ComponentName;
29 import android.hardware.display.DisplayManager;
30 import android.os.ServiceSpecificException;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 import android.util.Pair;
34 import android.util.SparseIntArray;
35 import android.view.Display;
36 
37 import com.android.car.internal.dep.Trace;
38 import com.android.car.internal.util.IndentingPrintWriter;
39 import com.android.internal.annotations.GuardedBy;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.List;
44 
45 /**
46  * Implementation of {@link CarLaunchParamsModifierUpdatable}.
47  *
48  * @hide
49  */
50 @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
51 public final class CarLaunchParamsModifierUpdatableImpl
52         implements CarLaunchParamsModifierUpdatable {
53     private static final String TAG = CarLaunchParamsModifierUpdatableImpl.class.getSimpleName();
54     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
55     // Comes from android.os.UserHandle.USER_NULL.
56     private static final int USER_NULL = -10000;
57 
58     private final CarLaunchParamsModifierInterface mBuiltin;
59     @NonNull
60     private final CarDisplayCompatScaleProviderUpdatableImpl mDisplayCompatProvider;
61     private final Object mLock = new Object();
62 
63     // Always start with USER_SYSTEM as the timing of handleCurrentUserSwitching(USER_SYSTEM) is not
64     // guaranteed to be earler than 1st Activity launch.
65     @GuardedBy("mLock")
66     private int mDriverUser = UserManagerHelper.USER_SYSTEM;
67 
68     // TODO: Switch from tracking displays to tracking display areas instead
69     /**
70      * This one is for holding all passenger (=profile user) displays which are mostly static unless
71      * displays are added / removed. Note that {@link #mDisplayToProfileUserMapping} can be empty
72      * while user is assigned and that cannot always tell if specific display is for driver or not.
73      */
74     @GuardedBy("mLock")
75     private final ArrayList<Integer> mPassengerDisplays = new ArrayList<>();
76 
77     /** key: display id, value: profile user id */
78     @GuardedBy("mLock")
79     private final SparseIntArray mDisplayToProfileUserMapping = new SparseIntArray();
80 
81     /** key: profile user id, value: display id */
82     @GuardedBy("mLock")
83     private final SparseIntArray mDefaultDisplayForProfileUser = new SparseIntArray();
84 
85     /** key: Activity, value: TaskDisplayAreaWrapper */
86     @GuardedBy("mLock")
87     private final ArrayMap<ComponentName, TaskDisplayAreaWrapper> mPersistentActivities =
88             new ArrayMap<>();
89 
CarLaunchParamsModifierUpdatableImpl(CarLaunchParamsModifierInterface builtin, @NonNull CarDisplayCompatScaleProviderUpdatableImpl carDisplayCompatProvider)90     public CarLaunchParamsModifierUpdatableImpl(CarLaunchParamsModifierInterface builtin,
91             @NonNull CarDisplayCompatScaleProviderUpdatableImpl carDisplayCompatProvider) {
92         mBuiltin = builtin;
93         mDisplayCompatProvider = carDisplayCompatProvider;
94     }
95 
requiresDisplayCompat(ComponentName launchIntent, int userId)96     private boolean requiresDisplayCompat(ComponentName launchIntent, int userId) {
97         return mDisplayCompatProvider
98                 .requiresDisplayCompat(launchIntent.getPackageName(), userId);
99     }
100 
getDisplayListener()101     public DisplayManager.DisplayListener getDisplayListener() {
102         return mDisplayListener;
103     }
104 
105     private final DisplayManager.DisplayListener mDisplayListener =
106             new DisplayManager.DisplayListener() {
107                 @Override
108                 public void onDisplayAdded(int displayId) {
109                     // ignore. car service should update whiltelist.
110                 }
111 
112                 @Override
113                 public void onDisplayRemoved(int displayId) {
114                     synchronized (mLock) {
115                         mPassengerDisplays.remove(Integer.valueOf(displayId));
116                         updateProfileUserConfigForDisplayRemovalLocked(displayId);
117                     }
118                 }
119 
120                 @Override
121                 public void onDisplayChanged(int displayId) {
122                     // ignore
123                 }
124             };
125 
126     @GuardedBy("mLock")
updateProfileUserConfigForDisplayRemovalLocked(int displayId)127     private void updateProfileUserConfigForDisplayRemovalLocked(int displayId) {
128         mDisplayToProfileUserMapping.delete(displayId);
129         int i = mDefaultDisplayForProfileUser.indexOfValue(displayId);
130         if (i >= 0) {
131             mDefaultDisplayForProfileUser.removeAt(i);
132         }
133     }
134 
135     @Override
handleUserVisibilityChanged(int userId, boolean visible)136     public void handleUserVisibilityChanged(int userId, boolean visible) {
137         synchronized (mLock) {
138             if (DBG) {
139                 Slogf.d(TAG, "handleUserVisibilityChanged user=%d, visible=%b",
140                         userId, visible);
141             }
142             if (userId != mDriverUser || visible) {
143                 return;
144             }
145             int currentOrTargetUserId = getCurrentOrTargetUserId();
146             maySwitchCurrentDriver(currentOrTargetUserId);
147         }
148     }
149 
getCurrentOrTargetUserId()150     private int getCurrentOrTargetUserId() {
151         Pair<Integer, Integer> currentAndTargetUserIds = mBuiltin.getCurrentAndTargetUserIds();
152         int currentUserId = currentAndTargetUserIds.first;
153         int targetUserId = currentAndTargetUserIds.second;
154         int currentOrTargetUserId = targetUserId != USER_NULL ? targetUserId : currentUserId;
155         return currentOrTargetUserId;
156     }
157 
158     /** Notifies user switching. */
handleCurrentUserSwitching(@serIdInt int newUserId)159     public void handleCurrentUserSwitching(@UserIdInt int newUserId) {
160         if (DBG) Slogf.d(TAG, "handleCurrentUserSwitching user=%d", newUserId);
161         maySwitchCurrentDriver(newUserId);
162     }
163 
maySwitchCurrentDriver(int userId)164     private void maySwitchCurrentDriver(int userId) {
165         synchronized (mLock) {
166             if (DBG) {
167                 Slogf.d(TAG, "maySwitchCurrentDriver old=%d, new=%d", mDriverUser, userId);
168             }
169             if (mDriverUser == userId) {
170                 return;
171             }
172             mDriverUser = userId;
173             mDefaultDisplayForProfileUser.clear();
174             mDisplayToProfileUserMapping.clear();
175         }
176     }
177 
178     /** Notifies user starting. */
handleUserStarting(int startingUser)179     public void handleUserStarting(int startingUser) {
180         if (DBG) Slogf.d(TAG, "handleUserStarting user=%d", startingUser);
181         // Do nothing
182     }
183 
184     /** Notifies user stopped. */
handleUserStopped(@serIdInt int stoppedUser)185     public void handleUserStopped(@UserIdInt int stoppedUser) {
186         if (DBG) Slogf.d(TAG, "handleUserStopped user=%d", stoppedUser);
187         // Note that the current user is never stopped. It always takes switching into
188         // non-current user before stopping the user.
189         synchronized (mLock) {
190             removeUserFromAllowlistsLocked(stoppedUser);
191         }
192     }
193 
194     @GuardedBy("mLock")
removeUserFromAllowlistsLocked(int userId)195     private void removeUserFromAllowlistsLocked(int userId) {
196         for (int i = mDisplayToProfileUserMapping.size() - 1; i >= 0; i--) {
197             if (mDisplayToProfileUserMapping.valueAt(i) == userId) {
198                 mDisplayToProfileUserMapping.removeAt(i);
199             }
200         }
201         mDefaultDisplayForProfileUser.delete(userId);
202     }
203 
204     /**
205      * Sets display allowlist for the {@code userId}. For passenger user, activity will be always
206      * launched to a display in the allowlist. If requested display is not in the allowlist, the 1st
207      * display in the allowlist will be selected as target display.
208      *
209      * <p>The allowlist is kept only for profile user. Assigning the current user unassigns users
210      * for the given displays.
211      */
setDisplayAllowListForUser(@serIdInt int userId, int[] displayIds)212     public void setDisplayAllowListForUser(@UserIdInt int userId, int[] displayIds) {
213         if (DBG) {
214             Slogf.d(TAG, "setDisplayAllowlistForUser userId:%d displays:%s",
215                     userId, Arrays.toString(displayIds));
216         }
217         synchronized (mLock) {
218             for (int displayId : displayIds) {
219                 if (!mPassengerDisplays.contains(displayId)) {
220                     Slogf.w(TAG, "setDisplayAllowlistForUser called with display:%d"
221                             + " not in passenger display list:%s", displayId, mPassengerDisplays);
222                     continue;
223                 }
224                 if (userId == mDriverUser) {
225                     mDisplayToProfileUserMapping.delete(displayId);
226                 } else {
227                     mDisplayToProfileUserMapping.put(displayId, userId);
228                 }
229                 // now the display cannot be a default display for other user
230                 int i = mDefaultDisplayForProfileUser.indexOfValue(displayId);
231                 if (i >= 0) {
232                     mDefaultDisplayForProfileUser.removeAt(i);
233                 }
234             }
235             if (displayIds.length > 0) {
236                 mDefaultDisplayForProfileUser.put(userId, displayIds[0]);
237             } else {
238                 removeUserFromAllowlistsLocked(userId);
239             }
240         }
241     }
242 
243     /**
244      * Sets displays assigned to passenger. All other displays will be treated as assigned to
245      * driver.
246      *
247      * <p>The 1st display in the array will be considered as a default display to assign
248      * for any non-driver user if there is no display assigned for the user. </p>
249      */
setPassengerDisplays(int[] displayIdsForPassenger)250     public void setPassengerDisplays(int[] displayIdsForPassenger) {
251         if (DBG) {
252             Slogf.d(TAG, "setPassengerDisplays displays:%s",
253                     Arrays.toString(displayIdsForPassenger));
254         }
255         synchronized (mLock) {
256             for (int id : displayIdsForPassenger) {
257                 mPassengerDisplays.remove(Integer.valueOf(id));
258             }
259             // handle removed displays
260             for (int i = 0; i < mPassengerDisplays.size(); i++) {
261                 int displayId = mPassengerDisplays.get(i);
262                 updateProfileUserConfigForDisplayRemovalLocked(displayId);
263             }
264             mPassengerDisplays.clear();
265             mPassengerDisplays.ensureCapacity(displayIdsForPassenger.length);
266             for (int id : displayIdsForPassenger) {
267                 mPassengerDisplays.add(id);
268             }
269         }
270     }
271 
272     /**
273      * Calculates {@code outParams} based on the given arguments.
274      * See {@code LaunchParamsController.LaunchParamsModifier.onCalculate()} for the detail.
275      */
calculate(CalculateParams params)276     public int calculate(CalculateParams params) {
277         Trace.beginSection("CarLaunchParamsModifier-calculate");
278         TaskWrapper task = params.getTask();
279         ActivityRecordWrapper activity = params.getActivity();
280         ActivityRecordWrapper source = params.getSource();
281         ActivityOptionsWrapper options = params.getOptions();
282         RequestWrapper request = params.getRequest();
283         LaunchParamsWrapper currentParams = params.getCurrentParams();
284         LaunchParamsWrapper outParams = params.getOutParams();
285 
286         int userId;
287         if (task != null) {
288             userId = task.getUserId();
289         } else if (activity != null) {
290             userId = activity.getUserId();
291         } else {
292             Slogf.w(TAG, "onCalculate, cannot decide user");
293             Trace.endSection();
294             return LaunchParamsWrapper.RESULT_SKIP;
295         }
296         // DisplayArea where user wants to launch the Activity.
297         TaskDisplayAreaWrapper originalDisplayArea = currentParams.getPreferredTaskDisplayArea();
298         // DisplayArea where CarLaunchParamsModifier targets to launch the Activity.
299         TaskDisplayAreaWrapper targetDisplayArea = null;
300         ComponentName activityName = null;
301         if (activity != null) {
302             activityName = activity.getComponentName();
303         }
304         if (DBG) {
305             Slogf.d(TAG, "onCalculate, userId:%d original displayArea:%s actvity:%s options:%s",
306                     userId, originalDisplayArea, activityName, options);
307         }
308         decision:
309         synchronized (mLock) {
310             // If originalDisplayArea is set, respect that before ActivityOptions check.
311             if (originalDisplayArea == null && options != null) {
312                 originalDisplayArea = options.getLaunchTaskDisplayArea();
313                 if (originalDisplayArea == null) {
314                     // If task display area is not specified in options - try launch display id
315                     originalDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay(
316                             options.getOptions().getLaunchDisplayId());
317                 }
318             }
319             if (originalDisplayArea == null && source != null) {
320                 // try the display area of the source
321                 TaskDisplayAreaWrapper sourceDisplayArea = source.getDisplayArea();
322                 int sourceDisplayId = sourceDisplayArea == null
323                         ? Display.INVALID_DISPLAY : sourceDisplayArea.getDisplay().getDisplayId();
324                 if (userId == getUserForDisplayLocked(sourceDisplayId)) {
325                     originalDisplayArea = sourceDisplayArea;
326                 }
327             }
328             if (originalDisplayArea == null && options != null) {
329                 // try the caller display id
330                 int callerDisplayId = options.getCallerDisplayId();
331                 if (userId == getUserForDisplayLocked(callerDisplayId)) {
332                     originalDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay(
333                             callerDisplayId);
334                 }
335             }
336             if (mPersistentActivities.containsKey(activityName)) {
337                 targetDisplayArea = mPersistentActivities.get(activityName);
338             } else if (originalDisplayArea == null
339                     && task == null  // launching as a new task
340                     && source != null && !source.isDisplayTrusted()
341                     && !source.allowingEmbedded()) {
342                 if (DBG) {
343                     Slogf.d(TAG, "Disallow launch on virtual display for not-embedded activity.");
344                 }
345                 targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay(
346                         Display.DEFAULT_DISPLAY);
347             }
348             if (userId == mDriverUser) {
349                 // Respect the existing DisplayArea.
350                 if (DBG) Slogf.d(TAG, "Skip the further check for Driver");
351                 break decision;
352             }
353             if (userId == UserManagerHelper.USER_SYSTEM) {
354                 // This will be only allowed if it has FLAG_SHOW_FOR_ALL_USERS.
355                 // The flag is not immediately accessible here so skip the check.
356                 // But other WM policy will enforce it.
357                 if (DBG) Slogf.d(TAG, "Skip the further check for SystemUser");
358                 break decision;
359             }
360             // Now user is a passenger.
361             if (mPassengerDisplays.isEmpty()) {
362                 // No displays for passengers. This could be old user and do not do anything.
363                 if (DBG) Slogf.d(TAG, "Skip the further check for no PassengerDisplays");
364                 break decision;
365             }
366             if (targetDisplayArea == null) {
367                 if (originalDisplayArea != null) {
368                     targetDisplayArea = originalDisplayArea;
369                 } else {
370                     targetDisplayArea = mBuiltin.getDefaultTaskDisplayAreaOnDisplay(
371                             Display.DEFAULT_DISPLAY);
372                 }
373             }
374             Display display = targetDisplayArea.getDisplay();
375             if ((display.getFlags() & Display.FLAG_PRIVATE) != 0) {
376                 // private display should follow its own restriction rule.
377                 if (DBG) Slogf.d(TAG, "Skip the further check for the private display");
378                 break decision;
379             }
380             if (DisplayHelper.getType(display) == DisplayHelper.TYPE_VIRTUAL) {
381                 // TODO(b/132903422) : We need to update this after the bug is resolved.
382                 // For now, don't change anything.
383                 if (DBG) Slogf.d(TAG, "Skip the further check for the virtual display");
384                 break decision;
385             }
386             int userForDisplay = getUserForDisplayLocked(display.getDisplayId());
387             if (userForDisplay == userId) {
388                 if (DBG) Slogf.d(TAG, "The display is assigned for the user");
389                 break decision;
390             }
391             targetDisplayArea = getAlternativeDisplayAreaForPassengerLocked(
392                     userId, activity, request);
393         }
394         if (targetDisplayArea != null && originalDisplayArea != targetDisplayArea) {
395             Slogf.i(TAG, "Changed launching display, user:%d requested display area:%s"
396                     + " target display area:%s", userId, originalDisplayArea, targetDisplayArea);
397             outParams.setPreferredTaskDisplayArea(targetDisplayArea);
398             if (options != null
399                     && options.getLaunchWindowingMode()
400                     != ActivityOptionsWrapper.WINDOWING_MODE_UNDEFINED) {
401                 outParams.setWindowingMode(options.getLaunchWindowingMode());
402             }
403             if (needsSafeRegionBounds(activity)) {
404                 outParams.setNeedsSafeRegionBounds(true);
405             }
406             Trace.endSection();
407             return LaunchParamsWrapper.RESULT_DONE;
408         } else if (needsSafeRegionBounds(activity)) {
409             outParams.setNeedsSafeRegionBounds(true);
410             Trace.endSection();
411             return LaunchParamsWrapper.RESULT_CONTINUE;
412         } else {
413             Trace.endSection();
414             return LaunchParamsWrapper.RESULT_SKIP;
415         }
416     }
417 
needsSafeRegionBounds(ActivityRecordWrapper activity)418     private boolean needsSafeRegionBounds(ActivityRecordWrapper activity) {
419         if (activity != null && activity.getComponentName() != null
420                 && requiresDisplayCompat(activity.getComponentName(), activity.getUserId())) {
421             if (DBG) {
422                 Slogf.d(TAG, "Activity:%s needs to be within a safe region",
423                         activity.getComponentName());
424             }
425             return true;
426         }
427         return false;
428     }
429 
430     @GuardedBy("mLock")
getUserForDisplayLocked(int displayId)431     private int getUserForDisplayLocked(int displayId) {
432         int userForDisplay = mDisplayToProfileUserMapping.get(displayId,
433                 UserManagerHelper.USER_NULL);
434         if (userForDisplay != UserManagerHelper.USER_NULL) {
435             return userForDisplay;
436         }
437         return mBuiltin.getUserAssignedToDisplay(displayId);
438     }
439 
440     @GuardedBy("mLock")
441     @Nullable
getAlternativeDisplayAreaForPassengerLocked(int userId, @Nullable ActivityRecordWrapper activityRecord, @Nullable RequestWrapper request)442     private TaskDisplayAreaWrapper getAlternativeDisplayAreaForPassengerLocked(int userId,
443             @Nullable ActivityRecordWrapper activityRecord, @Nullable RequestWrapper request) {
444         if (DBG) Slogf.d(TAG, "getAlternativeDisplayAreaForPassengerLocked:%d", userId);
445         List<TaskDisplayAreaWrapper> fallbacks = mBuiltin.getFallbackDisplayAreasForActivity(
446                 activityRecord, request);
447         for (int i = 0, size = fallbacks.size(); i < size; ++i) {
448             TaskDisplayAreaWrapper fallbackTda = fallbacks.get(i);
449             int userForDisplay = getUserForDisplayLocked(fallbackTda.getDisplay().getDisplayId());
450             if (userForDisplay == userId) {
451                 return fallbackTda;
452             }
453         }
454         return fallbackDisplayAreaForUserLocked(userId);
455     }
456 
457     /**
458      * Return a {@link TaskDisplayAreaWrapper} that can be used if a source display area is
459      * not found. First check the default display for the user. If it is absent select
460      * the first passenger display if present.  If both are absent return {@code null}
461      *
462      * @param userId ID of the active user
463      * @return {@link TaskDisplayAreaWrapper} that is recommended when a display area is
464      *     not specified
465      */
466     @GuardedBy("mLock")
467     @Nullable
fallbackDisplayAreaForUserLocked(@serIdInt int userId)468     private TaskDisplayAreaWrapper fallbackDisplayAreaForUserLocked(@UserIdInt int userId) {
469         int displayIdForUserProfile = mDefaultDisplayForProfileUser.get(userId,
470                 Display.INVALID_DISPLAY);
471         if (displayIdForUserProfile != Display.INVALID_DISPLAY) {
472             int displayId = mDefaultDisplayForProfileUser.get(userId);
473             return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
474         }
475         int displayId = mBuiltin.getMainDisplayAssignedToUser(userId);
476         if (displayId != Display.INVALID_DISPLAY) {
477             return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
478         }
479 
480         if (!mPassengerDisplays.isEmpty()) {
481             displayId = mPassengerDisplays.get(0);
482             if (DBG) {
483                 Slogf.d(TAG, "fallbackDisplayAreaForUserLocked: userId=%d, displayId=%d",
484                         userId, displayId);
485             }
486             return mBuiltin.getDefaultTaskDisplayAreaOnDisplay(displayId);
487         }
488         return null;
489     }
490 
491     /**
492      * See {@link CarActivityManager#setPersistentActivity(android.content.ComponentName,int, int)}
493      */
setPersistentActivity(ComponentName activity, int displayId, int featureId)494     public int setPersistentActivity(ComponentName activity, int displayId, int featureId) {
495         try {
496             Trace.beginSection(
497                     "CarLaunchParamsModifier-setPersistentActivityOnDisplay: " + displayId);
498             if (DBG) {
499                 Slogf.d(TAG, "setPersistentActivity: activity=%s, displayId=%d, featureId=%d",
500                         activity, displayId, featureId);
501             }
502             if (featureId == DisplayAreaOrganizerHelper.FEATURE_UNDEFINED) {
503                 synchronized (mLock) {
504                     TaskDisplayAreaWrapper removed = mPersistentActivities.remove(activity);
505                     if (removed == null) {
506                         throw new ServiceSpecificException(
507                                 CarActivityManager.ERROR_CODE_ACTIVITY_NOT_FOUND,
508                                 "Failed to remove " + activity.toShortString());
509                     }
510                     return CarActivityManager.RESULT_SUCCESS;
511                 }
512             }
513             TaskDisplayAreaWrapper tda = mBuiltin.findTaskDisplayArea(displayId, featureId);
514             if (tda == null) {
515                 throw new IllegalArgumentException(
516                         "Unknown display=" + displayId + " or feature=" + featureId);
517             }
518             synchronized (mLock) {
519                 mPersistentActivities.put(activity, tda);
520             }
521             return CarActivityManager.RESULT_SUCCESS;
522         } finally {
523             Trace.endSection();
524         }
525     }
526 
527     /**
528      * Dump {code CarLaunchParamsModifierUpdatableImpl#mPersistentActivities}
529      */
dump(IndentingPrintWriter writer)530     public void dump(IndentingPrintWriter writer) {
531         writer.println(TAG);
532         writer.increaseIndent();
533         writer.println("Persistent Activities:");
534         writer.increaseIndent();
535         synchronized (mLock) {
536             if (mPersistentActivities.size() == 0) {
537                 writer.println("No activity persisted on a task display area");
538             } else {
539                 for (int i = 0; i < mPersistentActivities.size(); i++) {
540                     TaskDisplayAreaWrapper taskDisplayAreaWrapper =
541                             mPersistentActivities.valueAt(i);
542                     writer.println(
543                             "Activity name: " + mPersistentActivities.keyAt(i) + " - Display ID: "
544                                     + taskDisplayAreaWrapper.getDisplay().getDisplayId()
545                                     + " , Feature ID: " + taskDisplayAreaWrapper.getFeatureId());
546                 }
547             }
548         }
549         writer.decreaseIndent();
550         writer.decreaseIndent();
551     }
552 }
553