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