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 17 package com.android.server.wm; 18 19 import static android.app.ActivityManager.START_SUCCESS; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 22 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 23 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; 24 25 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; 26 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; 27 28 import android.app.ActivityOptions; 29 import android.app.IApplicationThread; 30 import android.content.ComponentName; 31 import android.content.ContentResolver; 32 import android.content.Intent; 33 import android.content.pm.ActivityInfo; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ResolveInfo; 37 import android.os.Binder; 38 import android.os.Handler; 39 import android.os.IBinder; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.UserHandle; 43 import android.provider.Settings; 44 import android.util.Slog; 45 import android.util.proto.ProtoOutputStream; 46 import android.view.RemoteAnimationAdapter; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.util.ArrayUtils; 50 import com.android.server.am.ActivityManagerService; 51 import com.android.server.am.PendingIntentRecord; 52 import com.android.server.wm.ActivityStackSupervisor.PendingActivityLaunch; 53 import com.android.server.wm.ActivityStarter.DefaultFactory; 54 import com.android.server.wm.ActivityStarter.Factory; 55 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.List; 59 60 /** 61 * Controller for delegating activity launches. 62 * 63 * This class' main objective is to take external activity start requests and prepare them into 64 * a series of discrete activity launches that can be handled by an {@link ActivityStarter}. It is 65 * also responsible for handling logic that happens around an activity launch, but doesn't 66 * necessarily influence the activity start. Examples include power hint management, processing 67 * through the pending activity list, and recording home activity launches. 68 */ 69 public class ActivityStartController { 70 private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStartController" : TAG_ATM; 71 72 private static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 1; 73 74 private final ActivityTaskManagerService mService; 75 private final ActivityStackSupervisor mSupervisor; 76 77 /** Last home activity record we attempted to start. */ 78 private ActivityRecord mLastHomeActivityStartRecord; 79 80 /** Temporary array to capture start activity results */ 81 private ActivityRecord[] tmpOutRecord = new ActivityRecord[1]; 82 83 /** The result of the last home activity we attempted to start. */ 84 private int mLastHomeActivityStartResult; 85 86 /** A list of activities that are waiting to launch. */ 87 private final ArrayList<ActivityStackSupervisor.PendingActivityLaunch> 88 mPendingActivityLaunches = new ArrayList<>(); 89 90 private final Factory mFactory; 91 92 private final Handler mHandler; 93 94 private final PendingRemoteAnimationRegistry mPendingRemoteAnimationRegistry; 95 96 boolean mCheckedForSetup = false; 97 98 private final class StartHandler extends Handler { StartHandler(Looper looper)99 public StartHandler(Looper looper) { 100 super(looper, null, true); 101 } 102 103 @Override handleMessage(Message msg)104 public void handleMessage(Message msg) { 105 switch(msg.what) { 106 case DO_PENDING_ACTIVITY_LAUNCHES_MSG: 107 synchronized (mService.mGlobalLock) { 108 doPendingActivityLaunches(true); 109 } 110 break; 111 } 112 } 113 } 114 115 /** 116 * TODO(b/64750076): Capture information necessary for dump and 117 * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object 118 * around 119 */ 120 private ActivityStarter mLastStarter; 121 ActivityStartController(ActivityTaskManagerService service)122 ActivityStartController(ActivityTaskManagerService service) { 123 this(service, service.mStackSupervisor, 124 new DefaultFactory(service, service.mStackSupervisor, 125 new ActivityStartInterceptor(service, service.mStackSupervisor))); 126 } 127 128 @VisibleForTesting ActivityStartController(ActivityTaskManagerService service, ActivityStackSupervisor supervisor, Factory factory)129 ActivityStartController(ActivityTaskManagerService service, ActivityStackSupervisor supervisor, 130 Factory factory) { 131 mService = service; 132 mSupervisor = supervisor; 133 mHandler = new StartHandler(mService.mH.getLooper()); 134 mFactory = factory; 135 mFactory.setController(this); 136 mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service, 137 service.mH); 138 } 139 140 /** 141 * @return A starter to configure and execute starting an activity. It is valid until after 142 * {@link ActivityStarter#execute} is invoked. At that point, the starter should be 143 * considered invalid and no longer modified or used. 144 */ obtainStarter(Intent intent, String reason)145 ActivityStarter obtainStarter(Intent intent, String reason) { 146 return mFactory.obtain().setIntent(intent).setReason(reason); 147 } 148 onExecutionComplete(ActivityStarter starter)149 void onExecutionComplete(ActivityStarter starter) { 150 if (mLastStarter == null) { 151 mLastStarter = mFactory.obtain(); 152 } 153 154 mLastStarter.set(starter); 155 mFactory.recycle(starter); 156 } 157 158 /** 159 * TODO(b/64750076): usage of this doesn't seem right. We're making decisions based off the 160 * last starter for an arbitrary task record. Re-evaluate whether we can remove. 161 */ postStartActivityProcessingForLastStarter(ActivityRecord r, int result, ActivityStack targetStack)162 void postStartActivityProcessingForLastStarter(ActivityRecord r, int result, 163 ActivityStack targetStack) { 164 if (mLastStarter == null) { 165 return; 166 } 167 168 mLastStarter.postStartActivityProcessing(r, result, targetStack); 169 } 170 startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId)171 void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) { 172 final ActivityOptions options = ActivityOptions.makeBasic(); 173 options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); 174 if (!ActivityRecord.isResolverActivity(aInfo.name)) { 175 // The resolver activity shouldn't be put in home stack because when the foreground is 176 // standard type activity, the resolver activity should be put on the top of current 177 // foreground instead of bring home stack to front. 178 options.setLaunchActivityType(ACTIVITY_TYPE_HOME); 179 } 180 options.setLaunchDisplayId(displayId); 181 mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) 182 .setOutActivity(tmpOutRecord) 183 .setCallingUid(0) 184 .setActivityInfo(aInfo) 185 .setActivityOptions(options.toBundle()) 186 .execute(); 187 mLastHomeActivityStartRecord = tmpOutRecord[0]; 188 final ActivityDisplay display = 189 mService.mRootActivityContainer.getActivityDisplay(displayId); 190 final ActivityStack homeStack = display != null ? display.getHomeStack() : null; 191 if (homeStack != null && homeStack.mInResumeTopActivity) { 192 // If we are in resume section already, home activity will be initialized, but not 193 // resumed (to avoid recursive resume) and will stay that way until something pokes it 194 // again. We need to schedule another resume. 195 mSupervisor.scheduleResumeTopActivities(); 196 } 197 } 198 199 /** 200 * Starts the "new version setup screen" if appropriate. 201 */ startSetupActivity()202 void startSetupActivity() { 203 // Only do this once per boot. 204 if (mCheckedForSetup) { 205 return; 206 } 207 208 // We will show this screen if the current one is a different 209 // version than the last one shown, and we are not running in 210 // low-level factory test mode. 211 final ContentResolver resolver = mService.mContext.getContentResolver(); 212 if (mService.mFactoryTest != FACTORY_TEST_LOW_LEVEL 213 && Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0) { 214 mCheckedForSetup = true; 215 216 // See if we should be showing the platform update setup UI. 217 final Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP); 218 final List<ResolveInfo> ris = 219 mService.mContext.getPackageManager().queryIntentActivities(intent, 220 PackageManager.MATCH_SYSTEM_ONLY | PackageManager.GET_META_DATA 221 | ActivityManagerService.STOCK_PM_FLAGS); 222 if (!ris.isEmpty()) { 223 final ResolveInfo ri = ris.get(0); 224 String vers = ri.activityInfo.metaData != null 225 ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION) 226 : null; 227 if (vers == null && ri.activityInfo.applicationInfo.metaData != null) { 228 vers = ri.activityInfo.applicationInfo.metaData.getString( 229 Intent.METADATA_SETUP_VERSION); 230 } 231 String lastVers = Settings.Secure.getString( 232 resolver, Settings.Secure.LAST_SETUP_SHOWN); 233 if (vers != null && !vers.equals(lastVers)) { 234 intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 235 intent.setComponent(new ComponentName( 236 ri.activityInfo.packageName, ri.activityInfo.name)); 237 obtainStarter(intent, "startSetupActivity") 238 .setCallingUid(0) 239 .setActivityInfo(ri.activityInfo) 240 .execute(); 241 } 242 } 243 } 244 } 245 246 /** 247 * If {@code validateIncomingUser} is true, check {@code targetUserId} against the real calling 248 * user ID inferred from {@code realCallingUid}, then return the resolved user-id, taking into 249 * account "current user", etc. 250 * 251 * If {@code validateIncomingUser} is false, it skips the above check, but instead 252 * ensures {@code targetUserId} is a real user ID and not a special user ID such as 253 * {@link android.os.UserHandle#USER_ALL}, etc. 254 */ checkTargetUser(int targetUserId, boolean validateIncomingUser, int realCallingPid, int realCallingUid, String reason)255 int checkTargetUser(int targetUserId, boolean validateIncomingUser, 256 int realCallingPid, int realCallingUid, String reason) { 257 if (validateIncomingUser) { 258 return mService.handleIncomingUser( 259 realCallingPid, realCallingUid, targetUserId, reason); 260 } else { 261 mService.mAmInternal.ensureNotSpecialUser(targetUserId); 262 return targetUserId; 263 } 264 } 265 startActivityInPackage(int uid, int realCallingPid, int realCallingUid, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, SafeActivityOptions options, int userId, TaskRecord inTask, String reason, boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart)266 final int startActivityInPackage(int uid, int realCallingPid, int realCallingUid, 267 String callingPackage, Intent intent, String resolvedType, IBinder resultTo, 268 String resultWho, int requestCode, int startFlags, SafeActivityOptions options, 269 int userId, TaskRecord inTask, String reason, boolean validateIncomingUser, 270 PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart) { 271 272 userId = checkTargetUser(userId, validateIncomingUser, realCallingPid, realCallingUid, 273 reason); 274 275 // TODO: Switch to user app stacks here. 276 return obtainStarter(intent, reason) 277 .setCallingUid(uid) 278 .setRealCallingPid(realCallingPid) 279 .setRealCallingUid(realCallingUid) 280 .setCallingPackage(callingPackage) 281 .setResolvedType(resolvedType) 282 .setResultTo(resultTo) 283 .setResultWho(resultWho) 284 .setRequestCode(requestCode) 285 .setStartFlags(startFlags) 286 .setActivityOptions(options) 287 .setMayWait(userId) 288 .setInTask(inTask) 289 .setOriginatingPendingIntent(originatingPendingIntent) 290 .setAllowBackgroundActivityStart(allowBackgroundActivityStart) 291 .execute(); 292 } 293 294 /** 295 * Start intents as a package. 296 * 297 * @param uid Make a call as if this UID did. 298 * @param callingPackage Make a call as if this package did. 299 * @param intents Intents to start. 300 * @param userId Start the intents on this user. 301 * @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID. 302 * @param originatingPendingIntent PendingIntentRecord that originated this activity start or 303 * null if not originated by PendingIntent 304 */ startActivitiesInPackage(int uid, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart)305 final int startActivitiesInPackage(int uid, String callingPackage, Intent[] intents, 306 String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, 307 boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, 308 boolean allowBackgroundActivityStart) { 309 return startActivitiesInPackage(uid, 0 /* realCallingPid */, -1 /* realCallingUid */, 310 callingPackage, intents, resolvedTypes, resultTo, options, userId, validateIncomingUser, 311 originatingPendingIntent, allowBackgroundActivityStart); 312 } 313 314 /** 315 * Start intents as a package. 316 * 317 * @param uid Make a call as if this UID did. 318 * @param realCallingPid PID of the real caller. 319 * @param realCallingUid UID of the real caller. 320 * @param callingPackage Make a call as if this package did. 321 * @param intents Intents to start. 322 * @param userId Start the intents on this user. 323 * @param validateIncomingUser Set true to skip checking {@code userId} with the calling UID. 324 * @param originatingPendingIntent PendingIntentRecord that originated this activity start or 325 * null if not originated by PendingIntent 326 */ startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart)327 final int startActivitiesInPackage(int uid, int realCallingPid, int realCallingUid, 328 String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, 329 SafeActivityOptions options, int userId, boolean validateIncomingUser, 330 PendingIntentRecord originatingPendingIntent, 331 boolean allowBackgroundActivityStart) { 332 333 final String reason = "startActivityInPackage"; 334 335 userId = checkTargetUser(userId, validateIncomingUser, Binder.getCallingPid(), 336 Binder.getCallingUid(), reason); 337 338 // TODO: Switch to user app stacks here. 339 return startActivities(null, uid, realCallingPid, realCallingUid, callingPackage, intents, 340 resolvedTypes, resultTo, options, userId, reason, originatingPendingIntent, 341 allowBackgroundActivityStart); 342 } 343 startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid, int incomingRealCallingUid, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, int userId, String reason, PendingIntentRecord originatingPendingIntent, boolean allowBackgroundActivityStart)344 int startActivities(IApplicationThread caller, int callingUid, int incomingRealCallingPid, 345 int incomingRealCallingUid, String callingPackage, Intent[] intents, 346 String[] resolvedTypes, IBinder resultTo, SafeActivityOptions options, 347 int userId, String reason, PendingIntentRecord originatingPendingIntent, 348 boolean allowBackgroundActivityStart) { 349 if (intents == null) { 350 throw new NullPointerException("intents is null"); 351 } 352 if (resolvedTypes == null) { 353 throw new NullPointerException("resolvedTypes is null"); 354 } 355 if (intents.length != resolvedTypes.length) { 356 throw new IllegalArgumentException("intents are length different than resolvedTypes"); 357 } 358 359 final int realCallingPid = incomingRealCallingPid != 0 360 ? incomingRealCallingPid 361 : Binder.getCallingPid(); 362 final int realCallingUid = incomingRealCallingUid != -1 363 ? incomingRealCallingUid 364 : Binder.getCallingUid(); 365 366 int callingPid; 367 if (callingUid >= 0) { 368 callingPid = -1; 369 } else if (caller == null) { 370 callingPid = realCallingPid; 371 callingUid = realCallingUid; 372 } else { 373 callingPid = callingUid = -1; 374 } 375 final long origId = Binder.clearCallingIdentity(); 376 try { 377 intents = ArrayUtils.filterNotNull(intents, Intent[]::new); 378 final ActivityStarter[] starters = new ActivityStarter[intents.length]; 379 // Do not hold WM global lock on this loop because when resolving intent, it may 380 // potentially acquire activity manager lock that leads to deadlock. 381 for (int i = 0; i < intents.length; i++) { 382 Intent intent = intents[i]; 383 384 // Refuse possible leaked file descriptors. 385 if (intent.hasFileDescriptors()) { 386 throw new IllegalArgumentException("File descriptors passed in Intent"); 387 } 388 389 // Don't modify the client's object! 390 intent = new Intent(intent); 391 392 // Collect information about the target of the Intent. 393 ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i], 394 0 /* startFlags */, null /* profilerInfo */, userId, 395 ActivityStarter.computeResolveFilterUid( 396 callingUid, realCallingUid, UserHandle.USER_NULL)); 397 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId); 398 399 if (aInfo != null && (aInfo.applicationInfo.privateFlags 400 & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { 401 throw new IllegalArgumentException("FLAG_CANT_SAVE_STATE not supported here"); 402 } 403 404 final boolean top = i == intents.length - 1; 405 final SafeActivityOptions checkedOptions = top 406 ? options 407 : null; 408 starters[i] = obtainStarter(intent, reason) 409 .setCaller(caller) 410 .setResolvedType(resolvedTypes[i]) 411 .setActivityInfo(aInfo) 412 .setResultTo(resultTo) 413 .setRequestCode(-1) 414 .setCallingPid(callingPid) 415 .setCallingUid(callingUid) 416 .setCallingPackage(callingPackage) 417 .setRealCallingPid(realCallingPid) 418 .setRealCallingUid(realCallingUid) 419 .setActivityOptions(checkedOptions) 420 .setComponentSpecified(intent.getComponent() != null) 421 422 // Top activity decides on animation being run, so we allow only for the 423 // top one as otherwise an activity below might consume it. 424 .setAllowPendingRemoteAnimationRegistryLookup(top /* allowLookup*/) 425 .setOriginatingPendingIntent(originatingPendingIntent) 426 .setAllowBackgroundActivityStart(allowBackgroundActivityStart); 427 } 428 429 final ActivityRecord[] outActivity = new ActivityRecord[1]; 430 // Lock the loop to ensure the activities launched in a sequence. 431 synchronized (mService.mGlobalLock) { 432 for (int i = 0; i < starters.length; i++) { 433 final int startResult = starters[i].setOutActivity(outActivity).execute(); 434 if (startResult < START_SUCCESS) { 435 // Abort by error result and recycle unused starters. 436 for (int j = i + 1; j < starters.length; j++) { 437 mFactory.recycle(starters[j]); 438 } 439 return startResult; 440 } 441 resultTo = outActivity[0] != null ? outActivity[0].appToken : null; 442 } 443 } 444 } finally { 445 Binder.restoreCallingIdentity(origId); 446 } 447 448 return START_SUCCESS; 449 } 450 schedulePendingActivityLaunches(long delayMs)451 void schedulePendingActivityLaunches(long delayMs) { 452 mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG); 453 Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG); 454 mHandler.sendMessageDelayed(msg, delayMs); 455 } 456 doPendingActivityLaunches(boolean doResume)457 void doPendingActivityLaunches(boolean doResume) { 458 while (!mPendingActivityLaunches.isEmpty()) { 459 final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); 460 final boolean resume = doResume && mPendingActivityLaunches.isEmpty(); 461 final ActivityStarter starter = obtainStarter(null /* intent */, 462 "pendingActivityLaunch"); 463 try { 464 starter.startResolvedActivity(pal.r, pal.sourceRecord, null, null, pal.startFlags, 465 resume, pal.r.pendingOptions, null); 466 } catch (Exception e) { 467 Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); 468 pal.sendErrorResult(e.getMessage()); 469 } 470 } 471 } 472 addPendingActivityLaunch(PendingActivityLaunch launch)473 void addPendingActivityLaunch(PendingActivityLaunch launch) { 474 mPendingActivityLaunches.add(launch); 475 } 476 clearPendingActivityLaunches(String packageName)477 boolean clearPendingActivityLaunches(String packageName) { 478 final int pendingLaunches = mPendingActivityLaunches.size(); 479 480 for (int palNdx = pendingLaunches - 1; palNdx >= 0; --palNdx) { 481 final PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx); 482 final ActivityRecord r = pal.r; 483 if (r != null && r.packageName.equals(packageName)) { 484 mPendingActivityLaunches.remove(palNdx); 485 } 486 } 487 return mPendingActivityLaunches.size() < pendingLaunches; 488 } 489 registerRemoteAnimationForNextActivityStart(String packageName, RemoteAnimationAdapter adapter)490 void registerRemoteAnimationForNextActivityStart(String packageName, 491 RemoteAnimationAdapter adapter) { 492 mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter); 493 } 494 getPendingRemoteAnimationRegistry()495 PendingRemoteAnimationRegistry getPendingRemoteAnimationRegistry() { 496 return mPendingRemoteAnimationRegistry; 497 } 498 dump(PrintWriter pw, String prefix, String dumpPackage)499 void dump(PrintWriter pw, String prefix, String dumpPackage) { 500 pw.print(prefix); 501 pw.print("mLastHomeActivityStartResult="); 502 pw.println(mLastHomeActivityStartResult); 503 504 if (mLastHomeActivityStartRecord != null) { 505 pw.print(prefix); 506 pw.println("mLastHomeActivityStartRecord:"); 507 mLastHomeActivityStartRecord.dump(pw, prefix + " "); 508 } 509 510 final boolean dumpPackagePresent = dumpPackage != null; 511 512 if (mLastStarter != null) { 513 final boolean dump = !dumpPackagePresent 514 || mLastStarter.relatedToPackage(dumpPackage) 515 || (mLastHomeActivityStartRecord != null 516 && dumpPackage.equals(mLastHomeActivityStartRecord.packageName)); 517 518 if (dump) { 519 pw.print(prefix); 520 mLastStarter.dump(pw, prefix + " "); 521 522 if (dumpPackagePresent) { 523 return; 524 } 525 } 526 } 527 528 if (dumpPackagePresent) { 529 pw.print(prefix); 530 pw.println("(nothing)"); 531 } 532 } 533 writeToProto(ProtoOutputStream proto, long fieldId)534 public void writeToProto(ProtoOutputStream proto, long fieldId) { 535 for (PendingActivityLaunch activity: mPendingActivityLaunches) { 536 activity.r.writeIdentifierToProto(proto, fieldId); 537 } 538 } 539 } 540