1 /* 2 * Copyright (C) 2006 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 android.content; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.os.Build; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 import android.util.proto.ProtoOutputStream; 27 28 import java.io.PrintWriter; 29 30 /** 31 * Identifier for a specific application component 32 * ({@link android.app.Activity}, {@link android.app.Service}, 33 * {@link android.content.BroadcastReceiver}, or 34 * {@link android.content.ContentProvider}) that is available. Two 35 * pieces of information, encapsulated here, are required to identify 36 * a component: the package (a String) it exists in, and the class (a String) 37 * name inside of that package. 38 * 39 */ 40 @android.ravenwood.annotation.RavenwoodKeepWholeClass 41 public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> { 42 private final String mPackage; 43 private final String mClass; 44 45 /** 46 * Create a new component identifier where the class name may be specified 47 * as either absolute or relative to the containing package. 48 * 49 * <p>Relative package names begin with a <code>'.'</code> character. For a package 50 * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method 51 * will return a ComponentName with the package <code>"com.example"</code>and class name 52 * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also 53 * permitted.</p> 54 * 55 * @param pkg the name of the package the component exists in 56 * @param cls the name of the class inside of <var>pkg</var> that implements 57 * the component 58 * @return the new ComponentName 59 */ createRelative(@onNull String pkg, @NonNull String cls)60 public static @NonNull ComponentName createRelative(@NonNull String pkg, @NonNull String cls) { 61 if (TextUtils.isEmpty(cls)) { 62 throw new IllegalArgumentException("class name cannot be empty"); 63 } 64 65 final String fullName; 66 if (cls.charAt(0) == '.') { 67 // Relative to the package. Prepend the package name. 68 fullName = pkg + cls; 69 } else { 70 // Fully qualified package name. 71 fullName = cls; 72 } 73 return new ComponentName(pkg, fullName); 74 } 75 76 /** 77 * Create a new component identifier where the class name may be specified 78 * as either absolute or relative to the containing package. 79 * 80 * <p>Relative package names begin with a <code>'.'</code> character. For a package 81 * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method 82 * will return a ComponentName with the package <code>"com.example"</code>and class name 83 * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also 84 * permitted.</p> 85 * 86 * @param pkg a Context for the package implementing the component 87 * @param cls the name of the class inside of <var>pkg</var> that implements 88 * the component 89 * @return the new ComponentName 90 */ createRelative(@onNull Context pkg, @NonNull String cls)91 public static @NonNull ComponentName createRelative(@NonNull Context pkg, @NonNull String cls) { 92 return createRelative(pkg.getPackageName(), cls); 93 } 94 95 /** 96 * Create a new component identifier. 97 * 98 * @param pkg The name of the package that the component exists in. Can 99 * not be null. 100 * @param cls The name of the class inside of <var>pkg</var> that 101 * implements the component. Can not be null. 102 */ ComponentName(@onNull String pkg, @NonNull String cls)103 public ComponentName(@NonNull String pkg, @NonNull String cls) { 104 if (pkg == null) throw new NullPointerException("package name is null"); 105 if (cls == null) throw new NullPointerException("class name is null"); 106 mPackage = pkg; 107 mClass = cls; 108 } 109 110 /** 111 * Create a new component identifier from a Context and class name. 112 * 113 * @param pkg A Context for the package implementing the component, 114 * from which the actual package name will be retrieved. 115 * @param cls The name of the class inside of <var>pkg</var> that 116 * implements the component. 117 */ ComponentName(@onNull Context pkg, @NonNull String cls)118 public ComponentName(@NonNull Context pkg, @NonNull String cls) { 119 if (cls == null) throw new NullPointerException("class name is null"); 120 mPackage = pkg.getPackageName(); 121 mClass = cls; 122 } 123 124 /** 125 * Create a new component identifier from a Context and Class object. 126 * 127 * @param pkg A Context for the package implementing the component, from 128 * which the actual package name will be retrieved. 129 * @param cls The Class object of the desired component, from which the 130 * actual class name will be retrieved. 131 */ ComponentName(@onNull Context pkg, @NonNull Class<?> cls)132 public ComponentName(@NonNull Context pkg, @NonNull Class<?> cls) { 133 mPackage = pkg.getPackageName(); 134 mClass = cls.getName(); 135 } 136 clone()137 public ComponentName clone() { 138 return new ComponentName(mPackage, mClass); 139 } 140 141 /** 142 * Return the package name of this component. 143 */ getPackageName()144 public @NonNull String getPackageName() { 145 return mPackage; 146 } 147 148 /** 149 * Return the class name of this component. 150 */ getClassName()151 public @NonNull String getClassName() { 152 return mClass; 153 } 154 155 /** 156 * Return the class name, either fully qualified or in a shortened form 157 * (with a leading '.') if it is a suffix of the package. 158 */ getShortClassName()159 public String getShortClassName() { 160 if (mClass.startsWith(mPackage)) { 161 int PN = mPackage.length(); 162 int CN = mClass.length(); 163 if (CN > PN && mClass.charAt(PN) == '.') { 164 return mClass.substring(PN, CN); 165 } 166 } 167 return mClass; 168 } 169 appendShortClassName(StringBuilder sb, String packageName, String className)170 private static void appendShortClassName(StringBuilder sb, String packageName, 171 String className) { 172 if (className.startsWith(packageName)) { 173 int PN = packageName.length(); 174 int CN = className.length(); 175 if (CN > PN && className.charAt(PN) == '.') { 176 sb.append(className, PN, CN); 177 return; 178 } 179 } 180 sb.append(className); 181 } 182 printShortClassName(PrintWriter pw, String packageName, String className)183 private static void printShortClassName(PrintWriter pw, String packageName, 184 String className) { 185 if (className.startsWith(packageName)) { 186 int PN = packageName.length(); 187 int CN = className.length(); 188 if (CN > PN && className.charAt(PN) == '.') { 189 pw.write(className, PN, CN-PN); 190 return; 191 } 192 } 193 pw.print(className); 194 } 195 196 /** 197 * Helper to get {@link #flattenToShortString()} in a {@link ComponentName} reference that can 198 * be {@code null}. 199 * 200 * @hide 201 */ 202 @Nullable flattenToShortString(@ullable ComponentName componentName)203 public static String flattenToShortString(@Nullable ComponentName componentName) { 204 return componentName == null ? null : componentName.flattenToShortString(); 205 } 206 207 /** 208 * Return a String that unambiguously describes both the package and 209 * class names contained in the ComponentName. You can later recover 210 * the ComponentName from this string through 211 * {@link #unflattenFromString(String)}. 212 * 213 * @return Returns a new String holding the package and class names. This 214 * is represented as the package name, concatenated with a '/' and then the 215 * class name. 216 * 217 * @see #unflattenFromString(String) 218 */ flattenToString()219 public @NonNull String flattenToString() { 220 return mPackage + "/" + mClass; 221 } 222 223 /** 224 * The same as {@link #flattenToString()}, but abbreviates the class 225 * name if it is a suffix of the package. The result can still be used 226 * with {@link #unflattenFromString(String)}. 227 * 228 * @return Returns a new String holding the package and class names. This 229 * is represented as the package name, concatenated with a '/' and then the 230 * class name. 231 * 232 * @see #unflattenFromString(String) 233 */ flattenToShortString()234 public @NonNull String flattenToShortString() { 235 StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length()); 236 appendShortString(sb, mPackage, mClass); 237 return sb.toString(); 238 } 239 240 /** @hide */ appendShortString(StringBuilder sb)241 public void appendShortString(StringBuilder sb) { 242 appendShortString(sb, mPackage, mClass); 243 } 244 245 /** @hide */ 246 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) appendShortString(StringBuilder sb, String packageName, String className)247 public static void appendShortString(StringBuilder sb, String packageName, String className) { 248 sb.append(packageName).append('/'); 249 appendShortClassName(sb, packageName, className); 250 } 251 252 /** @hide */ 253 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) printShortString(PrintWriter pw, String packageName, String className)254 public static void printShortString(PrintWriter pw, String packageName, String className) { 255 pw.print(packageName); 256 pw.print('/'); 257 printShortClassName(pw, packageName, className); 258 } 259 260 /** 261 * Recover a ComponentName from a String that was previously created with 262 * {@link #flattenToString()}. It splits the string at the first '/', 263 * taking the part before as the package name and the part after as the 264 * class name. As a special convenience (to use, for example, when 265 * parsing component names on the command line), if the '/' is immediately 266 * followed by a '.' then the final class name will be the concatenation 267 * of the package name with the string following the '/'. Thus 268 * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah". 269 * 270 * @param str The String that was returned by flattenToString(). 271 * @return Returns a new ComponentName containing the package and class 272 * names that were encoded in <var>str</var> 273 * 274 * @see #flattenToString() 275 */ unflattenFromString(@onNull String str)276 public static @Nullable ComponentName unflattenFromString(@NonNull String str) { 277 int sep = str.indexOf('/'); 278 if (sep < 0 || (sep+1) >= str.length()) { 279 return null; 280 } 281 String pkg = str.substring(0, sep); 282 String cls = str.substring(sep+1); 283 if (cls.length() > 0 && cls.charAt(0) == '.') { 284 cls = pkg + cls; 285 } 286 return new ComponentName(pkg, cls); 287 } 288 289 /** 290 * Return string representation of this class without the class's name 291 * as a prefix. 292 */ toShortString()293 public String toShortString() { 294 return "{" + mPackage + "/" + mClass + "}"; 295 } 296 297 @Override toString()298 public String toString() { 299 return "ComponentInfo{" + mPackage + "/" + mClass + "}"; 300 } 301 302 /** Put this here so that individual services don't have to reimplement this. @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)303 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 304 final long token = proto.start(fieldId); 305 proto.write(ComponentNameProto.PACKAGE_NAME, mPackage); 306 proto.write(ComponentNameProto.CLASS_NAME, mClass); 307 proto.end(token); 308 } 309 310 /** 311 * {@inheritDoc} 312 * 313 * <p>Two components are considered to be equal if the packages in which they reside have the 314 * same name, and if the classes that implement each component also have the same name. 315 */ 316 @Override equals(@ullable Object obj)317 public boolean equals(@Nullable Object obj) { 318 if (obj instanceof ComponentName) { 319 ComponentName other = (ComponentName) obj; 320 // mPackage and mClass can never be null. 321 return mPackage.equals(other.mPackage) 322 && mClass.equals(other.mClass); 323 } else { 324 return false; 325 } 326 } 327 328 @Override hashCode()329 public int hashCode() { 330 return mPackage.hashCode() + mClass.hashCode(); 331 } 332 compareTo(ComponentName that)333 public int compareTo(ComponentName that) { 334 int v; 335 v = this.mPackage.compareTo(that.mPackage); 336 if (v != 0) { 337 return v; 338 } 339 return this.mClass.compareTo(that.mClass); 340 } 341 describeContents()342 public int describeContents() { 343 return 0; 344 } 345 writeToParcel(Parcel out, int flags)346 public void writeToParcel(Parcel out, int flags) { 347 // WARNING: If you modify this function, also update 348 // frameworks/base/libs/services/src/content/ComponentName.cpp. 349 out.writeString(mPackage); 350 out.writeString(mClass); 351 } 352 353 /** 354 * Write a ComponentName to a Parcel, handling null pointers. Must be 355 * read with {@link #readFromParcel(Parcel)}. 356 * 357 * @param c The ComponentName to be written. 358 * @param out The Parcel in which the ComponentName will be placed. 359 * 360 * @see #readFromParcel(Parcel) 361 */ writeToParcel(ComponentName c, Parcel out)362 public static void writeToParcel(ComponentName c, Parcel out) { 363 if (c != null) { 364 c.writeToParcel(out, 0); 365 } else { 366 out.writeString(null); 367 } 368 } 369 370 /** 371 * Read a ComponentName from a Parcel that was previously written 372 * with {@link #writeToParcel(ComponentName, Parcel)}, returning either 373 * a null or new object as appropriate. 374 * 375 * @param in The Parcel from which to read the ComponentName 376 * @return Returns a new ComponentName matching the previously written 377 * object, or null if a null had been written. 378 * 379 * @see #writeToParcel(ComponentName, Parcel) 380 */ readFromParcel(Parcel in)381 public static ComponentName readFromParcel(Parcel in) { 382 String pkg = in.readString(); 383 return pkg != null ? new ComponentName(pkg, in) : null; 384 } 385 386 public static final @android.annotation.NonNull Parcelable.Creator<ComponentName> CREATOR 387 = new Parcelable.Creator<ComponentName>() { 388 public ComponentName createFromParcel(Parcel in) { 389 return new ComponentName(in); 390 } 391 392 public ComponentName[] newArray(int size) { 393 return new ComponentName[size]; 394 } 395 }; 396 397 /** 398 * Instantiate a new ComponentName from the data in a Parcel that was 399 * previously written with {@link #writeToParcel(Parcel, int)}. Note that you 400 * must not use this with data written by 401 * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible 402 * to handle a null ComponentObject here. 403 * 404 * @param in The Parcel containing the previously written ComponentName, 405 * positioned at the location in the buffer where it was written. 406 */ ComponentName(Parcel in)407 public ComponentName(Parcel in) { 408 mPackage = in.readString(); 409 if (mPackage == null) throw new NullPointerException( 410 "package name is null"); 411 mClass = in.readString(); 412 if (mClass == null) throw new NullPointerException( 413 "class name is null"); 414 } 415 ComponentName(String pkg, Parcel in)416 private ComponentName(String pkg, Parcel in) { 417 mPackage = pkg; 418 mClass = in.readString(); 419 } 420 421 /** 422 * Interface for classes associated with a component name. 423 * @hide 424 */ 425 @FunctionalInterface 426 public interface WithComponentName { 427 /** Return the associated component name. */ getComponentName()428 ComponentName getComponentName(); 429 } 430 } 431